From fc34ed0d5ae0349714cad19c94001926f4b80ead Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 30 Nov 2025 16:12:56 +0800 Subject: [PATCH 1/3] 20251130_161248_108 --- appbase/build.properties | 4 +- libappbase/build.properties | 4 +- .../utils/CrashHandleNotifyUtils.java | 177 ++---------------- 3 files changed, 18 insertions(+), 167 deletions(-) diff --git a/appbase/build.properties b/appbase/build.properties index 96256333..426c33b3 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Nov 29 21:41:11 HKT 2025 +#Sun Nov 30 08:12:09 GMT 2025 stageCount=4 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.3 -buildCount=0 +buildCount=2 baseBetaVersion=15.11.4 diff --git a/libappbase/build.properties b/libappbase/build.properties index 96256333..426c33b3 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Nov 29 21:41:11 HKT 2025 +#Sun Nov 30 08:12:09 GMT 2025 stageCount=4 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.3 -buildCount=0 +buildCount=2 baseBetaVersion=15.11.4 diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java index ea52a5b5..ea98fab6 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java @@ -6,17 +6,15 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.os.Build; import cc.winboll.studio.libappbase.CrashHandler; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.R; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/11/29 21:12 * @Describe 应用崩溃处理通知实用工具集 - * 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏(3行内容省略+复制按钮),点击复制按钮可将完整崩溃日志复制到剪贴板 + * 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏(3行内容省略),点击通知跳转应用主界面 */ public class CrashHandleNotifyUtils { @@ -34,20 +32,11 @@ public class CrashHandleNotifyUtils { private static final int FLAG_IMMUTABLE = 0x00000040; /** 通知内容最大行数(控制在3行,超出部分省略) */ private static final int NOTIFICATION_MAX_LINES = 3; - /** 复制按钮 Action(用于区分通知按钮点击事件) */ - private static final String ACTION_COPY_CRASH_LOG = "cc.winboll.studio.action.COPY_CRASH_LOG"; - /** 复制按钮请求码(区分多个 PendingIntent) */ - private static final int REQUEST_CODE_COPY = 0x002; - /** 复制成功提示文本 */ - private static final String COPY_SUCCESS_TIP = "崩溃日志已复制到剪贴板"; - - // 静态广播接收器(避免重复注册,确保崩溃后仍能接收点击事件) - private static CopyCrashLogReceiver sCopyReceiver; /** * 处理未捕获异常(核心方法) * 1. 提取应用名称和崩溃日志; - * 2. 创建并发送系统通知(3行内容省略+复制按钮); + * 2. 创建并发送系统通知(3行内容省略,无复制按钮); * 3. 兼容 Android 8.0+ 通知渠道机制,适配低版本系统。 * @param app 应用全局 Application 实例(用于获取上下文、应用信息) * @param intent 存储崩溃信息的意图(extra 中携带崩溃日志) @@ -64,10 +53,7 @@ public class CrashHandleNotifyUtils { return; } - // 3. 注册静态广播接收器(仅注册一次,确保崩溃后能接收点击事件) - registerCopyReceiver(app); - - // 4. 发送崩溃通知到通知栏(3行省略+复制按钮) + // 3. 发送崩溃通知到通知栏(3行省略,无复制按钮) sendCrashNotification(app, appName, errorLog); } @@ -90,7 +76,7 @@ public class CrashHandleNotifyUtils { } /** - * 发送崩溃通知到系统通知栏(核心修改:3行内容+复制按钮) + * 发送崩溃通知到系统通知栏(移除复制按钮,仅显示3行内容) * @param context 上下文(Application 实例,确保后台也能发送) * @param title 通知标题(应用名称) * @param content 通知内容(崩溃日志) @@ -108,12 +94,11 @@ public class CrashHandleNotifyUtils { createCrashNotifyChannel(notificationManager); } - // 3. 构建通知意图(点击通知跳转主界面 + 点击复制按钮复制日志) + // 3. 构建通知意图(仅保留:点击通知跳转主界面) PendingIntent launchPendingIntent = getLaunchPendingIntent(context); // 主界面跳转意图 - PendingIntent copyPendingIntent = getCopyPendingIntent(context, content); // 复制日志意图 - // 4. 构建通知实例(核心修复:3行内容省略+复制按钮,修复setLines报错) - Notification notification = buildNotification(context, title, content, launchPendingIntent, copyPendingIntent); + // 4. 构建通知实例(移除复制按钮,仅保留3行内容省略) + Notification notification = buildNotification(context, title, content, launchPendingIntent); // 5. 发送通知(指定通知ID,重复发送同ID会覆盖原通知) notificationManager.notify(CRASH_NOTIFY_ID, notification); @@ -135,7 +120,7 @@ public class CrashHandleNotifyUtils { NotificationManager.IMPORTANCE_DEFAULT // 重要性:默认(不会弹窗,有声音提示) ); // 可选:设置渠道描述(用户在设置中可见) - channel.setDescription("用于显示应用崩溃信息,支持复制日志"); + channel.setDescription("用于显示应用崩溃信息"); // 注册通知渠道到系统 notificationManager.createNotificationChannel(channel); LogUtils.d(TAG, "崩溃通知渠道创建成功:" + CRASH_NOTIFY_CHANNEL_ID); @@ -173,153 +158,28 @@ public class CrashHandleNotifyUtils { } /** - * 构建复制按钮意图(点击复制崩溃日志到剪贴板) - * @param context 上下文 - * @param errorLog 崩溃日志(需要复制的内容) - * @return 复制日志 PendingIntent - */ - private static PendingIntent getCopyPendingIntent(Context context, String errorLog) { - // 1. 构建复制日志的隐式意图(指定 Action,用于 BroadcastReceiver 接收) - Intent copyIntent = new Intent(ACTION_COPY_CRASH_LOG); - copyIntent.putExtra("EXTRA_CRASH_LOG", errorLog); // 携带崩溃日志 - copyIntent.setPackage(context.getPackageName()); // 限制仅当前应用接收,避免安全问题 - - // 2. 构建 PendingIntent(使用广播类型,崩溃后仍能触发) - int flags = PendingIntent.FLAG_UPDATE_CURRENT; - if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) { - flags |= FLAG_IMMUTABLE; - } - - return PendingIntent.getBroadcast( - context, - REQUEST_CODE_COPY, // 唯一请求码,区分主界面意图 - copyIntent, - flags - ); - } - - /** - * 注册静态广播接收器(仅注册一次,确保崩溃后能接收点击事件) - * 解决动态广播崩溃后被销毁的问题 - * @param context 上下文(Application 实例) - */ - private static void registerCopyReceiver(Context context) { - // 避免重复注册(静态接收器仅注册一次) - if (sCopyReceiver != null) { - return; - } - - // 构建广播过滤器(仅接收复制日志的 Action) - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_COPY_CRASH_LOG); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - - // 初始化静态广播接收器 - sCopyReceiver = new CopyCrashLogReceiver(); - // 注册广播(使用 Application 上下文,确保生命周期与应用一致) - context.registerReceiver(sCopyReceiver, filter); - LogUtils.d(TAG, "复制日志广播接收器注册成功"); - } - - /** - * 静态广播接收器(处理复制按钮点击事件) - * 静态内部类避免内存泄漏,且崩溃后仍能接收系统发送的广播 - */ - private static class CopyCrashLogReceiver extends android.content.BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - // 验证 Action,确保是复制日志的点击事件 - if (ACTION_COPY_CRASH_LOG.equals(intent.getAction())) { - // 从意图中获取完整崩溃日志 - String crashLog = intent.getStringExtra("EXTRA_CRASH_LOG"); - if (crashLog != null && !crashLog.isEmpty()) { - // 复制日志到剪贴板 - copyTextToClipboard(context, "崩溃日志", crashLog); - // 复制成功后显示提示(可选,提升用户体验) - showCopySuccessTip(context); - LogUtils.d(TAG, "崩溃日志复制成功,长度:" + crashLog.length() + "字符"); - } else { - LogUtils.e(TAG, "复制崩溃日志失败:日志为空"); - } - } - } - } - - /** - * 复制文本到系统剪贴板(修复适配逻辑,确保全版本可用) - * @param context 上下文 - * @param label 剪贴板文本标签(用户不可见,用于区分剪贴板内容) - * @param text 需要复制的文本(崩溃日志) - */ - private static void copyTextToClipboard(Context context, String label, String text) { - try { - // 适配 Android 11+(API 30+)剪贴板 API,兼容低版本 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clipData = android.content.ClipData.newPlainText(label, text); - clipboard.setPrimaryClip(clipData); - } else { - // 低版本剪贴板 API(Android 10 及以下) - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(text); - } - } catch (Exception e) { - LogUtils.e(TAG, "复制文本到剪贴板失败", e); - } - } - - /** - * 显示复制成功提示(Toast,提升用户体验) - * @param context 上下文 - */ - private static void showCopySuccessTip(Context context) { - try { - // 若项目中 ToastUtils 支持后台显示,用 ToastUtils;否则用系统 Toast - if (cc.winboll.studio.libappbase.ToastUtils.isInited()) { - cc.winboll.studio.libappbase.ToastUtils.show(COPY_SUCCESS_TIP); - } else { - // 系统 Toast 适配(确保后台能显示) - android.widget.Toast.makeText(context, COPY_SUCCESS_TIP, android.widget.Toast.LENGTH_SHORT).show(); - } - } catch (Exception e) { - LogUtils.e(TAG, "显示复制成功提示失败", e); - } - } - - /** - * 构建通知实例(核心修复:3行内容省略+复制按钮,修复setLines报错) + * 构建通知实例(核心调整:移除复制按钮,仅保留3行内容省略) * @param context 上下文 * @param title 通知标题(应用名称) * @param content 通知内容(崩溃日志) * @param launchPendingIntent 通知点击跳转意图 - * @param copyPendingIntent 复制按钮点击意图 * @return 构建完成的 Notification 对象 */ - private static Notification buildNotification(Context context, String title, String content, PendingIntent launchPendingIntent, PendingIntent copyPendingIntent) { + private static Notification buildNotification(Context context, String title, String content, PendingIntent launchPendingIntent) { // 兼容 Android 8.0+:指定通知渠道ID Notification.Builder builder = new Notification.Builder(context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder.setChannelId(CRASH_NOTIFY_CHANNEL_ID); } - // 核心修复1:用BigTextStyle控制“默认3行省略,下拉显示完整” + // 核心:用BigTextStyle控制“默认3行省略,下拉显示完整” Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(); bigTextStyle.setSummaryText("日志已省略,下拉查看完整内容"); // 底部省略提示 bigTextStyle.bigText(content); // 完整日志(下拉时显示) bigTextStyle.setBigContentTitle(title); // 下拉后的标题(与主标题一致) builder.setStyle(bigTextStyle); - // 核心修改2:添加复制按钮(Android 4.1+ 支持通知按钮) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // 复制按钮:自定义图标+文本+点击意图(确保图标存在) - builder.addAction( - R.drawable.ic_content_copy, // 自定义复制图标(需确保drawable目录下存在,否则替换为系统图标) - "复制日志", // 按钮文本 - copyPendingIntent // 按钮点击意图(绑定复制广播) - ); - } - - // 配置通知核心参数(移除setLines,避免报错) + // 配置通知核心参数(移除复制按钮相关代码) builder .setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,否则通知不显示) .setContentTitle(title) // 通知主标题(应用名称) @@ -360,20 +220,11 @@ public class CrashHandleNotifyUtils { } /** - * 释放资源(可选,在 Application 销毁时调用,避免内存泄漏) + * 释放资源(移除广播注销逻辑,保留空实现避免调用报错) * @param context 上下文(Application 实例) */ public static void release(Context context) { - // 注销静态广播接收器,避免内存泄漏 - if (sCopyReceiver != null && context != null) { - try { - context.unregisterReceiver(sCopyReceiver); - sCopyReceiver = null; - LogUtils.d(TAG, "复制日志广播接收器已注销"); - } catch (Exception e) { - LogUtils.e(TAG, "注销广播接收器失败", e); - } - } + LogUtils.d(TAG, "CrashHandleNotifyUtils 资源释放完成"); } } From 8aafcabba91426212e35e24c8e0cf72bef36c056 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 30 Nov 2025 16:21:03 +0800 Subject: [PATCH 2/3] 20251130_162041_979 --- appbase/build.properties | 4 +- libappbase/build.properties | 4 +- .../utils/CrashHandleNotifyUtils.java | 53 ++++++++++--------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/appbase/build.properties b/appbase/build.properties index 426c33b3..43b7b4d9 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Nov 30 08:12:09 GMT 2025 +#Sun Nov 30 08:19:07 GMT 2025 stageCount=4 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.3 -buildCount=2 +buildCount=3 baseBetaVersion=15.11.4 diff --git a/libappbase/build.properties b/libappbase/build.properties index 426c33b3..43b7b4d9 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Nov 30 08:12:09 GMT 2025 +#Sun Nov 30 08:19:07 GMT 2025 stageCount=4 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.3 -buildCount=2 +buildCount=3 baseBetaVersion=15.11.4 diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java index ea98fab6..8faade8f 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java @@ -8,13 +8,14 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import cc.winboll.studio.libappbase.CrashHandler; +import cc.winboll.studio.libappbase.GlobalCrashActivity; import cc.winboll.studio.libappbase.LogUtils; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/11/29 21:12 * @Describe 应用崩溃处理通知实用工具集 - * 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏(3行内容省略),点击通知跳转应用主界面 + * 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏(3行内容省略),点击通知跳转 GlobalCrashActivity 并传递日志 */ public class CrashHandleNotifyUtils { @@ -53,7 +54,7 @@ public class CrashHandleNotifyUtils { return; } - // 3. 发送崩溃通知到通知栏(3行省略,无复制按钮) + // 3. 发送崩溃通知到通知栏(3行省略,点击跳转 GlobalCrashActivity) sendCrashNotification(app, appName, errorLog); } @@ -76,10 +77,10 @@ public class CrashHandleNotifyUtils { } /** - * 发送崩溃通知到系统通知栏(移除复制按钮,仅显示3行内容) + * 发送崩溃通知到系统通知栏(移除复制按钮,点击跳转 GlobalCrashActivity) * @param context 上下文(Application 实例,确保后台也能发送) * @param title 通知标题(应用名称) - * @param content 通知内容(崩溃日志) + * @param content 通知内容(崩溃日志,需传递给 GlobalCrashActivity) */ private static void sendCrashNotification(Context context, String title, String content) { // 1. 获取通知管理器(系统服务,用于发送/管理通知) @@ -94,15 +95,15 @@ public class CrashHandleNotifyUtils { createCrashNotifyChannel(notificationManager); } - // 3. 构建通知意图(仅保留:点击通知跳转主界面) - PendingIntent launchPendingIntent = getLaunchPendingIntent(context); // 主界面跳转意图 + // 3. 构建通知意图(核心修改:点击跳转 GlobalCrashActivity,传递崩溃日志) + PendingIntent jumpIntent = getGlobalCrashPendingIntent(context, content); // 跳转崩溃详情页意图 // 4. 构建通知实例(移除复制按钮,仅保留3行内容省略) - Notification notification = buildNotification(context, title, content, launchPendingIntent); + Notification notification = buildNotification(context, title, content, jumpIntent); // 5. 发送通知(指定通知ID,重复发送同ID会覆盖原通知) notificationManager.notify(CRASH_NOTIFY_ID, notification); - LogUtils.d(TAG, "崩溃通知发送成功:标题=" + title + ",内容长度=" + content.length() + "字符"); + LogUtils.d(TAG, "崩溃通知发送成功:标题=" + title + ",内容长度=" + content.length() + "字符(点击跳转崩溃详情页)"); } /** @@ -120,7 +121,7 @@ public class CrashHandleNotifyUtils { NotificationManager.IMPORTANCE_DEFAULT // 重要性:默认(不会弹窗,有声音提示) ); // 可选:设置渠道描述(用户在设置中可见) - channel.setDescription("用于显示应用崩溃信息"); + channel.setDescription("用于显示应用崩溃信息,点击查看详情"); // 注册通知渠道到系统 notificationManager.createNotificationChannel(channel); LogUtils.d(TAG, "崩溃通知渠道创建成功:" + CRASH_NOTIFY_CHANNEL_ID); @@ -128,19 +129,19 @@ public class CrashHandleNotifyUtils { } /** - * 构建通知点击跳转意图(跳转应用主界面) + * 核心修改:构建跳转 GlobalCrashActivity 的意图(传递崩溃日志) + * 与 GlobalCrashActivity 的日志接收键(EXTRA_CRASH_INFO)保持一致 * @param context 上下文 - * @return 主界面跳转 PendingIntent + * @param errorLog 崩溃日志(需传递给 GlobalCrashActivity) + * @return 跳转崩溃详情页的 PendingIntent */ - private static PendingIntent getLaunchPendingIntent(Context context) { - // 1. 获取应用主界面 Intent(从包名启动默认 launcher Activity) - Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage( - context.getPackageName() - ); - if (launchIntent == null) { - // 异常处理:若主界面 Intent 为空,创建空意图(避免崩溃) - launchIntent = new Intent(); - } + private static PendingIntent getGlobalCrashPendingIntent(Context context, String errorLog) { + // 1. 构建跳转 GlobalCrashActivity 的显式意图 + Intent crashIntent = new Intent(context, GlobalCrashActivity.class); + // 传递崩溃日志(键:EXTRA_CRASH_INFO,与 GlobalCrashActivity 一致) + crashIntent.putExtra(CrashHandler.EXTRA_CRASH_INFO, errorLog); + // 设置意图标志:确保 Activity 正常启动,避免重复创建 + crashIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // 2. 构建 PendingIntent(延迟执行的意图) int flags = PendingIntent.FLAG_UPDATE_CURRENT; @@ -151,8 +152,8 @@ public class CrashHandleNotifyUtils { return PendingIntent.getActivity( context, - 0, // 请求码(可忽略) - launchIntent, + CRASH_NOTIFY_ID, // 用通知ID作为请求码,确保唯一(避免意图复用) + crashIntent, flags ); } @@ -162,10 +163,10 @@ public class CrashHandleNotifyUtils { * @param context 上下文 * @param title 通知标题(应用名称) * @param content 通知内容(崩溃日志) - * @param launchPendingIntent 通知点击跳转意图 + * @param jumpIntent 通知点击跳转意图(跳转 GlobalCrashActivity) * @return 构建完成的 Notification 对象 */ - private static Notification buildNotification(Context context, String title, String content, PendingIntent launchPendingIntent) { + private static Notification buildNotification(Context context, String title, String content, PendingIntent jumpIntent) { // 兼容 Android 8.0+:指定通知渠道ID Notification.Builder builder = new Notification.Builder(context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -182,9 +183,9 @@ public class CrashHandleNotifyUtils { // 配置通知核心参数(移除复制按钮相关代码) builder .setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,否则通知不显示) - .setContentTitle(title) // 通知主标题(应用名称) + .setContentTitle(title + " 崩溃") // 优化标题:明确标识“崩溃” .setContentText(getShortContent(content)) // 核心:3行内缩略文本 - .setContentIntent(launchPendingIntent) // 通知主体点击跳转主界面 + .setContentIntent(jumpIntent) // 通知主体点击跳转 GlobalCrashActivity .setAutoCancel(true) // 点击通知后自动取消 .setWhen(System.currentTimeMillis()) // 通知创建时间 .setPriority(Notification.PRIORITY_DEFAULT); // 通知优先级 From ad6d29c27e81053aa100fb970cfedd89b2fcf781 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 30 Nov 2025 16:29:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=B4=A9=E6=BA=83?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E7=9A=84=E5=90=84=E7=B1=BB=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E7=B1=BB=E5=BA=93=E9=80=82=E9=85=8D=E8=83=BD?= =?UTF-8?q?=E5=8A=9B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appbase/build.properties | 4 +- libappbase/build.properties | 4 +- .../studio/libappbase/CrashHandler.java | 2 +- .../utils/CrashHandleNotifyUtils.java | 265 ++++++++++-------- 4 files changed, 151 insertions(+), 124 deletions(-) diff --git a/appbase/build.properties b/appbase/build.properties index 43b7b4d9..aa9043ef 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Nov 30 08:19:07 GMT 2025 +#Sun Nov 30 08:26:11 GMT 2025 stageCount=4 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.3 -buildCount=3 +buildCount=5 baseBetaVersion=15.11.4 diff --git a/libappbase/build.properties b/libappbase/build.properties index 43b7b4d9..aa9043ef 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Nov 30 08:19:07 GMT 2025 +#Sun Nov 30 08:26:11 GMT 2025 stageCount=4 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.3 -buildCount=3 +buildCount=5 baseBetaVersion=15.11.4 diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java index 2e9fdf03..31cd0ef4 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java @@ -186,7 +186,7 @@ public final class CrashHandler { ); try { - if (GlobalApplication.isDebugging()) { + if (GlobalApplication.isDebugging()&&AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) { // 如果是 debug 版,启动崩溃页面窗口 app.startActivity(intent); } else { diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java index 8faade8f..d3b1f913 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java @@ -7,6 +7,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; +import android.text.TextUtils; import cc.winboll.studio.libappbase.CrashHandler; import cc.winboll.studio.libappbase.GlobalCrashActivity; import cc.winboll.studio.libappbase.LogUtils; @@ -14,8 +15,9 @@ import cc.winboll.studio.libappbase.LogUtils; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/11/29 21:12 - * @Describe 应用崩溃处理通知实用工具集 - * 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏(3行内容省略),点击通知跳转 GlobalCrashActivity 并传递日志 + * @Describe 应用崩溃处理通知实用工具集(类库兼容版) + * 核心功能:作为独立类库使用,发送崩溃通知,点击跳转宿主应用的 GlobalCrashActivity 并传递日志 + * 适配说明:移除固定包名依赖,通过外部传入宿主包名,支持任意应用集成使用 */ public class CrashHandleNotifyUtils { @@ -35,174 +37,205 @@ public class CrashHandleNotifyUtils { private static final int NOTIFICATION_MAX_LINES = 3; /** - * 处理未捕获异常(核心方法) - * 1. 提取应用名称和崩溃日志; - * 2. 创建并发送系统通知(3行内容省略,无复制按钮); - * 3. 兼容 Android 8.0+ 通知渠道机制,适配低版本系统。 - * @param app 应用全局 Application 实例(用于获取上下文、应用信息) - * @param intent 存储崩溃信息的意图(extra 中携带崩溃日志) + * 处理未捕获异常(核心方法,类库入口) + * 改进点:新增宿主包名参数,移除类库对固定包名的依赖 + * @param hostApp 宿主应用的 Application 实例(用于获取宿主上下文) + * @param hostPackageName 宿主应用的包名(关键:用于绑定意图、匹配 Activity) + * @param errorLog 崩溃日志(从宿主 CrashHandler 传递过来) */ - public static void handleUncaughtException(Application app, Intent intent) { - // 1. 提取应用名称(优化:从 Application 中获取真实应用名,替代原类名) - String appName = getAppName(app); - // 2. 提取崩溃日志(从 Intent Extra 中获取,对应 CrashHandler 存储的崩溃信息) - String errorLog = intent.getStringExtra(CrashHandler.EXTRA_CRASH_INFO); - - // 校验参数(避免空指针,确保通知正常发送) - if (app == null || appName == null || errorLog == null) { - LogUtils.e(TAG, "发送崩溃通知失败:参数为空(app=" + app + ", appName=" + appName + ", errorLog=" + errorLog + ")"); + public static void handleUncaughtException(Application hostApp, String hostPackageName, String errorLog) { + // 1. 校验核心参数(类库场景必须严格校验,避免空指针) + if (hostApp == null || TextUtils.isEmpty(hostPackageName) || TextUtils.isEmpty(errorLog)) { + LogUtils.e(TAG, "发送崩溃通知失败:参数为空(hostApp=" + hostApp + ", hostPackageName=" + hostPackageName + ", errorLog=" + errorLog + ")"); return; } - // 3. 发送崩溃通知到通知栏(3行省略,点击跳转 GlobalCrashActivity) - sendCrashNotification(app, appName, errorLog); + // 2. 获取宿主应用名称(使用宿主上下文,避免类库包名混淆) + String hostAppName = getHostAppName(hostApp, hostPackageName); + + // 3. 发送崩溃通知到宿主通知栏(点击跳转宿主的 GlobalCrashActivity) + sendCrashNotification(hostApp, hostPackageName, hostAppName, errorLog); } /** - * 获取应用真实名称(从 AndroidManifest 中读取 android:label) - * 替代原 app.getClass().toString()(原方式获取的是类名,用户不可读) - * @param context 上下文(Application 实例) - * @return 应用名称(读取失败返回 "未知应用") + * 重载方法:兼容原有调用逻辑(避免宿主集成时改动过大) + * 从 Intent 中提取崩溃日志和宿主包名,适配原有 CrashHandler 调用方式 + * @param hostApp 宿主应用的 Application 实例 + * @param intent 存储崩溃信息的意图(extra 中携带崩溃日志) */ - private static String getAppName(Context context) { + public static void handleUncaughtException(Application hostApp, Intent intent) { + // 从意图中提取宿主包名(优先使用意图中携带的包名,无则用宿主 Application 包名) + String hostPackageName = intent.getStringExtra("EXTRA_HOST_PACKAGE_NAME"); + if (TextUtils.isEmpty(hostPackageName)) { + hostPackageName = hostApp.getPackageName(); + LogUtils.w(TAG, "意图中未携带宿主包名,使用 Application 包名:" + hostPackageName); + } + + // 从意图中提取崩溃日志(与 CrashHandler.EXTRA_CRASH_INFO 保持一致) + String errorLog = intent.getStringExtra(CrashHandler.EXTRA_CRASH_INFO); + + // 调用核心方法处理 + handleUncaughtException(hostApp, hostPackageName, errorLog); + } + + /** + * 获取宿主应用名称(类库场景适配:使用宿主包名获取,避免类库包名干扰) + * @param hostContext 宿主应用的上下文(Application 实例) + * @param hostPackageName 宿主应用的包名 + * @return 宿主应用名称(读取失败返回 "未知应用") + */ + private static String getHostAppName(Context hostContext, String hostPackageName) { try { - // 从包管理器中获取应用信息(包含应用名称) - return context.getPackageManager().getApplicationLabel( - context.getApplicationInfo() + // 用宿主包名获取宿主应用信息,确保获取的是宿主的应用名称(类库关键改进) + return hostContext.getPackageManager().getApplicationLabel( + hostContext.getPackageManager().getApplicationInfo(hostPackageName, 0) ).toString(); } catch (Exception e) { - LogUtils.e(TAG, "获取应用名称失败", e); + LogUtils.e(TAG, "获取宿主应用名称失败(包名:" + hostPackageName + ")", e); return "未知应用"; } } /** - * 发送崩溃通知到系统通知栏(移除复制按钮,点击跳转 GlobalCrashActivity) - * @param context 上下文(Application 实例,确保后台也能发送) - * @param title 通知标题(应用名称) - * @param content 通知内容(崩溃日志,需传递给 GlobalCrashActivity) + * 发送崩溃通知到宿主系统通知栏(类库兼容版) + * 改进点:全程使用宿主上下文和宿主包名,避免类库包名依赖 + * @param hostContext 宿主应用的上下文(Application 实例) + * @param hostPackageName 宿主应用的包名 + * @param hostAppName 宿主应用的名称(用于通知标题) + * @param errorLog 崩溃日志(传递给宿主的 GlobalCrashActivity) */ - private static void sendCrashNotification(Context context, String title, String content) { - // 1. 获取通知管理器(系统服务,用于发送/管理通知) - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + private static void sendCrashNotification(Context hostContext, String hostPackageName, String hostAppName, String errorLog) { + // 1. 获取宿主的通知管理器(使用宿主上下文,确保通知归属宿主应用) + NotificationManager notificationManager = (NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager == null) { - LogUtils.e(TAG, "发送崩溃通知失败:获取 NotificationManager 为空"); + LogUtils.e(TAG, "获取宿主 NotificationManager 失败(包名:" + hostPackageName + ")"); return; } - // 2. 适配 Android 8.0+(API 26+):创建通知渠道(必须,否则通知不显示) + // 2. 适配 Android 8.0+(API 26+):创建宿主的通知渠道(归属宿主,避免类库渠道冲突) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createCrashNotifyChannel(notificationManager); + createCrashNotifyChannel(hostContext, notificationManager); } - // 3. 构建通知意图(核心修改:点击跳转 GlobalCrashActivity,传递崩溃日志) - PendingIntent jumpIntent = getGlobalCrashPendingIntent(context, content); // 跳转崩溃详情页意图 + // 3. 构建通知意图(核心改进:绑定宿主包名,跳转宿主的 GlobalCrashActivity) + PendingIntent jumpIntent = getGlobalCrashPendingIntent(hostContext, hostPackageName, errorLog); + if (jumpIntent == null) { + LogUtils.e(TAG, "构建跳转意图失败(宿主包名:" + hostPackageName + ")"); + return; + } - // 4. 构建通知实例(移除复制按钮,仅保留3行内容省略) - Notification notification = buildNotification(context, title, content, jumpIntent); + // 4. 构建通知实例(使用宿主上下文,确保通知资源归属宿主) + Notification notification = buildNotification(hostContext, hostAppName, errorLog, jumpIntent); - // 5. 发送通知(指定通知ID,重复发送同ID会覆盖原通知) + // 5. 发送通知(归属宿主应用,避免类库与宿主通知混淆) notificationManager.notify(CRASH_NOTIFY_ID, notification); - LogUtils.d(TAG, "崩溃通知发送成功:标题=" + title + ",内容长度=" + content.length() + "字符(点击跳转崩溃详情页)"); + LogUtils.d(TAG, "崩溃通知发送成功(宿主包名:" + hostPackageName + ",标题:" + hostAppName + ",日志长度:" + errorLog.length() + "字符)"); } /** - * 创建崩溃通知渠道(Android 8.0+ 必需) - * 通知渠道用于归类通知,用户可在系统设置中管理(开启/关闭/静音) - * @param notificationManager 通知管理器 + * 创建宿主应用的崩溃通知渠道(类库场景:渠道归属宿主,避免类库与宿主渠道冲突) + * @param hostContext 宿主应用的上下文 + * @param notificationManager 宿主的通知管理器 */ - private static void createCrashNotifyChannel(NotificationManager notificationManager) { + private static void createCrashNotifyChannel(Context hostContext, NotificationManager notificationManager) { // 仅 Android 8.0+ 执行(避免低版本报错) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // 构建通知渠道(指定ID、名称、重要性) + // 构建通知渠道(归属宿主应用,描述明确类库用途) android.app.NotificationChannel channel = new android.app.NotificationChannel( CRASH_NOTIFY_CHANNEL_ID, CRASH_NOTIFY_CHANNEL_NAME, - NotificationManager.IMPORTANCE_DEFAULT // 重要性:默认(不会弹窗,有声音提示) + NotificationManager.IMPORTANCE_DEFAULT ); - // 可选:设置渠道描述(用户在设置中可见) - channel.setDescription("用于显示应用崩溃信息,点击查看详情"); - // 注册通知渠道到系统 + channel.setDescription("应用崩溃通知(由 WinBoLL Studio 类库提供,点击查看详情)"); + // 注册渠道到宿主的通知管理器,确保渠道归属宿主 notificationManager.createNotificationChannel(channel); - LogUtils.d(TAG, "崩溃通知渠道创建成功:" + CRASH_NOTIFY_CHANNEL_ID); + LogUtils.d(TAG, "宿主崩溃通知渠道创建成功(宿主包名:" + hostContext.getPackageName() + ",渠道ID:" + CRASH_NOTIFY_CHANNEL_ID + ")"); } } /** - * 核心修改:构建跳转 GlobalCrashActivity 的意图(传递崩溃日志) - * 与 GlobalCrashActivity 的日志接收键(EXTRA_CRASH_INFO)保持一致 - * @param context 上下文 - * @param errorLog 崩溃日志(需传递给 GlobalCrashActivity) + * 核心改进:构建跳转宿主 GlobalCrashActivity 的意图(类库关键) + * 1. 绑定宿主包名,确保类库能正确启动宿主的 Activity; + * 2. 传递崩溃日志,与宿主 GlobalCrashActivity 日志接收逻辑匹配; + * 3. 使用宿主上下文,避免类库上下文导致的适配问题。 + * @param hostContext 宿主应用的上下文 + * @param hostPackageName 宿主应用的包名 + * @param errorLog 崩溃日志(传递给宿主的 GlobalCrashActivity) * @return 跳转崩溃详情页的 PendingIntent */ - private static PendingIntent getGlobalCrashPendingIntent(Context context, String errorLog) { - // 1. 构建跳转 GlobalCrashActivity 的显式意图 - Intent crashIntent = new Intent(context, GlobalCrashActivity.class); - // 传递崩溃日志(键:EXTRA_CRASH_INFO,与 GlobalCrashActivity 一致) - crashIntent.putExtra(CrashHandler.EXTRA_CRASH_INFO, errorLog); - // 设置意图标志:确保 Activity 正常启动,避免重复创建 - crashIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + private static PendingIntent getGlobalCrashPendingIntent(Context hostContext, String hostPackageName, String errorLog) { + try { + // 1. 构建跳转宿主 GlobalCrashActivity 的显式意图(类库场景必须显式指定宿主包名) + Intent crashIntent = new Intent(hostContext, GlobalCrashActivity.class); + // 关键:绑定宿主包名,确保意图能正确匹配宿主的 Activity(避免类库包名干扰) + crashIntent.setPackage(hostPackageName); + // 传递崩溃日志(键:EXTRA_CRASH_INFO,与宿主 GlobalCrashActivity 完全匹配) + crashIntent.putExtra(CrashHandler.EXTRA_CRASH_INFO, errorLog); + // 设置意图标志:确保在宿主应用中正常启动,避免重复创建和任务栈混乱 + crashIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - // 2. 构建 PendingIntent(延迟执行的意图) - int flags = PendingIntent.FLAG_UPDATE_CURRENT; - // 适配 Android 12+(API 31+):添加 FLAG_IMMUTABLE 避免安全警告 - if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) { - flags |= FLAG_IMMUTABLE; + // 2. 构建 PendingIntent(使用宿主上下文,适配高版本) + int flags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) { + flags |= FLAG_IMMUTABLE; + } + + return PendingIntent.getActivity( + hostContext, + CRASH_NOTIFY_ID, // 用通知ID作为请求码,确保唯一(避免意图复用) + crashIntent, + flags + ); + } catch (Exception e) { + LogUtils.e(TAG, "构建跳转意图失败(宿主包名:" + hostPackageName + ")", e); + return null; } - - return PendingIntent.getActivity( - context, - CRASH_NOTIFY_ID, // 用通知ID作为请求码,确保唯一(避免意图复用) - crashIntent, - flags - ); } /** - * 构建通知实例(核心调整:移除复制按钮,仅保留3行内容省略) - * @param context 上下文 - * @param title 通知标题(应用名称) - * @param content 通知内容(崩溃日志) - * @param jumpIntent 通知点击跳转意图(跳转 GlobalCrashActivity) + * 构建通知实例(类库兼容版) + * 改进点:使用宿主上下文加载资源,确保通知样式适配宿主应用 + * @param hostContext 宿主应用的上下文 + * @param hostAppName 宿主应用的名称(通知标题) + * @param errorLog 崩溃日志(通知内容) + * @param jumpIntent 通知点击跳转意图(跳转宿主的 GlobalCrashActivity) * @return 构建完成的 Notification 对象 */ - private static Notification buildNotification(Context context, String title, String content, PendingIntent jumpIntent) { - // 兼容 Android 8.0+:指定通知渠道ID - Notification.Builder builder = new Notification.Builder(context); + private static Notification buildNotification(Context hostContext, String hostAppName, String errorLog, PendingIntent jumpIntent) { + // 兼容 Android 8.0+:指定宿主的通知渠道ID + Notification.Builder builder = new Notification.Builder(hostContext); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder.setChannelId(CRASH_NOTIFY_CHANNEL_ID); } - // 核心:用BigTextStyle控制“默认3行省略,下拉显示完整” + // 核心:用BigTextStyle控制“默认3行省略,下拉显示完整”(使用宿主上下文构建) Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(); - bigTextStyle.setSummaryText("日志已省略,下拉查看完整内容"); // 底部省略提示 - bigTextStyle.bigText(content); // 完整日志(下拉时显示) - bigTextStyle.setBigContentTitle(title); // 下拉后的标题(与主标题一致) + bigTextStyle.setSummaryText("日志已省略,下拉查看完整内容"); + bigTextStyle.bigText(errorLog); + bigTextStyle.setBigContentTitle(hostAppName + " 崩溃"); // 标题明确标识宿主和崩溃状态 builder.setStyle(bigTextStyle); - // 配置通知核心参数(移除复制按钮相关代码) + // 配置通知核心参数(全程使用宿主上下文,确保资源归属宿主) builder - .setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,否则通知不显示) - .setContentTitle(title + " 崩溃") // 优化标题:明确标识“崩溃” - .setContentText(getShortContent(content)) // 核心:3行内缩略文本 - .setContentIntent(jumpIntent) // 通知主体点击跳转 GlobalCrashActivity - .setAutoCancel(true) // 点击通知后自动取消 - .setWhen(System.currentTimeMillis()) // 通知创建时间 - .setPriority(Notification.PRIORITY_DEFAULT); // 通知优先级 + // 关键:使用宿主应用的小图标(避免类库图标显示异常) + .setSmallIcon(hostContext.getApplicationInfo().icon) + .setContentTitle(hostAppName + " 崩溃") + .setContentText(getShortContent(errorLog)) // 3行内缩略文本 + .setContentIntent(jumpIntent) // 点击跳转宿主的 GlobalCrashActivity + .setAutoCancel(true) // 点击后自动关闭 + .setWhen(System.currentTimeMillis()) + .setPriority(Notification.PRIORITY_DEFAULT); - // 适配 Android 4.1+:确保文本显示正常 - // 构建通知并返回(兼容低版本,区分 API 等级避免报错) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - return builder.build(); - } else { - // Android 4.0 及以下版本,使用 getNotification() 方法 - return builder.getNotification(); - } + // 适配 Android 4.1+:确保在宿主应用中正常显示 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return builder.build(); + } else { + return builder.getNotification(); + } } /** - * 辅助方法:截取日志文本,确保显示在3行内(按字符数估算,适配大多数设备) - * 一行约20-30字符,3行约80字符(留冗余,取80字符,超出加省略号) + * 辅助方法:截取日志文本,确保显示在3行内(通用逻辑,无包名依赖) * @param content 完整崩溃日志 * @return 3行内的缩略文本 */ @@ -210,22 +243,16 @@ public class CrashHandleNotifyUtils { if (content == null || content.isEmpty()) { return "无崩溃日志"; } - // 估算3行字符数(80字符,可根据设备屏幕调整,避免因字符过长导致换行超3行) - int maxLength = 80; - if (content.length() <= maxLength) { - return content; // 不足3行,直接返回完整文本 - } else { - // 超出3行,截取前80字符并加省略号(确保视觉上仅显示3行) - return content.substring(0, maxLength) + "..."; - } + int maxLength = 80; // 估算3行字符数(可根据需求调整) + return content.length() <= maxLength ? content : content.substring(0, maxLength) + "..."; } /** - * 释放资源(移除广播注销逻辑,保留空实现避免调用报错) - * @param context 上下文(Application 实例) + * 释放资源(类库场景:空实现,避免宿主调用时报错,预留扩展) + * @param hostContext 宿主应用的上下文(显式传入,避免类库上下文依赖) */ - public static void release(Context context) { - LogUtils.d(TAG, "CrashHandleNotifyUtils 资源释放完成"); + public static void release(Context hostContext) { + LogUtils.d(TAG, "CrashHandleNotifyUtils 资源释放完成(宿主包名:" + (hostContext != null ? hostContext.getPackageName() : "未知") + ")"); } }