改进应用崩溃处理作为类库使用的鲁棒性
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 资源释放完成(类库场景,无广播接收器需注销)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user