添加发布版应用异常处理方案
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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&豆包大模型<zhangsken@qq.com>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user