@@ -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 资源释放完成(类库场景, 无广播接收器需注销) " ) ;
}
}