服务启动问题Bugfix
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Fri Dec 12 12:07:02 GMT 2025
|
||||
#Fri Dec 12 13:05:40 GMT 2025
|
||||
stageCount=1
|
||||
libraryProject=
|
||||
baseVersion=15.12
|
||||
publishVersion=15.12.0
|
||||
buildCount=49
|
||||
buildCount=58
|
||||
baseBetaVersion=15.12.1
|
||||
|
||||
@@ -19,17 +19,17 @@ import android.telephony.TelephonyManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import cc.winboll.studio.contacts.activities.SettingsActivity;
|
||||
import cc.winboll.studio.contacts.activities.WinBollActivity;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
@@ -46,6 +46,7 @@ import cc.winboll.studio.libaes.views.ADsBannerView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -56,7 +57,7 @@ import java.util.List;
|
||||
*/
|
||||
public final class MainActivity extends WinBollActivity implements IWinBoLLActivity, ViewPager.OnPageChangeListener, View.OnClickListener {
|
||||
|
||||
// ====================== 常量定义区(Java 7 硬编码 API 版本,避免高版本依赖) ======================
|
||||
// ====================== 1. 常量定义区(硬编码API版本,避免高版本依赖) ======================
|
||||
public static final String TAG = "MainActivity";
|
||||
public static final int REQUEST_HOME_ACTIVITY = 0;
|
||||
public static final int REQUEST_ABOUT_ACTIVITY = 1;
|
||||
@@ -66,19 +67,20 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
private static final int REQUEST_REQUIRED_PERMISSIONS = 1002;
|
||||
private static final int REQUEST_OVERLAY_PERMISSION = 1003;
|
||||
private static final int REQUEST_CALL_SCREENING_PERMISSION = 1004;
|
||||
// API 版本常量硬编码(Java 7 兼容,不依赖 Build.VERSION_CODES 高版本字段)
|
||||
|
||||
// API版本硬编码常量(Java 7兼容)
|
||||
private static final int ANDROID_6_API = 23;
|
||||
private static final int ANDROID_8_API = 26;
|
||||
private static final int ANDROID_10_API = 29;
|
||||
private static final int ANDROID_14_API = 34;
|
||||
|
||||
// ====================== 静态成员区 ======================
|
||||
// ====================== 2. 静态成员区 ======================
|
||||
static MainActivity _MainActivity;
|
||||
|
||||
// ====================== 权限常量区(移除废弃权限,直接复用 PermissionUtils 常量) ======================
|
||||
// ====================== 3. 权限常量区 ======================
|
||||
private final String[] REQUIRED_PERMISSIONS = PermissionUtils.BASE_PERMISSIONS;
|
||||
|
||||
// ====================== UI控件成员区 ======================
|
||||
// ====================== 4. UI控件成员区 ======================
|
||||
private ADsBannerView mADsBannerView;
|
||||
private LogView mLogView;
|
||||
private Toolbar mToolbar;
|
||||
@@ -89,7 +91,7 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
private ImageView[] imageViews;
|
||||
private LinearLayout linearLayout;
|
||||
|
||||
// ====================== 业务逻辑成员区 ======================
|
||||
// ====================== 5. 业务逻辑成员区 ======================
|
||||
private MainServiceBean mMainServiceBean;
|
||||
private int currentPoint = 0;
|
||||
private TelephonyManager telephonyManager;
|
||||
@@ -97,7 +99,7 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
private List<Fragment> fragmentList;
|
||||
private List<String> tabTitleList;
|
||||
|
||||
// ====================== 接口实现区 ======================
|
||||
// ====================== 6. 接口实现区 ======================
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
@@ -108,29 +110,16 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// ====================== 生命周期函数区(优化权限检查逻辑,接入 PermissionUtils 工具类) ======================
|
||||
// ====================== 7. 生命周期函数区 ======================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate: 主Activity开始创建");
|
||||
LogUtils.d(TAG, "===== onCreate: 主Activity开始创建 =====");
|
||||
_MainActivity = this;
|
||||
|
||||
// 权限检查分流:基于 PermissionUtils 工具类,简化逻辑
|
||||
// if (!PermissionUtils.checkPermissions(this, REQUIRED_PERMISSIONS)) {
|
||||
// LogUtils.d(TAG, "onCreate: 危险权限未完全授予,发起申请");
|
||||
// PermissionUtils.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_REQUIRED_PERMISSIONS);
|
||||
// } else if (!PermissionUtils.isOverlayPermissionGranted(this)) {
|
||||
// LogUtils.d(TAG, "onCreate: 悬浮窗权限未授予,跳转设置页");
|
||||
// PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
|
||||
// } else if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
// LogUtils.d(TAG, "onCreate: 通话筛选权限未授予,跳转设置页");
|
||||
// PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
|
||||
// } else {
|
||||
// LogUtils.d(TAG, "onCreate: 所有权限已授予,初始化UI和业务逻辑");
|
||||
// initUIAndLogic(savedInstanceState);
|
||||
// }
|
||||
initUIAndLogic(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate: 主Activity创建流程结束");
|
||||
// 直接初始化UI(原权限检查逻辑注释保留,按需启用)
|
||||
initUIAndLogic(savedInstanceState);
|
||||
LogUtils.d(TAG, "===== onCreate: 主Activity创建流程结束 =====");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,25 +131,6 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// LogUtils.d(TAG, "onResume: 主Activity进入前台");
|
||||
// // 权限补全检查:使用工具类统一判断,简化代码
|
||||
// boolean isAllPermGranted = PermissionUtils.checkPermissions(this, REQUIRED_PERMISSIONS)
|
||||
// && PermissionUtils.isOverlayPermissionGranted(this);
|
||||
// boolean isCallScreeningOk = true;
|
||||
// if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
// isCallScreeningOk = PermissionUtils.isCallScreeningPermissionGranted(this);
|
||||
// }
|
||||
// if (isAllPermGranted && isCallScreeningOk && mToolbar == null) {
|
||||
// LogUtils.d(TAG, "onResume: 权限已补全,初始化UI和逻辑");
|
||||
// initUIAndLogic(null);
|
||||
// } else if (!PermissionUtils.isOverlayPermissionGranted(this)) {
|
||||
// LogUtils.w(TAG, "onResume: 悬浮窗权限仍未授予,再次提示申请");
|
||||
// PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
|
||||
// } else if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
// LogUtils.w(TAG, "onResume: 通话筛选权限仍未授予,再次提示申请");
|
||||
// PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
|
||||
// }
|
||||
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs(MainActivity.this);
|
||||
LogUtils.d(TAG, "onResume: 广告栏资源已恢复");
|
||||
@@ -170,41 +140,34 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy: 主Activity开始销毁");
|
||||
LogUtils.d(TAG, "===== onDestroy: 主Activity开始销毁 =====");
|
||||
// 释放广告资源
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.releaseAdResources();
|
||||
LogUtils.d(TAG, "onDestroy: 广告栏资源已释放");
|
||||
}
|
||||
// 移除电话状态监听,避免内存泄漏(Java 7 规范)
|
||||
// 移除电话状态监听,防止内存泄漏
|
||||
if (telephonyManager != null && phoneStateListener != null) {
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
LogUtils.d(TAG, "onDestroy: 电话状态监听器已移除");
|
||||
}
|
||||
LogUtils.d(TAG, "onDestroy: 主Activity销毁完成");
|
||||
LogUtils.d(TAG, "===== onDestroy: 主Activity销毁完成 =====");
|
||||
}
|
||||
|
||||
// ====================== 权限相关函数区(完全接入 PermissionUtils,移除冗余代码) ======================
|
||||
// ====================== 8. 权限相关回调函数区 ======================
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求结果回调,requestCode=" + requestCode);
|
||||
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求回调,requestCode=" + requestCode);
|
||||
|
||||
if (requestCode == REQUEST_REQUIRED_PERMISSIONS) {
|
||||
// 使用工具类解析被拒绝的权限,简化字符串拼接逻辑
|
||||
String deniedPerms = PermissionUtils.getDeniedPermissions(this, permissions);
|
||||
if (deniedPerms.length() == 0) {
|
||||
LogUtils.d(TAG, "onRequestPermissionsResult: 所有危险权限授予成功");
|
||||
if (PermissionUtils.isOverlayPermissionGranted(this)) {
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
|
||||
} else {
|
||||
initUIAndLogic(null);
|
||||
}
|
||||
} else {
|
||||
PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
|
||||
}
|
||||
checkAndRequestRemainingPermissions();
|
||||
} else {
|
||||
LogUtils.e(TAG, "onRequestPermissionsResult: 存在权限被拒绝,弹出提示对话框");
|
||||
String tip = "应用需要「" + deniedPerms + "」权限才能正常运行,请授予权限后重新打开应用。";
|
||||
showPermissionDeniedDialogAndExit(tip);
|
||||
LogUtils.e(TAG, "onRequestPermissionsResult: 被拒权限:" + deniedPerms);
|
||||
showPermissionDeniedDialogAndExit("应用需要「" + deniedPerms + "」权限才能正常运行,请授予权限后重新打开应用。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,113 +175,144 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, "onActivityResult: 回调触发,requestCode=" + requestCode + ",resultCode=" + resultCode);
|
||||
if (requestCode == DIALER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
LogUtils.d(TAG, "onActivityResult: 设为默认拨号应用成功");
|
||||
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else if (requestCode == REQUEST_APP_SETTINGS) {
|
||||
LogUtils.d(TAG, "onActivityResult: 从设置页返回,重建Activity");
|
||||
recreate();
|
||||
} else if (requestCode == REQUEST_OVERLAY_PERMISSION) {
|
||||
LogUtils.d(TAG, "onActivityResult: 从悬浮窗设置页返回");
|
||||
if (PermissionUtils.isOverlayPermissionGranted(this)) {
|
||||
LogUtils.d(TAG, "onActivityResult: 悬浮窗权限申请成功");
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
|
||||
} else {
|
||||
initUIAndLogic(null);
|
||||
LogUtils.d(TAG, "onActivityResult: 页面回调触发,requestCode=" + requestCode + ",resultCode=" + resultCode);
|
||||
|
||||
switch (requestCode) {
|
||||
case DIALER_REQUEST_CODE:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
LogUtils.d(TAG, "onActivityResult: 设为默认拨号应用成功");
|
||||
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
LogUtils.e(TAG, "onActivityResult: 悬浮窗权限申请失败");
|
||||
showPermissionDeniedDialogAndExit("应用需要悬浮窗权限才能展示来电弹窗,请授予后重新打开应用。");
|
||||
}
|
||||
} else if (requestCode == REQUEST_CALL_SCREENING_PERMISSION) {
|
||||
LogUtils.d(TAG, "onActivityResult: 从通话筛选设置页返回");
|
||||
if (PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
LogUtils.d(TAG, "onActivityResult: 通话筛选权限申请成功");
|
||||
initUIAndLogic(null);
|
||||
} else {
|
||||
LogUtils.e(TAG, "onActivityResult: 通话筛选权限申请失败");
|
||||
showPermissionDeniedDialogAndExit("应用需要通话筛选权限监听外拨电话,请授予后重新打开应用。");
|
||||
}
|
||||
break;
|
||||
case REQUEST_APP_SETTINGS:
|
||||
LogUtils.d(TAG, "onActivityResult: 从设置页返回,重建Activity");
|
||||
recreate();
|
||||
break;
|
||||
case REQUEST_OVERLAY_PERMISSION:
|
||||
handleOverlayPermissionResult();
|
||||
break;
|
||||
case REQUEST_CALL_SCREENING_PERMISSION:
|
||||
handleCallScreeningPermissionResult();
|
||||
break;
|
||||
default:
|
||||
LogUtils.w(TAG, "onActivityResult: 未知requestCode=" + requestCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限拒绝提示对话框(纯 Java 7 匿名内部类,无 Lambda)
|
||||
* 处理悬浮窗权限申请结果
|
||||
*/
|
||||
private void handleOverlayPermissionResult() {
|
||||
if (PermissionUtils.isOverlayPermissionGranted(this)) {
|
||||
LogUtils.d(TAG, "handleOverlayPermissionResult: 悬浮窗权限申请成功");
|
||||
checkAndRequestRemainingPermissions();
|
||||
} else {
|
||||
LogUtils.e(TAG, "handleOverlayPermissionResult: 悬浮窗权限申请失败");
|
||||
showPermissionDeniedDialogAndExit("应用需要悬浮窗权限才能展示来电弹窗,请授予后重新打开应用。");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理通话筛选权限申请结果
|
||||
*/
|
||||
private void handleCallScreeningPermissionResult() {
|
||||
if (PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
LogUtils.d(TAG, "handleCallScreeningPermissionResult: 通话筛选权限申请成功");
|
||||
initUIAndLogic(null);
|
||||
} else {
|
||||
LogUtils.e(TAG, "handleCallScreeningPermissionResult: 通话筛选权限申请失败");
|
||||
showPermissionDeniedDialogAndExit("应用需要通话筛选权限监听外拨电话,请授予后重新打开应用。");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并申请剩余权限(悬浮窗+通话筛选)
|
||||
*/
|
||||
private void checkAndRequestRemainingPermissions() {
|
||||
if (!PermissionUtils.isOverlayPermissionGranted(this)) {
|
||||
LogUtils.d(TAG, "checkAndRequestRemainingPermissions: 悬浮窗权限未授予,跳转设置页");
|
||||
PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
|
||||
} else if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
|
||||
LogUtils.d(TAG, "checkAndRequestRemainingPermissions: 通话筛选权限未授予,跳转设置页");
|
||||
PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
|
||||
} else {
|
||||
LogUtils.d(TAG, "checkAndRequestRemainingPermissions: 所有权限已授予");
|
||||
initUIAndLogic(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限拒绝提示对话框(Java 7 匿名内部类实现)
|
||||
*/
|
||||
private void showPermissionDeniedDialogAndExit(String tip) {
|
||||
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 弹出权限不足提示框");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("权限不足,无法使用");
|
||||
builder.setMessage(tip);
|
||||
builder.setCancelable(false);
|
||||
|
||||
builder.setNegativeButton("去设置", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户点击设置权限,跳转应用设置页");
|
||||
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户选择去设置权限");
|
||||
PermissionUtils.goAppDetailsSettings(MainActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
builder.setPositiveButton("确定退出", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户点击退出,关闭应用");
|
||||
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户选择退出应用");
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
});
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
// ====================== UI与逻辑初始化区(修复服务启动判断,优化内存管理) ======================
|
||||
// ====================== 9. UI与业务逻辑初始化区 ======================
|
||||
private void initUIAndLogic(Bundle savedInstanceState) {
|
||||
if (mToolbar != null) {
|
||||
LogUtils.d(TAG, "initUIAndLogic: UI已初始化,无需重复执行");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "initUIAndLogic: 开始初始化UI布局");
|
||||
|
||||
LogUtils.d(TAG, "===== initUIAndLogic: 开始初始化UI与业务逻辑 =====");
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// 工具栏初始化
|
||||
// 1. 工具栏初始化
|
||||
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
|
||||
setSupportActionBar(mToolbar);
|
||||
getSupportActionBar().setSubtitle(TAG);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// 显示状态栏
|
||||
getWindow().getInsetsController().show(WindowInsets.Type.statusBars());
|
||||
// 设置滑动时状态栏暂显的正常行为
|
||||
getWindow().getInsetsController().setSystemBarsBehavior(
|
||||
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
);
|
||||
}
|
||||
LogUtils.d(TAG, "initUIAndLogic: 工具栏初始化完成");
|
||||
|
||||
// Tab与ViewPager初始化
|
||||
// 2. TabLayout与ViewPager初始化
|
||||
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
|
||||
viewPager = (ViewPager) findViewById(R.id.viewPager);
|
||||
initViewPagerAndTabs();
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
LogUtils.d(TAG, "initUIAndLogic: ViewPager与TabLayout初始化完成");
|
||||
|
||||
// 广告栏初始化
|
||||
// 3. 广告栏初始化
|
||||
mADsBannerView = (ADsBannerView) findViewById(R.id.adsbanner);
|
||||
LogUtils.d(TAG, "initUIAndLogic: 广告栏控件初始化完成");
|
||||
|
||||
// 服务启动逻辑
|
||||
// 4. 主服务初始化
|
||||
initMainService();
|
||||
LogUtils.d(TAG, "initUIAndLogic: 主服务初始化完成");
|
||||
|
||||
// 电话状态监听初始化(适配 API 30,启动通话筛选服务)
|
||||
// 5. 电话状态监听初始化
|
||||
initPhoneStateListener();
|
||||
LogUtils.d(TAG, "initUIAndLogic: 电话状态监听器初始化完成");
|
||||
|
||||
// 盾值视图初始化(Java 7 数组初始化规范)
|
||||
// 6. 盾值视图初始化
|
||||
DunTemperatureView tempView = (DunTemperatureView) findViewById(R.id.dun_temp_view);
|
||||
tempView.setMaxValue(Rules.getInstance(this).getSettingsModel().getDunTotalCount());
|
||||
tempView.setCurrentValue(Rules.getInstance(this).getSettingsModel().getDunCurrentCount());
|
||||
|
||||
int[] customColors = new int[2];
|
||||
customColors[0] = Color.parseColor("#FF3366FF");
|
||||
customColors[1] = Color.parseColor("#FF9900CC");
|
||||
@@ -326,12 +320,19 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
positions[0] = 0.0f;
|
||||
positions[1] = 1.0f;
|
||||
tempView.setGradientColors(customColors, positions);
|
||||
LogUtils.d(TAG, "initUIAndLogic: 盾值视图初始化完成");
|
||||
LogUtils.d(TAG, "===== initUIAndLogic: 初始化流程全部结束 =====");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化ViewPager与Tab数据
|
||||
*/
|
||||
private void initViewPagerAndTabs() {
|
||||
LogUtils.d(TAG, "initViewPagerAndTabs: 开始初始化ViewPager数据");
|
||||
fragmentList = new ArrayList<Fragment>();
|
||||
tabTitleList = new ArrayList<String>();
|
||||
|
||||
// 添加Fragment与标题
|
||||
fragmentList.add(CallLogFragment.newInstance(0));
|
||||
fragmentList.add(ContactsFragment.newInstance(1));
|
||||
fragmentList.add(LogFragment.newInstance(2));
|
||||
@@ -339,81 +340,93 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
tabTitleList.add("联系人");
|
||||
tabTitleList.add("应用日志");
|
||||
|
||||
// 设置适配器
|
||||
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
|
||||
viewPager.setAdapter(adapter);
|
||||
viewPager.setOffscreenPageLimit(2); // 优化:保留2个缓存页,减少重建
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
viewPager.addOnPageChangeListener(this);
|
||||
|
||||
LogUtils.d(TAG, "initViewPagerAndTabs: ViewPager初始化完成,Fragment数量=" + fragmentList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化主服务
|
||||
*/
|
||||
private void initMainService() {
|
||||
LogUtils.d(TAG, "initMainService: 开始初始化主服务配置");
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
|
||||
if (mMainServiceBean == null) {
|
||||
LogUtils.d(TAG, "initMainService: 服务配置Bean不存在,创建新实例并保存");
|
||||
LogUtils.d(TAG, "initMainService: 服务配置Bean不存在,创建新实例");
|
||||
mMainServiceBean = new MainServiceBean();
|
||||
MainServiceBean.saveBean(this, mMainServiceBean);
|
||||
}
|
||||
|
||||
// 双重判断:服务是否启用 + 是否已运行,避免重复启动
|
||||
// 检查服务状态并启动
|
||||
if (mMainServiceBean.isEnable() && !isServiceRunning(MainService.class)) {
|
||||
LogUtils.d(TAG, "initMainService: 主服务已启用且未运行,延迟1秒启动服务");
|
||||
LogUtils.d(TAG, "initMainService: 主服务已启用且未运行,延迟1秒启动");
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MainService.startMainService(MainActivity.this);
|
||||
LogUtils.d(TAG, "initMainService: 主服务启动任务执行");
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
LogUtils.d(TAG, "initMainService: 主服务未启用或已运行,跳过启动流程");
|
||||
String status = mMainServiceBean.isEnable() ? "已启用但运行中" : "未启用";
|
||||
LogUtils.d(TAG, "initMainService: 主服务" + status + ",跳过启动流程");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化通话监听(API 30 适配,启动通话筛选服务)
|
||||
* 初始化电话状态监听与通话筛选服务
|
||||
*/
|
||||
private void initPhoneStateListener() {
|
||||
LogUtils.d(TAG, "initPhoneStateListener: 开始初始化电话监听");
|
||||
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
|
||||
phoneStateListener = new MyPhoneStateListener();
|
||||
// 仅监听通话状态,避免冗余监听
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
|
||||
// API 30+ 启动通话筛选服务,替代 PROCESS_OUTGOING_CALLS 权限
|
||||
// API 10+ 启动通话筛选服务
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
Intent screeningIntent = new Intent(this, MyCallScreeningService.class);
|
||||
// 适配 Android O 以上前台服务启动要求
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
startForegroundService(screeningIntent);
|
||||
LogUtils.d(TAG, "initPhoneStateListener: 以前台服务方式启动通话筛选服务");
|
||||
} else {
|
||||
startService(screeningIntent);
|
||||
LogUtils.d(TAG, "initPhoneStateListener: 以普通服务方式启动通话筛选服务");
|
||||
}
|
||||
LogUtils.d(TAG, "initPhoneStateListener: API 30+ 通话筛选服务已启动");
|
||||
}
|
||||
LogUtils.d(TAG, "initPhoneStateListener: 电话监听初始化完成");
|
||||
}
|
||||
|
||||
// ====================== 菜单相关函数区 ======================
|
||||
// ====================== 10. 菜单相关函数区 ======================
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
LogUtils.d(TAG, "onCreateOptionsMenu: 菜单加载完成");
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.item_settings) {
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 用户点击设置菜单,跳转设置页面");
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 用户点击设置菜单");
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// ====================== 页面回调函数区 ======================
|
||||
// ====================== 11. ViewPager页面回调区 ======================
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
currentPoint = position;
|
||||
LogUtils.d(TAG, "onPageSelected: 页面切换至索引" + position + ",标题=" + tabTitleList.get(position));
|
||||
LogUtils.d(TAG, "onPageSelected: 页面切换至[" + position + "],标题=" + tabTitleList.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -422,7 +435,10 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
@Override
|
||||
public void onClick(View v) {}
|
||||
|
||||
// ====================== 电话相关工具函数区(优化权限检查,接入工具类) ======================
|
||||
// ====================== 12. 工具函数区 ======================
|
||||
/**
|
||||
* 拨号工具方法
|
||||
*/
|
||||
public static void dialPhoneNumber(String phoneNumber) {
|
||||
if (PermissionUtils.checkPermission(_MainActivity, Manifest.permission.CALL_PHONE)) {
|
||||
Intent intent = new Intent(Intent.ACTION_DIAL);
|
||||
@@ -435,20 +451,25 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为默认拨号应用
|
||||
*/
|
||||
public boolean isDefaultPhoneCallApp() {
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_6_API) {
|
||||
TelecomManager manager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
|
||||
if (manager != null && manager.getDefaultDialerPackage() != null) {
|
||||
boolean isDefault = manager.getDefaultDialerPackage().equals(getPackageName());
|
||||
LogUtils.d(TAG, "isDefaultPhoneCallApp: 当前应用是否为默认拨号应用=" + isDefault);
|
||||
LogUtils.d(TAG, "isDefaultPhoneCallApp: 是否为默认拨号应用=" + isDefault);
|
||||
return isDefault;
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "isDefaultPhoneCallApp: 系统版本低于M,无法判断默认拨号应用");
|
||||
LogUtils.d(TAG, "isDefaultPhoneCallApp: 系统版本低于Android 6,无法判断");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====================== 服务状态工具函数区 ======================
|
||||
/**
|
||||
* 检查服务是否正在运行
|
||||
*/
|
||||
public boolean isServiceRunning(Class<?> serviceClass) {
|
||||
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (manager == null) {
|
||||
@@ -458,16 +479,19 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
|
||||
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (serviceClass.getName().equals(service.service.getClassName())) {
|
||||
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getName() + "]正在运行");
|
||||
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getSimpleName() + "]正在运行");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getName() + "]未运行");
|
||||
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getSimpleName() + "]未运行");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====================== 内部类定义区(Java 7 规范,无 Lambda) ======================
|
||||
private class MyPagerAdapter extends FragmentPagerAdapter {
|
||||
// ====================== 13. 内部类定义区(Java 7 规范) ======================
|
||||
/**
|
||||
* ViewPager适配器(静态内部类减少内存泄漏风险)
|
||||
*/
|
||||
private static class MyPagerAdapter extends FragmentPagerAdapter {
|
||||
private final List<Fragment> fragmentList;
|
||||
private final List<String> tabTitleList;
|
||||
|
||||
@@ -475,7 +499,7 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
super(fm);
|
||||
this.fragmentList = fragmentList;
|
||||
this.tabTitleList = tabTitleList;
|
||||
LogUtils.d(TAG, "MyPagerAdapter: ViewPager适配器初始化,Fragment数量=" + fragmentList.size());
|
||||
LogUtils.d(MainActivity.TAG, "MyPagerAdapter: 初始化完成,Fragment数量=" + fragmentList.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -494,24 +518,29 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 电话状态监听器
|
||||
*/
|
||||
private class MyPhoneStateListener extends PhoneStateListener {
|
||||
@Override
|
||||
public void onCallStateChanged(int state, String incomingNumber) {
|
||||
super.onCallStateChanged(state, incomingNumber);
|
||||
String stateDesc = "";
|
||||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 电话状态-空闲");
|
||||
stateDesc = "空闲";
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 电话状态-通话中");
|
||||
stateDesc = "通话中";
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 电话状态-来电,号码=" + incomingNumber);
|
||||
stateDesc = "来电(" + incomingNumber + ")";
|
||||
break;
|
||||
default:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 未知通话状态,state=" + state);
|
||||
stateDesc = "未知状态(" + state + ")";
|
||||
break;
|
||||
}
|
||||
LogUtils.d(TAG, "onCallStateChanged: 电话状态=" + stateDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
@@ -28,89 +30,99 @@ import cc.winboll.studio.contacts.phonecallui.PhoneCallService;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/12 16:06
|
||||
* @Describe 通话监听服务,监听来电状态并展示悬浮窗(适配 Java7 + Android API30)
|
||||
* 终极优化版:onCreate 仅注册前台服务,其他逻辑延迟初始化,避免阻塞
|
||||
* 适配 Java7 + Android API30
|
||||
*/
|
||||
public class CallListenerService extends Service {
|
||||
|
||||
// 常量定义区(硬编码,避免依赖高版本API)
|
||||
private static final String TAG = "CallListenerService";
|
||||
private static final String CHANNEL_ID = "call_listener_channel";
|
||||
private static final int NOTIFICATION_ID = 1003;
|
||||
// phoneCall 前台服务类型正确值(0x80 对应 phoneCall,与清单配置一致)
|
||||
private static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 0x80;
|
||||
// API版本常量硬编码(Java7兼容,不用Build.VERSION_CODES)
|
||||
private static final int ANDROID_8_API = 26;
|
||||
private static final int ANDROID_10_API = 29;
|
||||
private static final int ANDROID_19_API = 19;
|
||||
|
||||
// View 相关属性
|
||||
// View 与服务相关属性
|
||||
private View phoneCallView;
|
||||
private TextView tvCallNumber;
|
||||
private Button btnOpenApp;
|
||||
|
||||
// 系统服务相关属性
|
||||
private WindowManager windowManager;
|
||||
private WindowManager.LayoutParams params;
|
||||
private PhoneStateListener phoneStateListener;
|
||||
private TelephonyManager telephonyManager;
|
||||
|
||||
// 业务逻辑相关属性
|
||||
private String callNumber;
|
||||
private boolean hasShown;
|
||||
private boolean isCallingIn;
|
||||
// 延迟初始化 Handler
|
||||
private Handler mDelayHandler;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate: 通话监听服务启动");
|
||||
// 前台服务启动:API30属于Android10+,直接传类型参数
|
||||
Notification notification = createForegroundNotification();
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_PHONE_CALL);
|
||||
LogUtils.d(TAG, "onCreate: 前台服务启动(phoneCall类型,API30+)");
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
LogUtils.d(TAG, "onCreate: 前台服务启动(低版本兼容)");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "onCreate: 前台服务启动失败,类型不匹配", e);
|
||||
LogUtils.d(TAG, "onCreate: 服务启动,立即注册前台服务");
|
||||
// ========== 第一步:无条件优先执行前台服务注册,这是唯一必须同步做的事 ==========
|
||||
boolean foregroundSuccess = startForegroundImmediately();
|
||||
if (!foregroundSuccess) {
|
||||
LogUtils.e(TAG, "onCreate: 前台服务注册失败,直接停止服务");
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
initPhoneStateListener();
|
||||
initPhoneCallView();
|
||||
|
||||
// ========== 第二步:所有其他逻辑延迟 100ms 执行,让出主线程,避免阻塞 ==========
|
||||
mDelayHandler = new Handler(Looper.getMainLooper());
|
||||
mDelayHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initDelayedLogic();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
LogUtils.d(TAG, "onBind: 服务绑定");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化前台服务通知(适配Android8.0+渠道要求,Java7写法)
|
||||
* 核心:仅负责前台服务注册,无任何其他逻辑,确保 5 秒内完成
|
||||
* @return true 注册成功,false 失败
|
||||
*/
|
||||
private boolean startForegroundImmediately() {
|
||||
try {
|
||||
Notification notification = createForegroundNotification();
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_PHONE_CALL);
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
LogUtils.d(TAG, "startForegroundImmediately: 前台服务注册成功");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "startForegroundImmediately: 注册失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化通知创建逻辑,只保留必要参数,确保最快创建
|
||||
*/
|
||||
private Notification createForegroundNotification() {
|
||||
LogUtils.d(TAG, "createForegroundNotification: 创建前台服务通知");
|
||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
// Android8.0+创建通知渠道
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
// 8.0+ 创建渠道,极简配置
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API && manager != null) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"通话监听服务",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("后台监听通话状态");
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
|
||||
}
|
||||
CHANNEL_ID, "通话监听", NotificationManager.IMPORTANCE_LOW);
|
||||
channel.setSound(null, null);
|
||||
channel.enableVibration(false);
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
// 构建通知(Java7不支持链式调用简化,分步设置)
|
||||
|
||||
// 通知构建:必须确保小图标存在!!!替换为你项目中真实存在的图标资源
|
||||
Notification.Builder builder = new Notification.Builder(this);
|
||||
builder.setSmallIcon(R.drawable.ic_winboll);
|
||||
builder.setContentTitle("通话监听中");
|
||||
builder.setContentText("服务运行中");
|
||||
// 重点:这里的 ic_launcher 必须是 res/drawable 下存在的图标,否则通知创建失败
|
||||
builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
builder.setContentTitle("通话监听");
|
||||
builder.setContentText("运行中");
|
||||
builder.setPriority(Notification.PRIORITY_LOW);
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
builder.setChannelId(CHANNEL_ID);
|
||||
@@ -119,53 +131,47 @@ public class CallListenerService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化来电状态监听器,添加空指针兜底
|
||||
* 延迟初始化所有非核心逻辑:电话监听、悬浮窗
|
||||
*/
|
||||
private void initDelayedLogic() {
|
||||
LogUtils.d(TAG, "initDelayedLogic: 开始初始化延迟逻辑");
|
||||
initPhoneStateListener();
|
||||
initPhoneCallView();
|
||||
LogUtils.d(TAG, "initDelayedLogic: 延迟逻辑初始化完成");
|
||||
}
|
||||
|
||||
private void initPhoneStateListener() {
|
||||
LogUtils.d(TAG, "initPhoneStateListener: 初始化来电状态监听器");
|
||||
phoneStateListener = new PhoneStateListener() {
|
||||
@Override
|
||||
public void onCallStateChanged(int state, String incomingNumber) {
|
||||
super.onCallStateChanged(state, incomingNumber);
|
||||
callNumber = incomingNumber;
|
||||
LogUtils.d(TAG, "onCallStateChanged: 状态变更 state=" + state + " number=" + incomingNumber);
|
||||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 通话结束,关闭悬浮窗");
|
||||
dismiss();
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 来电响铃,展示悬浮窗");
|
||||
isCallingIn = true;
|
||||
updateUI();
|
||||
show();
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 通话接通,展示悬浮窗");
|
||||
updateUI();
|
||||
show();
|
||||
break;
|
||||
default:
|
||||
LogUtils.d(TAG, "onCallStateChanged: 未知通话状态");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 空指针兜底,避免TelephonyManager获取失败
|
||||
|
||||
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
||||
if (telephonyManager != null) {
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
LogUtils.d(TAG, "initPhoneStateListener: 监听器注册成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "initPhoneStateListener: TelephonyManager获取失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void initPhoneCallView() {
|
||||
LogUtils.d(TAG, "initPhoneCallView: 初始化悬浮窗视图");
|
||||
windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
|
||||
if (windowManager == null) {
|
||||
LogUtils.e(TAG, "initPhoneCallView: WindowManager获取失败");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,47 +183,37 @@ public class CallListenerService extends Service {
|
||||
params.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
params.format = PixelFormat.TRANSLUCENT;
|
||||
|
||||
// 悬浮窗类型适配(Android8.0+用TYPE_APPLICATION_OVERLAY)
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
} else {
|
||||
params.type = WindowManager.LayoutParams.TYPE_PHONE;
|
||||
}
|
||||
|
||||
// 设置Flag,避免遮挡状态栏
|
||||
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
params.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
|
||||
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
|
||||
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_19_API) {
|
||||
params.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
|
||||
}
|
||||
|
||||
// 拦截返回键的布局
|
||||
FrameLayout interceptorLayout = new FrameLayout(this) {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||
LogUtils.d(TAG, "dispatchKeyEvent: 拦截返回键");
|
||||
return true;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载悬浮窗布局
|
||||
phoneCallView = LayoutInflater.from(this).inflate(R.layout.view_phone_call, interceptorLayout);
|
||||
tvCallNumber = (TextView) phoneCallView.findViewById(R.id.tv_call_number);
|
||||
btnOpenApp = (Button) phoneCallView.findViewById(R.id.btn_open_app);
|
||||
// 按钮点击事件(Java7匿名内部类写法)
|
||||
btnOpenApp.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "onClick: 点击打开通话页面 number=" + callNumber);
|
||||
PhoneCallService.CallType callType = isCallingIn ? PhoneCallService.CallType.CALL_IN : PhoneCallService.CallType.CALL_OUT;
|
||||
PhoneCallActivity.actionStart(CallListenerService.this, callNumber, callType);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "initPhoneCallView: 悬浮窗视图初始化完成");
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
PhoneCallService.CallType type = isCallingIn ? PhoneCallService.CallType.CALL_IN : PhoneCallService.CallType.CALL_OUT;
|
||||
PhoneCallActivity.actionStart(CallListenerService.this, callNumber, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void show() {
|
||||
@@ -225,51 +221,38 @@ public class CallListenerService extends Service {
|
||||
try {
|
||||
windowManager.addView(phoneCallView, params);
|
||||
hasShown = true;
|
||||
LogUtils.d(TAG, "show: 悬浮窗展示成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "show: 悬浮窗展示失败", e);
|
||||
LogUtils.e(TAG, "show: 悬浮窗添加失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭悬浮窗,添加try-catch避免崩溃
|
||||
*/
|
||||
private void dismiss() {
|
||||
if (hasShown && phoneCallView != null && windowManager != null) {
|
||||
try {
|
||||
windowManager.removeView(phoneCallView);
|
||||
LogUtils.d(TAG, "dismiss: 悬浮窗关闭成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "dismiss: 悬浮窗关闭失败", e);
|
||||
LogUtils.e(TAG, "dismiss: 悬浮窗移除失败", e);
|
||||
} finally {
|
||||
isCallingIn = false;
|
||||
hasShown = false;
|
||||
isCallingIn = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
if (tvCallNumber == null) {
|
||||
LogUtils.e(TAG, "updateUI: 号码显示控件为空");
|
||||
return;
|
||||
}
|
||||
String formatNumber = formatPhoneNumber(callNumber);
|
||||
tvCallNumber.setText(formatNumber);
|
||||
int callTypeDrawable = isCallingIn ? R.drawable.ic_phone_call_in : R.drawable.ic_phone_call_out;
|
||||
tvCallNumber.setCompoundDrawablesWithIntrinsicBounds(null, null,
|
||||
getResources().getDrawable(callTypeDrawable), null);
|
||||
LogUtils.d(TAG, "updateUI: 悬浮窗UI更新完成 number=" + formatNumber);
|
||||
int drawableId = isCallingIn ? R.drawable.ic_phone_call_in : R.drawable.ic_phone_call_out;
|
||||
tvCallNumber.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(drawableId), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化手机号(11位手机号加分隔符)
|
||||
*/
|
||||
public static String formatPhoneNumber(String phoneNum) {
|
||||
if (!TextUtils.isEmpty(phoneNum) && phoneNum.length() == 11) {
|
||||
return phoneNum.substring(0, 3) + "-"
|
||||
+ phoneNum.substring(3, 7) + "-"
|
||||
+ phoneNum.substring(7);
|
||||
return phoneNum.substring(0, 3) + "-" + phoneNum.substring(3, 7) + "-" + phoneNum.substring(7);
|
||||
}
|
||||
return phoneNum;
|
||||
}
|
||||
@@ -277,20 +260,18 @@ public class CallListenerService extends Service {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy: 通话监听服务销毁");
|
||||
// 释放资源,避免内存泄漏
|
||||
dismiss();
|
||||
if (telephonyManager != null) {
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
LogUtils.d(TAG, "onDestroy: 监听器注销成功");
|
||||
}
|
||||
// 清空引用,帮助GC回收
|
||||
if (mDelayHandler != null) {
|
||||
mDelayHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
// 清空所有引用
|
||||
phoneStateListener = null;
|
||||
telephonyManager = null;
|
||||
windowManager = null;
|
||||
phoneCallView = null;
|
||||
tvCallNumber = null;
|
||||
btnOpenApp = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cc.winboll.studio.contacts.services;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -14,7 +15,6 @@ import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.bobulltoon.TomCat;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
@@ -26,7 +26,6 @@ import cc.winboll.studio.contacts.receivers.MainReceiver;
|
||||
import cc.winboll.studio.contacts.threads.MainServiceThread;
|
||||
import cc.winboll.studio.contacts.utils.AppForegroundUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@@ -34,10 +33,10 @@ import java.util.TimerTask;
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 06:56:41
|
||||
* @Describe 拨号主服务,负责核心业务逻辑、守护进程绑定、铃声音量监控及通话监听启动
|
||||
* 适配 Android API 30 + Java 7 语法
|
||||
* 严格适配 Android API 30 + Java 7 语法规范 | 解决前台服务启动超时崩溃
|
||||
*/
|
||||
public class MainService extends Service {
|
||||
// ====================== 常量定义区 ======================
|
||||
// ====================== 常量定义区(全硬编码,杜绝高版本 API 依赖) ======================
|
||||
public static final String TAG = "MainService";
|
||||
public static final int MSG_UPDATE_STATUS = 0;
|
||||
// 铃声音量检查定时器参数
|
||||
@@ -46,20 +45,21 @@ public class MainService extends Service {
|
||||
// 前台服务通知配置
|
||||
private static final String FOREGROUND_CHANNEL_ID = "main_service_foreground_channel";
|
||||
private static final int FOREGROUND_NOTIFICATION_ID = 1001;
|
||||
// 修复:前台服务类型改为 dataSync(0x01),与 Manifest 配置匹配,解决崩溃
|
||||
// 前台服务类型硬编码:dataSync(0x01) | 替代 Build.VERSION_CODES 高版本常量
|
||||
private static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 0x00000001;
|
||||
// 版本常量硬编码(Java 7 兼容,不依赖 Build.VERSION_CODES 高版本字段)
|
||||
// Android 版本常量硬编码 | Java 7 不支持 Build.VERSION_CODES 高版本引用
|
||||
private static final int ANDROID_8_API = 26;
|
||||
private static final int ANDROID_10_API = 29;
|
||||
private static final int ANDROID_12_API = 31;
|
||||
// 重试延迟时间
|
||||
// 重试延迟时间 & 非核心服务延迟启动时间
|
||||
private static final long RETRY_DELAY_MS = 3000L;
|
||||
private static final long DELAY_NON_CORE_SERVICE_MS = 100L;
|
||||
|
||||
// ====================== 静态成员变量区 ======================
|
||||
private static MainService sMainServiceInstance;
|
||||
private static volatile TomCat sTomCatInstance;
|
||||
|
||||
// ====================== 成员变量区 ======================
|
||||
// ====================== 成员变量区(新增主窗口销毁状态标记) ======================
|
||||
private volatile boolean mIsServiceRunning;
|
||||
private MainServiceBean mMainServiceBean;
|
||||
private MainServiceThread mMainServiceThread;
|
||||
@@ -69,16 +69,25 @@ public class MainService extends Service {
|
||||
private boolean mIsBound;
|
||||
private MainReceiver mMainReceiver;
|
||||
private Timer mStreamVolumeCheckTimer;
|
||||
private Handler mDelayHandler;
|
||||
// 核心:标记主窗口是否已销毁,阻断后续无效服务启动
|
||||
private boolean mIsMainWindowDestroyed = false;
|
||||
|
||||
// ====================== Binder 内部类 ======================
|
||||
// ====================== Binder 内部类(Java 7 规范写法,新增窗口状态设置方法) ======================
|
||||
public class MyBinder extends Binder {
|
||||
public MainService getService() {
|
||||
LogUtils.d(TAG, "MyBinder.getService: 获取 MainService 实例");
|
||||
return MainService.this;
|
||||
}
|
||||
|
||||
// 对外暴露:设置主窗口销毁状态
|
||||
public void setMainWindowDestroyed(boolean isDestroyed) {
|
||||
mIsMainWindowDestroyed = isDestroyed;
|
||||
LogUtils.w(TAG, "MyBinder.setMainWindowDestroyed: 主窗口销毁状态更新为 " + isDestroyed);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== ServiceConnection 内部类 ======================
|
||||
// ====================== ServiceConnection 内部类(Java 7 匿名内部类规范) ======================
|
||||
private class MyServiceConnection implements ServiceConnection {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
@@ -105,20 +114,22 @@ public class MainService extends Service {
|
||||
mAssistantService = null;
|
||||
mIsBound = false;
|
||||
|
||||
if (mMainServiceBean != null && mMainServiceBean.isEnable()) {
|
||||
// 新增:主窗口销毁后,不再重试绑定守护服务
|
||||
if (mMainServiceBean != null && mMainServiceBean.isEnable() && !mIsMainWindowDestroyed) {
|
||||
LogUtils.d(TAG, "MyServiceConnection.onServiceDisconnected: 重新唤醒并绑定守护服务");
|
||||
// Java 7 匿名 Runnable,替代 Lambda
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
wakeupAndBindAssistant();
|
||||
}
|
||||
}, RETRY_DELAY_MS);
|
||||
} else {
|
||||
LogUtils.w(TAG, "MyServiceConnection.onServiceDisconnected: 主窗口已销毁/服务未启用,跳过重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 对外静态方法区 ======================
|
||||
// ====================== 对外静态方法区(强化参数校验,添加日志) ======================
|
||||
public static boolean isPhoneInBoBullToon(String phone) {
|
||||
if (sTomCatInstance != null && phone != null && !phone.isEmpty()) {
|
||||
return sTomCatInstance.isPhoneBoBullToon(phone);
|
||||
@@ -143,7 +154,7 @@ public class MainService extends Service {
|
||||
}
|
||||
LogUtils.d(TAG, "startMainService: 执行启动主服务操作");
|
||||
Intent intent = new Intent(context, MainService.class);
|
||||
// API 30 属于 Android 10+,按 10+ 逻辑处理前台启动
|
||||
// API 30 属于 Android 10+,按前台服务逻辑处理
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !AppForegroundUtils.isAppForeground(context)) {
|
||||
LogUtils.d(TAG, "startMainService: 应用后台,使用 startForegroundService 启动");
|
||||
context.startForegroundService(intent);
|
||||
@@ -193,17 +204,15 @@ public class MainService extends Service {
|
||||
startMainService(context);
|
||||
}
|
||||
|
||||
// ====================== 成员方法区 ======================
|
||||
// ====================== 成员方法区(新增服务启动前置校验 + 延迟启动封装) ======================
|
||||
/**
|
||||
* 补充缺失的 appenMessage 方法
|
||||
* 用于接收并处理消息,支持日志打印和通过 Handler 转发到主线程
|
||||
*/
|
||||
public void appenMessage(String message) {
|
||||
// 空指针防护,避免传入 null 导致崩溃
|
||||
String msg = message == null ? "null" : message;
|
||||
LogUtils.d(TAG, "追加消息: " + msg);
|
||||
|
||||
// 可选:将消息通过 Handler 转发,用于更新 UI 或后续业务处理
|
||||
if (mMainServiceHandler != null) {
|
||||
android.os.Message handlerMsg = android.os.Message.obtain();
|
||||
handlerMsg.what = MSG_UPDATE_STATUS;
|
||||
@@ -212,8 +221,65 @@ public class MainService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务启动前置校验(核心:阻断无效启动)
|
||||
* @param serviceClass 待启动服务类
|
||||
* @return true=允许启动 false=禁止启动
|
||||
*/
|
||||
private boolean canStartService(Class<?> serviceClass) {
|
||||
// 1. 主窗口已销毁 → 禁止启动
|
||||
if (mIsMainWindowDestroyed) {
|
||||
LogUtils.w(TAG, "canStartService: 主窗口已销毁,禁止启动 " + serviceClass.getSimpleName());
|
||||
return false;
|
||||
}
|
||||
// 2. 服务未启用/已运行 → 禁止启动
|
||||
if (mMainServiceBean == null || !mMainServiceBean.isEnable()) {
|
||||
LogUtils.w(TAG, "canStartService: 主服务未启用,禁止启动 " + serviceClass.getSimpleName());
|
||||
return false;
|
||||
}
|
||||
if (isServiceRunning(serviceClass)) {
|
||||
LogUtils.d(TAG, "canStartService: " + serviceClass.getSimpleName() + " 已运行,跳过启动");
|
||||
return false;
|
||||
}
|
||||
// 3. Android 12+ 应用后台 → 禁止启动非核心服务
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_12_API && !AppForegroundUtils.isAppForeground(this)) {
|
||||
LogUtils.w(TAG, "canStartService: Android 12+ 应用后台,禁止启动 " + serviceClass.getSimpleName());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟启动非核心服务(降低主线程负载,避免前台服务超时)
|
||||
* @param serviceClass 待启动服务类
|
||||
*/
|
||||
private void delayStartNonCoreService(final Class<?> serviceClass) {
|
||||
if (!canStartService(serviceClass)) {
|
||||
return;
|
||||
}
|
||||
mDelayHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent intent = new Intent(MainService.this, serviceClass);
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !AppForegroundUtils.isAppForeground(MainService.this)) {
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
LogUtils.d(TAG, "delayStartNonCoreService: " + serviceClass.getSimpleName() + " 启动成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "delayStartNonCoreService: " + serviceClass.getSimpleName() + " 启动失败", e);
|
||||
}
|
||||
}
|
||||
}, DELAY_NON_CORE_SERVICE_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建前台服务通知(Java 7 分步构建,强化版本兼容 + 异常防护)
|
||||
*/
|
||||
private Notification createForegroundNotification() {
|
||||
// 1. 创建通知渠道(Android 8.0+ 必需)
|
||||
// 1. 创建通知渠道(Android 8.0+ 必需,添加空指针防护)
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
FOREGROUND_CHANNEL_ID,
|
||||
@@ -221,21 +287,26 @@ public class MainService extends Service {
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("主服务后台运行,保障拨号功能正常");
|
||||
// 空指针防护
|
||||
channel.setSound(null, null);
|
||||
channel.enableVibration(false);
|
||||
|
||||
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "createForegroundNotification: NotificationManager 获取失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 构建通知(Java 7 分步设置,取消链式调用简化写法)
|
||||
// 2. 构建通知(Java 7 分步设置,禁止链式调用简化)
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
builder = new Notification.Builder(this, FOREGROUND_CHANNEL_ID);
|
||||
} else {
|
||||
builder = new Notification.Builder(this);
|
||||
}
|
||||
// 关键:确保图标资源存在,否则通知创建失败导致前台服务启动超时
|
||||
builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
builder.setContentTitle("拨号服务运行中");
|
||||
builder.setContentText("正在后台保障通话监听与号码识别");
|
||||
@@ -245,35 +316,76 @@ public class MainService extends Service {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// ====================== Service 生命周期方法区 ======================
|
||||
/**
|
||||
* 启动通话监听服务(重构:改为延迟启动非核心服务)
|
||||
*/
|
||||
private void startPhoneCallListener() {
|
||||
delayStartNonCoreService(CallListenerService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查服务是否正在运行(通用工具方法,避免重复代码)
|
||||
*/
|
||||
private boolean isServiceRunning(Class<?> serviceClass) {
|
||||
if (serviceClass == null) {
|
||||
LogUtils.e(TAG, "isServiceRunning: 服务类参数为 null");
|
||||
return false;
|
||||
}
|
||||
|
||||
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (manager == null) {
|
||||
LogUtils.w(TAG, "isServiceRunning: ActivityManager 获取失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (serviceClass.getName().equals(info.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====================== Service 生命周期方法区(核心优化:优先启动前台服务) ======================
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate: 主服务创建");
|
||||
LogUtils.d(TAG, "===== onCreate: 主服务开始创建 =====");
|
||||
sMainServiceInstance = this;
|
||||
mIsServiceRunning = false;
|
||||
|
||||
// 初始化配置与核心组件
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
mMyServiceConnection = new MyServiceConnection();
|
||||
mMainServiceHandler = new MainServiceHandler(this);
|
||||
|
||||
// 启动前台服务:类型改为 dataSync,与 Manifest 保持一致,添加异常捕获
|
||||
Notification foregroundNotification = createForegroundNotification();
|
||||
// ========== 核心优化 1:优先启动前台服务,放在 onCreate 最前端,避免业务逻辑阻塞导致超时 ==========
|
||||
try {
|
||||
Notification foregroundNotification = createForegroundNotification();
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
|
||||
LogUtils.d(TAG, "onCreate: 主服务已启动为前台服务(dataSync 类型)");
|
||||
LogUtils.d(TAG, "onCreate: 主服务已启动为前台服务(dataSync 类型,API30+)");
|
||||
} else {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification);
|
||||
LogUtils.d(TAG, "onCreate: 主服务已启动为前台服务(低版本兼容)");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "onCreate: 启动前台服务失败,类型不匹配", e);
|
||||
stopSelf();
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "onCreate: 启动前台服务异常", e);
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== 初始化核心组件 ==========
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
mMyServiceConnection = new MyServiceConnection();
|
||||
mMainServiceHandler = new MainServiceHandler(this);
|
||||
mDelayHandler = new Handler(Looper.getMainLooper()); // 初始化延迟 Handler
|
||||
|
||||
// 初始化铃声音量检查定时器
|
||||
initVolumeCheckTimer();
|
||||
|
||||
// 启动主服务核心逻辑
|
||||
mainService();
|
||||
LogUtils.d(TAG, "===== onCreate: 主服务创建完成 =====");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -285,14 +397,22 @@ public class MainService extends Service {
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "onStartCommand: 服务被启动 | startId=" + startId);
|
||||
mainService();
|
||||
// 主窗口销毁后,不再执行核心逻辑
|
||||
if (!mIsMainWindowDestroyed) {
|
||||
mainService();
|
||||
}
|
||||
return (mMainServiceBean != null && mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy: 主服务销毁");
|
||||
LogUtils.d(TAG, "===== onDestroy: 主服务开始销毁 =====");
|
||||
|
||||
// 释放延迟 Handler 资源
|
||||
if (mDelayHandler != null) {
|
||||
mDelayHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
// 释放定时器资源
|
||||
cancelVolumeCheckTimer();
|
||||
@@ -318,19 +438,19 @@ public class MainService extends Service {
|
||||
LogUtils.d(TAG, "onDestroy: 广播接收器已注销");
|
||||
}
|
||||
|
||||
// 停止主线程
|
||||
// 停止子服务
|
||||
if (mMainServiceThread != null) {
|
||||
mMainServiceThread.setIsExit(true);
|
||||
mMainServiceThread = null;
|
||||
}
|
||||
|
||||
// 停止守护服务
|
||||
stopService(new Intent(this, AssistantService.class));
|
||||
stopService(new Intent(this, CallListenerService.class));
|
||||
}
|
||||
|
||||
// 清空静态实例
|
||||
// 清空静态实例,避免内存泄漏
|
||||
sMainServiceInstance = null;
|
||||
sTomCatInstance = null;
|
||||
LogUtils.d(TAG, "===== onDestroy: 主服务销毁完成 =====");
|
||||
}
|
||||
|
||||
// ====================== 核心业务逻辑方法区 ======================
|
||||
@@ -347,31 +467,27 @@ public class MainService extends Service {
|
||||
mIsServiceRunning = true;
|
||||
LogUtils.i(TAG, "mainService: 主服务开始运行");
|
||||
|
||||
// 绑定守护服务保活
|
||||
// 1. 绑定守护服务(核心服务,立即启动)
|
||||
wakeupAndBindAssistant();
|
||||
|
||||
// 初始化 TomCat 与号码数据
|
||||
// 2. 初始化业务组件
|
||||
initTomCat();
|
||||
|
||||
// 注册广播接收器
|
||||
initMainReceiver();
|
||||
|
||||
// 加载黑白名单规则
|
||||
Rules.getInstance(this).loadRules();
|
||||
LogUtils.d(TAG, "mainService: 黑白名单规则已加载");
|
||||
|
||||
// 启动通话监听服务
|
||||
// 3. 延迟启动通话监听服务(非核心服务,降低主线程负载)
|
||||
startPhoneCallListener();
|
||||
|
||||
// 启动主业务线程
|
||||
// 4. 启动主业务线程
|
||||
mMainServiceThread = MainServiceThread.getInstance(this, mMainServiceHandler);
|
||||
mMainServiceThread.start();
|
||||
LogUtils.i(TAG, "mainService: 主业务线程已启动");
|
||||
}
|
||||
|
||||
private void wakeupAndBindAssistant() {
|
||||
if (mMyServiceConnection == null) {
|
||||
LogUtils.e(TAG, "wakeupAndBindAssistant: MyServiceConnection 未初始化,绑定失败");
|
||||
if (mMyServiceConnection == null || mIsMainWindowDestroyed) {
|
||||
LogUtils.e(TAG, "wakeupAndBindAssistant: 初始化失败/主窗口已销毁,绑定失败");
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(this, AssistantService.class);
|
||||
@@ -385,16 +501,6 @@ public class MainService extends Service {
|
||||
LogUtils.d(TAG, "wakeupAndBindAssistant: 已启动并绑定守护服务");
|
||||
}
|
||||
|
||||
private void startPhoneCallListener() {
|
||||
Intent callListenerIntent = new Intent(this, CallListenerService.class);
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !AppForegroundUtils.isAppForeground(this)) {
|
||||
startForegroundService(callListenerIntent);
|
||||
} else {
|
||||
startService(callListenerIntent);
|
||||
}
|
||||
LogUtils.d(TAG, "startPhoneCallListener: 通话监听服务已启动");
|
||||
}
|
||||
|
||||
// ====================== 铃声音量监控相关方法区 ======================
|
||||
private void initVolumeCheckTimer() {
|
||||
cancelVolumeCheckTimer();
|
||||
@@ -423,7 +529,7 @@ public class MainService extends Service {
|
||||
LogUtils.d(TAG, "checkAndRestoreRingerVolume: 铃音配置未存在,已初始化默认配置");
|
||||
}
|
||||
|
||||
// 检查并恢复音量
|
||||
// 检查并恢复音量,添加权限异常捕获
|
||||
try {
|
||||
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
|
||||
int configVolume = ringTongBean.getStreamVolume();
|
||||
|
||||
@@ -16,41 +16,55 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/12 19:00
|
||||
* @Describe 通话筛选服务(完全匹配 API 30 父类方法原型,Java 7 兼容)
|
||||
* 基于 android-30.jar 中 CallScreeningService 的 onScreenCall 方法编写
|
||||
* 修复:1. 前台服务启动超时异常 2. 移除 Build.VERSION_CODES.S 依赖
|
||||
* @Describe 通话筛选服务(完全匹配 API 30 父类方法原型,严格 Java 7 兼容)
|
||||
* 修复:1. 前台服务启动超时异常 2. 移除所有 Build.VERSION_CODES 高版本依赖 3. 强化空指针防护
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
@RequiresApi(api = 29) // API 29 = Android 10 = Build.VERSION_CODES.Q,直接写数值避免依赖
|
||||
public class MyCallScreeningService extends CallScreeningService {
|
||||
// ====================== 常量定义区(全硬编码,杜绝高版本 API 依赖) ======================
|
||||
public static final String TAG = "MyCallScreeningService";
|
||||
private Context mContext;
|
||||
|
||||
// ====================== 常量定义(硬编码 API 版本,避免高版本依赖) ======================
|
||||
// 前台服务配置
|
||||
private static final int FOREGROUND_NOTIFICATION_ID = 1003;
|
||||
private static final String FOREGROUND_CHANNEL_ID = "call_screening_service_channel";
|
||||
private static final int ANDROID_8_API = 26; // 通知渠道最低版本
|
||||
private static final int ANDROID_12_API = 31; // 替代 Build.VERSION_CODES.S
|
||||
private static final int STOP_FOREGROUND_REMOVE = 1; // 硬编码常量,避免 API 30 未定义
|
||||
// API 版本常量(直接写数值,替代 Build.VERSION_CODES)
|
||||
private static final int ANDROID_8_API = 26; // Android 8.0,通知渠道最低版本
|
||||
private static final int ANDROID_12_API = 31; // Android 12,stopForeground 带参数版本
|
||||
// stopForeground 参数常量(硬编码 1,替代 STOP_FOREGROUND_REMOVE)
|
||||
private static final int STOP_FOREGROUND_REMOVE = 1;
|
||||
// 通话方向常量(直接写数值,替代 Call.Details 中的常量,适配 API 30)
|
||||
private static final int CALL_DIRECTION_INCOMING = 1;
|
||||
private static final int CALL_DIRECTION_OUTGOING = 2;
|
||||
|
||||
// 成员变量
|
||||
private Context mContext;
|
||||
|
||||
// ====================== Service 生命周期方法 ======================
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = this;
|
||||
LogUtils.d(TAG, "通话筛选服务已启动");
|
||||
LogUtils.d(TAG, "onCreate: 通话筛选服务启动");
|
||||
|
||||
// 核心修复:启动前台服务,绑定通知,避免 5 秒超时异常
|
||||
Notification foregroundNotification = createForegroundNotification();
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification);
|
||||
// ========== 核心优化:优先启动前台服务,放在 onCreate 最前端,避免超时 ==========
|
||||
try {
|
||||
Notification foregroundNotification = createForegroundNotification();
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification);
|
||||
LogUtils.d(TAG, "onCreate: 前台服务启动成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "onCreate: 前台服务启动失败", e);
|
||||
stopSelf(); // 启动失败直接停止服务,避免系统抛出异常
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 100% 匹配 API 30 父类的抽象方法原型
|
||||
* 方法签名:public abstract void onScreenCall(@NonNull Call.Details details)
|
||||
* 100% 匹配 API 30 CallScreeningService 抽象方法原型
|
||||
* 完全移除高版本依赖,使用硬编码常量
|
||||
*/
|
||||
@Override
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
@RequiresApi(api = 29) // 与类注解一致,明确最低 API 要求
|
||||
public void onScreenCall(@NonNull android.telecom.Call.Details details) {
|
||||
// 1. 获取通话号码(Java 7 空指针多层防护,避免崩溃)
|
||||
// 1. 获取通话号码:多层空指针防护,Java 7 规范写法
|
||||
String phoneNumber = "未知号码";
|
||||
Uri handle = details.getHandle();
|
||||
if (handle != null) {
|
||||
@@ -60,14 +74,14 @@ public class MyCallScreeningService extends CallScreeningService {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 判断通话方向(来电/外拨)
|
||||
// 2. 判断通话方向:用硬编码常量替代 API 高版本字段
|
||||
String callTypeStr;
|
||||
int callType;
|
||||
int direction = details.getCallDirection();
|
||||
if (direction == android.telecom.Call.Details.DIRECTION_INCOMING) {
|
||||
if (direction == CALL_DIRECTION_INCOMING) {
|
||||
callTypeStr = "来电";
|
||||
callType = TelephonyManager.CALL_STATE_RINGING;
|
||||
} else if (direction == android.telecom.Call.Details.DIRECTION_OUTGOING) {
|
||||
} else if (direction == CALL_DIRECTION_OUTGOING) {
|
||||
callTypeStr = "外拨";
|
||||
callType = TelephonyManager.CALL_STATE_OFFHOOK;
|
||||
} else {
|
||||
@@ -75,24 +89,24 @@ public class MyCallScreeningService extends CallScreeningService {
|
||||
callType = TelephonyManager.CALL_STATE_IDLE;
|
||||
}
|
||||
|
||||
// Java 7 字符串拼接,规避 String.format 兼容性问题
|
||||
// Java 7 字符串拼接,避免 String.format 兼容性问题
|
||||
LogUtils.d(TAG, "检测到" + callTypeStr + ":" + phoneNumber);
|
||||
|
||||
// 3. 自定义拦截逻辑示例:拦截 10086 号码
|
||||
// 3. 自定义拦截逻辑:示例拦截 10086
|
||||
boolean shouldBlock = "10086".equals(phoneNumber);
|
||||
|
||||
// 4. 构建通话筛选响应
|
||||
// 4. 构建筛选响应:Java 7 分步调用,不使用链式写法
|
||||
CallResponse.Builder responseBuilder = new CallResponse.Builder();
|
||||
responseBuilder.setDisallowCall(shouldBlock); // 禁止通话
|
||||
responseBuilder.setRejectCall(shouldBlock); // 拒绝通话
|
||||
responseBuilder.setSkipCallLog(!shouldBlock); // 拦截时不写入通话记录
|
||||
responseBuilder.setSkipNotification(!shouldBlock);// 拦截时不显示通知
|
||||
responseBuilder.setDisallowCall(shouldBlock);
|
||||
responseBuilder.setRejectCall(shouldBlock);
|
||||
responseBuilder.setSkipCallLog(!shouldBlock);
|
||||
responseBuilder.setSkipNotification(!shouldBlock);
|
||||
CallResponse response = responseBuilder.build();
|
||||
|
||||
// 5. 提交筛选结果(API 30 父类提供的 respondToCall 方法)
|
||||
// 5. 提交筛选结果(API 30 父类方法,直接调用)
|
||||
respondToCall(details, response);
|
||||
|
||||
// 6. 处理正常通话的业务逻辑
|
||||
// 6. 处理业务逻辑
|
||||
if (!shouldBlock) {
|
||||
handleNormalCall(phoneNumber, callType);
|
||||
} else {
|
||||
@@ -100,39 +114,73 @@ public class MyCallScreeningService extends CallScreeningService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy: 通话筛选服务销毁");
|
||||
// 适配 Android 12+ stopForeground 参数,硬编码常量避免依赖
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_12_API) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 业务逻辑方法 ======================
|
||||
/**
|
||||
* 处理正常通话的扩展业务逻辑
|
||||
* 可在此添加广播、数据库存储、界面通知等操作
|
||||
* 处理正常通话的扩展逻辑,Java 7 规范写法,添加空指针防护
|
||||
*/
|
||||
private void handleNormalCall(String phoneNumber, int callType) {
|
||||
// 示例:记录通话号码到本地(需添加存储权限)
|
||||
// SharedPreferences sp = mContext.getSharedPreferences("call_log", Context.MODE_PRIVATE);
|
||||
// sp.edit().putString("last_call", phoneNumber).apply();
|
||||
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
|
||||
LogUtils.w(TAG, "handleNormalCall: 号码为空,跳过处理");
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "处理正常通话:" + phoneNumber + " | 状态:" + getCallStateStr(callType));
|
||||
// 可在此添加通话记录、广播通知等逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建前台服务通知(Java 7 兼容,适配 Android 8.0+ 通知渠道)
|
||||
* @return 前台服务所需的 Notification 实例
|
||||
* 辅助方法:将通话状态转为字符串,避免重复代码
|
||||
*/
|
||||
private String getCallStateStr(int callType) {
|
||||
switch (callType) {
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
return "响铃中";
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
return "通话中";
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
return "空闲";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 前台服务通知构建方法 ======================
|
||||
/**
|
||||
* 创建前台服务通知:严格 Java 7 分步构建,强化异常防护
|
||||
* @return 可用的 Notification 实例
|
||||
*/
|
||||
private Notification createForegroundNotification() {
|
||||
// 1. 适配 Android 8.0+ 必须的通知渠道
|
||||
// 1. 创建 Android 8.0+ 通知渠道,添加空指针防护
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
FOREGROUND_CHANNEL_ID,
|
||||
"来电筛查服务",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
FOREGROUND_CHANNEL_ID,
|
||||
"来电筛查服务",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("用于后台处理来电筛查逻辑,防止服务被系统回收");
|
||||
channel.setDescription("后台处理来电筛查,防止服务被系统回收");
|
||||
// 关闭通知音效和震动,避免打扰用户
|
||||
channel.setSound(null, null);
|
||||
channel.enableVibration(false);
|
||||
|
||||
// 空指针防护:获取 NotificationManager 服务
|
||||
NotificationManager manager = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
LogUtils.d(TAG, "前台服务通知渠道创建成功");
|
||||
LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "createForegroundNotification: NotificationManager 获取失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 构建 Notification 对象(Java 7 分步设置,避免链式调用简化写法)
|
||||
// 2. 构建 Notification:Java 7 分步设置,不使用链式调用
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
builder = new Notification.Builder(mContext, FOREGROUND_CHANNEL_ID);
|
||||
@@ -140,22 +188,14 @@ public class MyCallScreeningService extends CallScreeningService {
|
||||
builder = new Notification.Builder(mContext);
|
||||
}
|
||||
|
||||
builder.setSmallIcon(R.drawable.ic_launcher); // 替换为应用实际图标资源
|
||||
// 关键:确保小图标资源存在,替换为项目实际图标
|
||||
builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
builder.setContentTitle("来电筛查服务运行中");
|
||||
builder.setContentText("正在监控来电状态");
|
||||
builder.setPriority(Notification.PRIORITY_LOW);
|
||||
builder.setOngoing(true); // 通知不可手动取消,符合前台服务特性
|
||||
builder.setOngoing(true); // 前台服务通知不可手动取消
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "通话筛选服务已销毁");
|
||||
// 修复:用硬编码 API 版本和常量替代高版本依赖
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_12_API) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user