feat(CrashHandleNotifyUtils): 增强崩溃通知摘要提取与展示逻辑
- 新增正则表达式解析崩溃日志,提取异常类型、消息、原因及堆栈信息 - 实现 extractBriefInfo() 方法,生成结构化的崩溃摘要 - 优化通知内容展示:异常类型 + 错误信息 + 关键堆栈(最多3行) - 添加摘要最大长度控制(200字符),防止内容过长 - 新增展开通知布局文件支持
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,7 +120,7 @@ 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,
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
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