diff --git a/contacts/build.properties b/contacts/build.properties index 75b1211..93c74da 100644 --- a/contacts/build.properties +++ b/contacts/build.properties @@ -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 diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java index 75b4ee3..d793a6f 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java @@ -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 fragmentList; private List 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(); tabTitleList = new ArrayList(); + // 添加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 fragmentList; private final List 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); } } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java b/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java index af7af24..f36a8e7 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java @@ -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&豆包大模型 - * @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; } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java b/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java index 5fd7e21..b372da9 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java @@ -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&豆包大模型 * @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(); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/services/MyCallScreeningService.java b/contacts/src/main/java/cc/winboll/studio/contacts/services/MyCallScreeningService.java index be64d12..4d390c2 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/services/MyCallScreeningService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/services/MyCallScreeningService.java @@ -16,41 +16,55 @@ import cc.winboll.studio.libappbase.LogUtils; /** * @Author ZhanGSKen&豆包大模型 * @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); - } - } } +