feat(CrashHandleNotifyUtils): 增强崩溃通知摘要提取与展示逻辑
- 新增正则表达式解析崩溃日志,提取异常类型、消息、原因及堆栈信息 - 实现 extractBriefInfo() 方法,生成结构化的崩溃摘要 - 优化通知内容展示:异常类型 + 错误信息 + 关键堆栈(最多3行) - 添加摘要最大长度控制(200字符),防止内容过长 - 新增展开通知布局文件支持
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon May 11 16:56:31 HKT 2026
|
||||
#Mon May 11 18:51:30 CST 2026
|
||||
stageCount=7
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.20
|
||||
publishVersion=15.20.6
|
||||
buildCount=0
|
||||
buildCount=12
|
||||
baseBetaVersion=15.20.7
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon May 11 16:56:19 HKT 2026
|
||||
#Mon May 11 18:51:30 CST 2026
|
||||
stageCount=7
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.20
|
||||
publishVersion=15.20.6
|
||||
buildCount=0
|
||||
buildCount=12
|
||||
baseBetaVersion=15.20.7
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.text.TextUtils;
|
||||
|
||||
import cc.winboll.studio.libappbase.CrashHandler;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
|
||||
/**
|
||||
* 应用崩溃处理通知实用工具集(类库兼容版)
|
||||
@@ -36,6 +37,21 @@ public class CrashHandleNotifyUtils {
|
||||
private static final int FLAG_IMMUTABLE = 0x00000040;
|
||||
/** 通知内容最大行数(控制在3行,超出部分省略) */
|
||||
private static final int NOTIFICATION_MAX_LINES = 3;
|
||||
/** 通知摘要最大长度 */
|
||||
private static final int SUMMARY_MAX_LENGTH = 200;
|
||||
/** 展开按钮广播Action */
|
||||
private static final String ACTION_EXPAND = "cc.winboll.studio.libappbase.ACTION_EXPAND_CRASH";
|
||||
|
||||
private static Context sHostContext = null;
|
||||
private static String sHostPackageName = "";
|
||||
private static String sHostAppName = "";
|
||||
private static Class<?> sReportCrashActivity = null;
|
||||
|
||||
// ====================== 正则表达式定义 ======================
|
||||
private static final String REGEX_EXCEPTION_TYPE = "([\\w.]+Exception|[\\w.]+Error)";
|
||||
private static final String REGEX_EXCEPTION_MESSAGE = "(?<=:\\s)(.+?)(?=\\n|\\r|$)";
|
||||
private static final String REGEX_STACK_TRACE = "\\s+at\\s+([\\w.$]+)\\.([\\w<>]+)\\(([^:]+\\.java):(\\d+)\\)";
|
||||
private static final String REGEX_CAUSE = "(?<=Caused by:\\s)" + REGEX_EXCEPTION_TYPE + "\\s*:";
|
||||
|
||||
// ====================== 对外核心方法 ======================
|
||||
/**
|
||||
@@ -104,11 +120,11 @@ public class CrashHandleNotifyUtils {
|
||||
* @param errorLog 崩溃日志
|
||||
* @param reportCrashActivity 跳转Activity
|
||||
*/
|
||||
private static void sendCrashNotification(final Context hostContext,
|
||||
final String hostPackageName,
|
||||
final String hostAppName,
|
||||
final String errorLog,
|
||||
final Class<?> reportCrashActivity) {
|
||||
private static void sendCrashNotification(final Context hostContext,
|
||||
final String hostPackageName,
|
||||
final String hostAppName,
|
||||
final String errorLog,
|
||||
final Class<?> reportCrashActivity) {
|
||||
final NotificationManager notificationManager =
|
||||
(NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (notificationManager == null) {
|
||||
@@ -125,7 +141,7 @@ public class CrashHandleNotifyUtils {
|
||||
LogUtils.e(TAG, "构建跳转PendingIntent失败");
|
||||
return;
|
||||
}
|
||||
final Notification notification = buildNotification(hostContext, hostAppName, errorLog, jumpIntent);
|
||||
final Notification notification = buildNotification(hostContext, hostPackageName, hostAppName, errorLog, jumpIntent);
|
||||
notificationManager.notify(CRASH_NOTIFY_ID, notification);
|
||||
LogUtils.d(TAG, "崩溃通知发送成功,宿主包名:" + hostPackageName);
|
||||
}
|
||||
@@ -171,7 +187,7 @@ public class CrashHandleNotifyUtils {
|
||||
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
|
||||
flags |= FLAG_IMMUTABLE;
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
return PendingIntent.getActivity(
|
||||
hostContext,
|
||||
CRASH_NOTIFY_ID,
|
||||
crashIntent,
|
||||
@@ -186,6 +202,7 @@ public class CrashHandleNotifyUtils {
|
||||
/**
|
||||
* 构建Notification通知实例
|
||||
* @param hostContext 宿主上下文
|
||||
* @param hostPackageName 宿主包名
|
||||
* @param hostAppName 宿主应用名
|
||||
* @param errorLog 崩溃日志
|
||||
* @param jumpIntent 点击跳转意图
|
||||
@@ -193,6 +210,7 @@ public class CrashHandleNotifyUtils {
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private static Notification buildNotification(final Context hostContext,
|
||||
final String hostPackageName,
|
||||
final String hostAppName,
|
||||
final String errorLog,
|
||||
final PendingIntent jumpIntent) {
|
||||
@@ -200,15 +218,15 @@ public class CrashHandleNotifyUtils {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setChannelId(CRASH_NOTIFY_CHANNEL_ID);
|
||||
}
|
||||
String briefInfo = extractBriefInfo(errorLog);
|
||||
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
|
||||
bigTextStyle.setSummaryText("日志已省略,下拉查看完整内容");
|
||||
bigTextStyle.bigText(errorLog);
|
||||
bigTextStyle.setBigContentTitle(hostAppName + " 崩溃");
|
||||
bigTextStyle.bigText(briefInfo);
|
||||
bigTextStyle.setSummaryText("点击查看详情");
|
||||
builder.setStyle(bigTextStyle);
|
||||
|
||||
builder.setSmallIcon(hostContext.getApplicationInfo().icon)
|
||||
.setContentTitle(hostAppName + " 崩溃")
|
||||
.setContentText(getShortContent(errorLog))
|
||||
.setContentText(briefInfo.split("\n")[0])
|
||||
.setContentIntent(jumpIntent)
|
||||
.setAutoCancel(true)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
@@ -226,12 +244,76 @@ public class CrashHandleNotifyUtils {
|
||||
* @param content 原始日志
|
||||
* @return 缩略文案
|
||||
*/
|
||||
private static String getShortContent(final String content) {
|
||||
if (content == null || content.isEmpty()) {
|
||||
private static String getShortContent(final String errorLog) {
|
||||
if (errorLog == null || errorLog.isEmpty()) {
|
||||
return "无崩溃日志";
|
||||
}
|
||||
final int maxLength = 80;
|
||||
return content.length() <= maxLength ? content : content.substring(0, maxLength) + "...";
|
||||
String brief = extractBriefInfo(errorLog);
|
||||
String firstLine = brief.split("\n")[0];
|
||||
if (firstLine.length() > 80) {
|
||||
firstLine = firstLine.substring(0, 80) + "...";
|
||||
}
|
||||
return firstLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用正则表达式从崩溃日志中提取简要信息
|
||||
* @param crashLog 完整崩溃日志
|
||||
* @return 简要崩溃信息
|
||||
*/
|
||||
private static String extractBriefInfo(final String crashLog) {
|
||||
if (crashLog == null || crashLog.isEmpty()) {
|
||||
return "无崩溃日志";
|
||||
}
|
||||
StringBuilder brief = new StringBuilder();
|
||||
try {
|
||||
java.util.regex.Pattern exceptionPattern = java.util.regex.Pattern.compile(REGEX_EXCEPTION_TYPE);
|
||||
java.util.regex.Matcher exceptionMatcher = exceptionPattern.matcher(crashLog);
|
||||
if (exceptionMatcher.find()) {
|
||||
brief.append(exceptionMatcher.group(1));
|
||||
}
|
||||
java.util.regex.Pattern messagePattern = java.util.regex.Pattern.compile(REGEX_EXCEPTION_MESSAGE);
|
||||
java.util.regex.Matcher messageMatcher = messagePattern.matcher(crashLog);
|
||||
if (messageMatcher.find()) {
|
||||
String message = messageMatcher.group(1).trim();
|
||||
if (message.length() > 100) {
|
||||
message = message.substring(0, 100) + "...";
|
||||
}
|
||||
if (brief.length() > 0) {
|
||||
brief.append(" - ");
|
||||
}
|
||||
brief.append(message);
|
||||
}
|
||||
java.util.regex.Pattern causePattern = java.util.regex.Pattern.compile(REGEX_CAUSE);
|
||||
java.util.regex.Matcher causeMatcher = causePattern.matcher(crashLog);
|
||||
if (causeMatcher.find()) {
|
||||
if (brief.length() > 0) {
|
||||
brief.append("\n");
|
||||
}
|
||||
brief.append("原因: ").append(causeMatcher.group(1));
|
||||
}
|
||||
java.util.regex.Pattern stackPattern = java.util.regex.Pattern.compile(REGEX_STACK_TRACE);
|
||||
java.util.regex.Matcher stackMatcher = stackPattern.matcher(crashLog);
|
||||
int lineCount = 0;
|
||||
while (stackMatcher.find() && lineCount < 3) {
|
||||
if (brief.length() > 0) {
|
||||
brief.append("\n");
|
||||
}
|
||||
brief.append(" at ").append(stackMatcher.group(1)).append(".")
|
||||
.append(stackMatcher.group(2)).append("(")
|
||||
.append(stackMatcher.group(3)).append(":")
|
||||
.append(stackMatcher.group(4)).append(")");
|
||||
lineCount++;
|
||||
}
|
||||
if (brief.length() == 0) {
|
||||
brief.append(crashLog.length() > SUMMARY_MAX_LENGTH ? crashLog.substring(0, SUMMARY_MAX_LENGTH) + "..." : crashLog);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "提取崩溃简要信息失败", e);
|
||||
brief.setLength(0);
|
||||
brief.append(crashLog.length() > SUMMARY_MAX_LENGTH ? crashLog.substring(0, SUMMARY_MAX_LENGTH) + "..." : crashLog);
|
||||
}
|
||||
return brief.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
27
libappbase/src/main/res/layout/notification_crash.xml
Normal file
27
libappbase/src/main/res/layout/notification_crash.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#666666"
|
||||
android:minHeight="200dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="13sp"
|
||||
android:textColor="#666666"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user