feat(CrashHandleNotifyUtils): 增强崩溃通知摘要提取与展示逻辑

- 新增正则表达式解析崩溃日志,提取异常类型、消息、原因及堆栈信息
- 实现 extractBriefInfo() 方法,生成结构化的崩溃摘要
- 优化通知内容展示:异常类型 + 错误信息 + 关键堆栈(最多3行)
- 添加摘要最大长度控制(200字符),防止内容过长
- 新增展开通知布局文件支持
This commit is contained in:
2026-05-11 19:02:51 +08:00
parent d20923eaee
commit 01b0a7736d
5 changed files with 155 additions and 19 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #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 stageCount=7
libraryProject=libappbase libraryProject=libappbase
baseVersion=15.20 baseVersion=15.20
publishVersion=15.20.6 publishVersion=15.20.6
buildCount=0 buildCount=12
baseBetaVersion=15.20.7 baseBetaVersion=15.20.7

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #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 stageCount=7
libraryProject=libappbase libraryProject=libappbase
baseVersion=15.20 baseVersion=15.20
publishVersion=15.20.6 publishVersion=15.20.6
buildCount=0 buildCount=12
baseBetaVersion=15.20.7 baseBetaVersion=15.20.7

View File

@@ -11,6 +11,7 @@ import android.text.TextUtils;
import cc.winboll.studio.libappbase.CrashHandler; import cc.winboll.studio.libappbase.CrashHandler;
import cc.winboll.studio.libappbase.LogUtils; 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; private static final int FLAG_IMMUTABLE = 0x00000040;
/** 通知内容最大行数控制在3行超出部分省略 */ /** 通知内容最大行数控制在3行超出部分省略 */
private static final int NOTIFICATION_MAX_LINES = 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 errorLog 崩溃日志
* @param reportCrashActivity 跳转Activity * @param reportCrashActivity 跳转Activity
*/ */
private static void sendCrashNotification(final Context hostContext, private static void sendCrashNotification(final Context hostContext,
final String hostPackageName, final String hostPackageName,
final String hostAppName, final String hostAppName,
final String errorLog, final String errorLog,
final Class<?> reportCrashActivity) { final Class<?> reportCrashActivity) {
final NotificationManager notificationManager = final NotificationManager notificationManager =
(NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE); (NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) { if (notificationManager == null) {
@@ -125,7 +141,7 @@ public class CrashHandleNotifyUtils {
LogUtils.e(TAG, "构建跳转PendingIntent失败"); LogUtils.e(TAG, "构建跳转PendingIntent失败");
return; return;
} }
final Notification notification = buildNotification(hostContext, hostAppName, errorLog, jumpIntent); final Notification notification = buildNotification(hostContext, hostPackageName, hostAppName, errorLog, jumpIntent);
notificationManager.notify(CRASH_NOTIFY_ID, notification); notificationManager.notify(CRASH_NOTIFY_ID, notification);
LogUtils.d(TAG, "崩溃通知发送成功,宿主包名:" + hostPackageName); LogUtils.d(TAG, "崩溃通知发送成功,宿主包名:" + hostPackageName);
} }
@@ -171,7 +187,7 @@ public class CrashHandleNotifyUtils {
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) { if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
flags |= FLAG_IMMUTABLE; flags |= FLAG_IMMUTABLE;
} }
return PendingIntent.getActivity( return PendingIntent.getActivity(
hostContext, hostContext,
CRASH_NOTIFY_ID, CRASH_NOTIFY_ID,
crashIntent, crashIntent,
@@ -186,6 +202,7 @@ public class CrashHandleNotifyUtils {
/** /**
* 构建Notification通知实例 * 构建Notification通知实例
* @param hostContext 宿主上下文 * @param hostContext 宿主上下文
* @param hostPackageName 宿主包名
* @param hostAppName 宿主应用名 * @param hostAppName 宿主应用名
* @param errorLog 崩溃日志 * @param errorLog 崩溃日志
* @param jumpIntent 点击跳转意图 * @param jumpIntent 点击跳转意图
@@ -193,6 +210,7 @@ public class CrashHandleNotifyUtils {
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private static Notification buildNotification(final Context hostContext, private static Notification buildNotification(final Context hostContext,
final String hostPackageName,
final String hostAppName, final String hostAppName,
final String errorLog, final String errorLog,
final PendingIntent jumpIntent) { final PendingIntent jumpIntent) {
@@ -200,15 +218,15 @@ public class CrashHandleNotifyUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CRASH_NOTIFY_CHANNEL_ID); builder.setChannelId(CRASH_NOTIFY_CHANNEL_ID);
} }
String briefInfo = extractBriefInfo(errorLog);
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(); Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
bigTextStyle.setSummaryText("日志已省略,下拉查看完整内容");
bigTextStyle.bigText(errorLog);
bigTextStyle.setBigContentTitle(hostAppName + " 崩溃"); bigTextStyle.setBigContentTitle(hostAppName + " 崩溃");
bigTextStyle.bigText(briefInfo);
bigTextStyle.setSummaryText("点击查看详情");
builder.setStyle(bigTextStyle); builder.setStyle(bigTextStyle);
builder.setSmallIcon(hostContext.getApplicationInfo().icon) builder.setSmallIcon(hostContext.getApplicationInfo().icon)
.setContentTitle(hostAppName + " 崩溃") .setContentTitle(hostAppName + " 崩溃")
.setContentText(getShortContent(errorLog)) .setContentText(briefInfo.split("\n")[0])
.setContentIntent(jumpIntent) .setContentIntent(jumpIntent)
.setAutoCancel(true) .setAutoCancel(true)
.setWhen(System.currentTimeMillis()) .setWhen(System.currentTimeMillis())
@@ -226,12 +244,76 @@ public class CrashHandleNotifyUtils {
* @param content 原始日志 * @param content 原始日志
* @return 缩略文案 * @return 缩略文案
*/ */
private static String getShortContent(final String content) { private static String getShortContent(final String errorLog) {
if (content == null || content.isEmpty()) { if (errorLog == null || errorLog.isEmpty()) {
return "无崩溃日志"; return "无崩溃日志";
} }
final int maxLength = 80; String brief = extractBriefInfo(errorLog);
return content.length() <= maxLength ? content : content.substring(0, maxLength) + "..."; 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();
} }
/** /**

View 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>

View 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="13sp"
android:textColor="#666666"
android:scrollbars="vertical" />
</LinearLayout>