diff --git a/appbase/build.properties b/appbase/build.properties index 176903be..d29d8239 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Nov 29 02:41:18 HKT 2025 +#Sat Nov 29 13:34:58 GMT 2025 stageCount=3 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.2 -buildCount=0 +buildCount=1 baseBetaVersion=15.11.3 diff --git a/appbase/src/main/java/cc/winboll/studio/appbase/App.java b/appbase/src/main/java/cc/winboll/studio/appbase/App.java index 888d581f..501cdc5c 100644 --- a/appbase/src/main/java/cc/winboll/studio/appbase/App.java +++ b/appbase/src/main/java/cc/winboll/studio/appbase/App.java @@ -21,6 +21,7 @@ public class App extends GlobalApplication { @Override public void onCreate() { super.onCreate(); // 调用父类初始化逻辑(如基础库配置、全局上下文设置) + setIsDebugging(false); // 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用) ToastUtils.init(getApplicationContext()); } diff --git a/libappbase/build.properties b/libappbase/build.properties index de547372..d29d8239 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Nov 29 02:40:35 HKT 2025 +#Sat Nov 29 13:34:58 GMT 2025 stageCount=3 libraryProject=libappbase baseVersion=15.11 publishVersion=15.11.2 -buildCount=0 +buildCount=1 baseBetaVersion=15.11.3 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 2432cd9b..2e9fdf03 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java @@ -23,6 +23,7 @@ import android.widget.HorizontalScrollView; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; +import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -185,10 +186,17 @@ public final class CrashHandler { ); try { - // 启动崩溃页面,终止当前进程(确保完全重启) - app.startActivity(intent); + if (GlobalApplication.isDebugging()) { + // 如果是 debug 版,启动崩溃页面窗口 + app.startActivity(intent); + } else { + // 如果是 release 版,就只发送一个通知 + CrashHandleNotifyUtils.handleUncaughtException(app, intent); + } + // 终止当前进程(确保完全重启) android.os.Process.killProcess(android.os.Process.myPid()); System.exit(0); + } catch (ActivityNotFoundException e) { // 未找到崩溃页面(如未在 Manifest 注册),交给系统默认处理器 e.printStackTrace(); 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 new file mode 100644 index 00000000..e6c77d41 --- /dev/null +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/utils/CrashHandleNotifyUtils.java @@ -0,0 +1,195 @@ +package cc.winboll.studio.libappbase.utils; + +import android.app.Application; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import cc.winboll.studio.libappbase.CrashHandler; +import cc.winboll.studio.libappbase.LogUtils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/11/29 21:12 + * @Describe 应用崩溃处理通知实用工具集 + * 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏,方便用户查看崩溃信息 + */ +public class CrashHandleNotifyUtils { + + public static final String TAG = "CrashHandleNotifyUtils"; + + /** 通知渠道ID(Android 8.0+ 必须,用于归类通知) */ + private static final String CRASH_NOTIFY_CHANNEL_ID = "crash_notify_channel"; + /** 通知渠道名称(用户可见,描述渠道用途) */ + private static final String CRASH_NOTIFY_CHANNEL_NAME = "应用崩溃通知"; + /** 通知ID(唯一标识一条通知,避免重复创建) */ + private static final int CRASH_NOTIFY_ID = 0x001; + /** Android 12 对应 API 版本号(31),替代 Build.VERSION_CODES.S */ + private static final int API_LEVEL_ANDROID_12 = 31; + /** PendingIntent.FLAG_IMMUTABLE 常量值(API 31+),避免依赖高版本 SDK */ + private static final int FLAG_IMMUTABLE = 0x00000040; + + /** + * 处理未捕获异常(核心方法) + * 1. 提取应用名称和崩溃日志; + * 2. 创建并发送系统通知(标题:应用名称,内容:崩溃日志); + * 3. 兼容 Android 8.0+ 通知渠道机制,适配低版本系统。 + * @param app 应用全局 Application 实例(用于获取上下文、应用信息) + * @param intent 存储崩溃信息的意图(extra 中携带崩溃日志) + */ + 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 + ")"); + return; + } + + // 3. 发送崩溃通知到通知栏 + sendCrashNotification(app, appName, errorLog); + } + + /** + * 获取应用真实名称(从 AndroidManifest 中读取 android:label) + * 替代原 app.getClass().toString()(原方式获取的是类名,用户不可读) + * @param context 上下文(Application 实例) + * @return 应用名称(读取失败返回 "未知应用") + */ + private static String getAppName(Context context) { + try { + // 从包管理器中获取应用信息(包含应用名称) + return context.getPackageManager().getApplicationLabel( + context.getApplicationInfo() + ).toString(); + } catch (Exception e) { + LogUtils.e(TAG, "获取应用名称失败", e); + return "未知应用"; + } + } + + /** + * 发送崩溃通知到系统通知栏 + * @param context 上下文(Application 实例,确保后台也能发送) + * @param title 通知标题(应用名称) + * @param content 通知内容(崩溃日志) + */ + private static void sendCrashNotification(Context context, String title, String content) { + // 1. 获取通知管理器(系统服务,用于发送/管理通知) + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + LogUtils.e(TAG, "发送崩溃通知失败:获取 NotificationManager 为空"); + return; + } + + // 2. 适配 Android 8.0+(API 26+):创建通知渠道(必须,否则通知不显示) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createCrashNotifyChannel(notificationManager); + } + + // 3. 构建通知意图(点击通知时可跳转,此处默认跳转应用主界面,可自定义) + PendingIntent pendingIntent = getNotificationPendingIntent(context); + + // 4. 构建通知实例(兼容低版本,使用 Notification.Builder 构建) + Notification notification = buildNotification(context, title, content, pendingIntent); + + // 5. 发送通知(指定通知ID,重复发送同ID会覆盖原通知) + notificationManager.notify(CRASH_NOTIFY_ID, notification); + LogUtils.d(TAG, "崩溃通知发送成功:标题=" + title + ",内容长度=" + content.length() + "字符"); + } + + /** + * 创建崩溃通知渠道(Android 8.0+ 必需) + * 通知渠道用于归类通知,用户可在系统设置中管理(开启/关闭/静音) + * @param notificationManager 通知管理器 + */ + private static void createCrashNotifyChannel(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 // 重要性:默认(不会弹窗,有声音提示) + ); + // 可选:设置渠道描述(用户在设置中可见) + channel.setDescription("用于显示应用崩溃信息,帮助定位问题"); + // 注册通知渠道到系统 + notificationManager.createNotificationChannel(channel); + LogUtils.d(TAG, "崩溃通知渠道创建成功:" + CRASH_NOTIFY_CHANNEL_ID); + } + } + + /** + * 构建通知点击意图(PendingIntent) + * 点击通知后跳转应用主界面(可根据需求修改为跳转崩溃日志详情页) + * @param context 上下文 + * @return 封装好的 PendingIntent(用于通知点击跳转) + */ + private static PendingIntent getNotificationPendingIntent(Context context) { + // 1. 获取应用主界面 Intent(从包名启动默认 launcher Activity) + Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage( + context.getPackageName() + ); + if (launchIntent == null) { + // 异常处理:若主界面 Intent 为空,创建空意图(避免崩溃) + launchIntent = new Intent(); + } + + // 2. 构建 PendingIntent(延迟执行的意图,FLAG_UPDATE_CURRENT 表示更新已存在的意图) + int flags = PendingIntent.FLAG_UPDATE_CURRENT; + // 适配 Android 12+(API 31+):添加 FLAG_IMMUTABLE 避免安全警告(用常量值替代高版本 API) + if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) { + flags |= FLAG_IMMUTABLE; + } + + return PendingIntent.getActivity( + context, + 0, // 请求码(可忽略,用于区分多个 PendingIntent) + launchIntent, + flags + ); + } + + /** + * 构建通知实例(兼容 Android 4.0+ 所有版本) + * @param context 上下文 + * @param title 通知标题 + * @param content 通知内容 + * @param pendingIntent 通知点击意图 + * @return 构建完成的 Notification 对象 + */ + private static Notification buildNotification(Context context, String title, String content, PendingIntent pendingIntent) { + // 兼容 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); + } + + // 配置通知核心参数 + builder + .setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,从应用图标获取) + .setContentTitle(title) // 通知标题(应用名称) + .setContentText(content) // 通知内容(崩溃日志) + .setContentIntent(pendingIntent) // 通知点击意图 + .setAutoCancel(true) // 点击通知后自动取消 + .setWhen(System.currentTimeMillis()) // 通知创建时间(当前时间) + .setPriority(Notification.PRIORITY_DEFAULT); // 通知优先级(默认) + + // 可选:长文本适配(当崩溃日志过长时,显示完整文本) + if (content.length() > 100) { // 超过100字符时,设置长文本样式 + Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(); + bigTextStyle.bigText(content); // 显示完整崩溃日志 + builder.setStyle(bigTextStyle); + } + + // 构建通知并返回(getNotification() 兼容低版本,build() 是 Android 4.1+ 方法) + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ? builder.build() : builder.getNotification(); + } +} +