改进应用崩溃处理作为类库使用的鲁棒性

This commit is contained in:
2025-11-30 11:42:18 +08:00
parent aa24bc5e11
commit 65161b1a80
4 changed files with 58 additions and 50 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Nov 30 03:45:02 HKT 2025
#Sun Nov 30 03:41:07 GMT 2025
stageCount=5
libraryProject=libappbase
baseVersion=15.11
publishVersion=15.11.4
buildCount=0
buildCount=3
baseBetaVersion=15.11.5

View File

@@ -22,6 +22,7 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate(); // 调用父类初始化逻辑(如基础库配置、全局上下文设置)
//setIsDebugging(false);
setIsDebugging(BuildConfig.DEBUG);
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Nov 30 03:42:05 HKT 2025
#Sun Nov 30 03:41:07 GMT 2025
stageCount=5
libraryProject=libappbase
baseVersion=15.11
publishVersion=15.11.4
buildCount=0
buildCount=3
baseBetaVersion=15.11.5

View File

@@ -7,7 +7,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import cc.winboll.studio.libappbase.activities.CrashCopyReceiverActivity; // 导入新增的复制活动
import cc.winboll.studio.libappbase.activities.CrashCopyReceiverActivity;
import cc.winboll.studio.libappbase.CrashHandler;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
@@ -15,7 +15,7 @@ import cc.winboll.studio.libappbase.R;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/29 21:12
* @Describe 应用崩溃处理通知实用工具集
* @Describe 应用崩溃处理通知实用工具集(类库专用,支持宿主应用唤醒活动)
* 核心功能应用崩溃时捕获错误日志发送通知到系统通知栏3行内容省略+复制按钮点击复制按钮唤醒CrashCopyReceiverActivity完成日志拷贝
*/
public class CrashHandleNotifyUtils {
@@ -38,15 +38,15 @@ public class CrashHandleNotifyUtils {
private static final int REQUEST_CODE_COPY = 0x002;
/**
* 处理未捕获异常(核心方法)
* 处理未捕获异常(核心方法,类库场景专用:强制使用宿主应用包名构建意图
* 1. 提取应用名称和崩溃日志;
* 2. 创建并发送系统通知3行内容省略+复制按钮);
* 3. 兼容 Android 8.0+ 通知渠道机制,适配低版本系统。
* @param app 应用全局 Application 实例(用于获取上下文、应用信息
* @param app 应用全局 Application 实例(宿主应用的 Application确保包名正确
* @param intent 存储崩溃信息的意图extra 中携带崩溃日志)
*/
public static void handleUncaughtException(Application app, Intent intent) {
// 1. 提取应用名称(优化:从 Application 中获取真实应用名,替代原类名
// 1. 提取应用名称(优化:从宿主 Application 中获取真实应用名)
String appName = getAppName(app);
// 2. 提取崩溃日志(从 Intent Extra 中获取,对应 CrashHandler 存储的崩溃信息)
String errorLog = intent.getStringExtra(CrashHandler.EXTRA_CRASH_INFO);
@@ -57,19 +57,18 @@ public class CrashHandleNotifyUtils {
return;
}
// 3. 发送崩溃通知到通知栏(3行省略+复制按钮点击唤醒CrashCopyReceiverActivity
sendCrashNotification(app, appName, errorLog);
// 3. 发送崩溃通知到通知栏(类库场景:强制用宿主包名构建意图
sendCrashNotification(app, appName, errorLog, app.getPackageName());
}
/**
* 获取应用真实名称(从 AndroidManifest 中读取 android:label
* 替代原 app.getClass().toString()(原方式获取的是类名,用户不可读
* @param context 上下文Application 实例)
* 获取应用真实名称(从宿主 AndroidManifest 中读取 android:label
* @param context 上下文(宿主 Application 实例,确保获取正确的应用名称
* @return 应用名称(读取失败返回 "未知应用"
*/
private static String getAppName(Context context) {
try {
// 从包管理器中获取应用信息(包含应用名称
// 从宿主包管理器中获取应用信息(类库场景必须用宿主上下文
return context.getPackageManager().getApplicationLabel(
context.getApplicationInfo()
).toString();
@@ -80,12 +79,13 @@ public class CrashHandleNotifyUtils {
}
/**
* 发送崩溃通知到系统通知栏(核心修改:替换为活动唤醒方案
* @param context 上下文Application 实例,确保后台也能发送
* 发送崩溃通知到系统通知栏(类库专用:新增宿主包名参数,确保意图跳转正确
* @param context 上下文(宿主 Application 实例)
* @param title 通知标题(应用名称)
* @param content 通知内容(崩溃日志)
* @param hostPackageName 宿主应用包名(关键:用于构建跨类库的活动意图)
*/
private static void sendCrashNotification(Context context, String title, String content) {
private static void sendCrashNotification(Context context, String title, String content, String hostPackageName) {
// 1. 获取通知管理器(系统服务,用于发送/管理通知)
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
@@ -98,21 +98,20 @@ public class CrashHandleNotifyUtils {
createCrashNotifyChannel(notificationManager);
}
// 3. 构建通知意图(点击通知跳转主界面 + 点击复制按钮唤醒复制活动
PendingIntent launchPendingIntent = getLaunchPendingIntent(context); // 主界面跳转意图
PendingIntent copyPendingIntent = getCopyPendingIntent(context, content); // 唤醒复制活动的意图
// 3. 构建通知意图(类库场景:用宿主包名构建意图,确保跳转成功
PendingIntent launchPendingIntent = getLaunchPendingIntent(context, hostPackageName); // 主界面跳转意图
PendingIntent copyPendingIntent = getCopyPendingIntent(context, content, hostPackageName); // 唤醒复制活动的意图
// 4. 构建通知实例核心修复3行内容省略+复制按钮)
Notification notification = buildNotification(context, title, content, launchPendingIntent, copyPendingIntent);
// 5. 发送通知指定通知ID重复发送同ID会覆盖原通知
notificationManager.notify(CRASH_NOTIFY_ID, notification);
LogUtils.d(TAG, "崩溃通知发送成功:标题=" + title + "内容长度=" + content.length() + "字符");
LogUtils.d(TAG, "崩溃通知发送成功(类库场景):标题=" + title + "宿主包名=" + hostPackageName);
}
/**
* 创建崩溃通知渠道Android 8.0+ 必需)
* 通知渠道用于归类通知,用户可在系统设置中管理(开启/关闭/静音)
* @param notificationManager 通知管理器
*/
private static void createCrashNotifyChannel(NotificationManager notificationManager) {
@@ -133,21 +132,20 @@ public class CrashHandleNotifyUtils {
}
/**
* 构建通知点击跳转意图(跳转应用主界面)
* @param context 上下文
* 构建通知点击跳转意图(跳转宿主应用主界面,类库场景专用
* @param context 上下文(宿主 Application 实例)
* @param hostPackageName 宿主应用包名(关键:确保跳转宿主主界面)
* @return 主界面跳转 PendingIntent
*/
private static PendingIntent getLaunchPendingIntent(Context context) {
// 1. 获取应用主界面 Intent从包名启动默认 launcher Activity
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(
context.getPackageName()
);
private static PendingIntent getLaunchPendingIntent(Context context, String hostPackageName) {
// 1. 获取宿主应用主界面 Intent强制用宿主包名获取,避免类库包名干扰
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(hostPackageName);
if (launchIntent == null) {
// 异常处理:若主界面 Intent 为空,创建空意图(避免崩溃)
launchIntent = new Intent();
}
// 2. 构建 PendingIntent延迟执行的意图
// 2. 构建 PendingIntent延迟执行的意图,类库场景增加 FLAG_ONE_SHOT 确保单次有效
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
// 适配 Android 12+API 31+):添加 FLAG_IMMUTABLE 避免安全警告
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
@@ -163,30 +161,35 @@ public class CrashHandleNotifyUtils {
}
/**
* 构建复制按钮意图(核心修改:点击唤醒 CrashCopyReceiverActivity 完成日志拷贝
* 替代原广播意图,确保应用崩溃后仍能稳定触发复制
* @param context 上下文
* 构建复制按钮意图(类库场景专用:用宿主包名唤醒活动,确保跳转成功
* @param context 上下文(宿主 Application 实例)
* @param errorLog 崩溃日志(需要复制的内容)
* @param hostPackageName 宿主应用包名(关键:确保系统能找到类库中的活动)
* @return 唤醒复制活动的 PendingIntent
*/
private static PendingIntent getCopyPendingIntent(Context context, String errorLog) {
// 1. 构建唤醒 CrashCopyReceiverActivity 的显式意图(指定目标活动,避免意图匹配失败
private static PendingIntent getCopyPendingIntent(Context context, String errorLog, String hostPackageName) {
// 1. 构建唤醒 CrashCopyReceiverActivity 的显式意图(类库场景关键:指定宿主包名
Intent copyIntent = new Intent(context, CrashCopyReceiverActivity.class);
// 设置动作与Activity中匹配可选但建议保留便于扩展
// 强制设置意图的包名为宿主包名(解决类库与宿主包名不匹配问题
copyIntent.setPackage(hostPackageName);
// 设置动作与Activity中匹配确保意图精准匹配
copyIntent.setAction(CrashCopyReceiverActivity.ACTION_COPY_CRASH_LOG);
// 携带完整崩溃日志键与Activity中一致
copyIntent.putExtra(CrashCopyReceiverActivity.EXTRA_CRASH_LOG, errorLog);
// 设置标志:确保活动在应用崩溃后仍能被唤醒,且不重复创建
copyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// 类库场景增强标志:确保活动在宿主应用中能被唤醒,且不重复创建
copyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_NO_HISTORY); // 不保留活动历史,避免残留
// 2. 构建 PendingIntent活动类型,优先级高于广播
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
// 2. 构建 PendingIntent类库场景:使用 FLAG_ONE_SHOT 避免重复触发
int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT;
// 适配 Android 12+API 31+):添加 FLAG_IMMUTABLE 避免安全警告
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
flags |= FLAG_IMMUTABLE;
}
// 3. 返回活动类型的 PendingIntent替代原广播类型
// 3. 返回活动类型的 PendingIntent类库场景:用宿主上下文构建,确保权限通过
return PendingIntent.getActivity(
context,
REQUEST_CODE_COPY, // 唯一请求码,区分主界面意图
@@ -197,7 +200,7 @@ public class CrashHandleNotifyUtils {
/**
* 构建通知实例核心修复3行内容省略+复制按钮)
* @param context 上下文
* @param context 上下文(宿主 Application 实例)
* @param title 通知标题(应用名称)
* @param content 通知内容(崩溃日志)
* @param launchPendingIntent 通知点击跳转意图
@@ -218,11 +221,16 @@ public class CrashHandleNotifyUtils {
bigTextStyle.setBigContentTitle(title); // 下拉后的标题(与主标题一致)
builder.setStyle(bigTextStyle);
// 核心修改2添加复制按钮Android 4.1+ 支持通知按钮)
// 核心修改2添加复制按钮Android 4.1+ 支持通知按钮,类库场景用系统图标避免资源缺失
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// 复制按钮:自定义图标+文本+点击意图(绑定唤醒复制活动
// 类库场景:用系统默认复制图标(避免自定义图标在宿主应用中缺失
int copyIcon = R.drawable.ic_content_copy;
// 若类库有自定义图标,优先使用(需确保宿主应用能访问类库资源)
if (R.drawable.ic_content_copy != 0) {
copyIcon = R.drawable.ic_content_copy;
}
builder.addAction(
R.drawable.ic_content_copy, // 自定义复制图标需确保drawable目录下存在
copyIcon, // 兼容图标(系统图标兜底
"复制日志", // 按钮文本
copyPendingIntent // 按钮点击意图唤醒CrashCopyReceiverActivity
);
@@ -230,7 +238,7 @@ public class CrashHandleNotifyUtils {
// 配置通知核心参数移除setLines避免报错
builder
.setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,否则通知不显示
.setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(用宿主应用图标,避免类库图标缺失
.setContentTitle(title) // 通知主标题(应用名称)
.setContentText(getShortContent(content)) // 核心3行内缩略文本
.setContentIntent(launchPendingIntent) // 通知主体点击跳转主界面
@@ -249,7 +257,6 @@ public class CrashHandleNotifyUtils {
/**
* 辅助方法截取日志文本确保显示在3行内按字符数估算适配大多数设备
* 一行约20-30字符3行约80字符留冗余取80字符超出加省略号
* @param content 完整崩溃日志
* @return 3行内的缩略文本
*/
@@ -269,11 +276,11 @@ public class CrashHandleNotifyUtils {
/**
* 释放资源(删除原广播注销逻辑,仅保留空实现便于兼容旧代码调用)
* @param context 上下文Application 实例)
* @param context 上下文(宿主 Application 实例)
*/
public static void release(Context context) {
// 因已移除广播接收器,此处仅保留空实现,避免调用方报错
LogUtils.d(TAG, "CrashHandleNotifyUtils 资源释放完成(无广播接收器需注销)");
LogUtils.d(TAG, "CrashHandleNotifyUtils 资源释放完成(类库场景,无广播接收器需注销)");
}
}