服务启动问题Bugfix

This commit is contained in:
2025-12-12 21:08:00 +08:00
parent c1d2158578
commit 416079c356
5 changed files with 518 additions and 362 deletions

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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 12stopForeground 带参数版本
// 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. 构建 NotificationJava 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);
}
}
}