LibAppBase: 添加崩溃通知分享日志功能
- 在崩溃通知中添加"分享日志"按钮,点击可分享崩溃日志 - 新增 ShareLogActivity 窗口类处理分享逻辑 - 崩溃日志先保存到缓存文件,再读取分享 - 移除广播接收器方案,简化实现 - 更新 AndroidManifest 注册新 Activity 修改文件: - libappbase/src/main/AndroidManifest.xml - libappbase/src/main/java/.../utils/CrashHandleNotifyUtils.java - libappbase/src/main/java/.../utils/ShareLogActivity.java (新增) 影响范围: 崩溃通知功能
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 19:10:52 HKT 2026
|
#Mon May 11 20:19:17 CST 2026
|
||||||
stageCount=8
|
stageCount=8
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.20
|
baseVersion=15.20
|
||||||
publishVersion=15.20.7
|
publishVersion=15.20.7
|
||||||
buildCount=0
|
buildCount=8
|
||||||
baseBetaVersion=15.20.8
|
baseBetaVersion=15.20.8
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Mon May 11 19:10:34 HKT 2026
|
#Mon May 11 20:19:17 CST 2026
|
||||||
stageCount=8
|
stageCount=8
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.20
|
baseVersion=15.20
|
||||||
publishVersion=15.20.7
|
publishVersion=15.20.7
|
||||||
buildCount=0
|
buildCount=8
|
||||||
baseBetaVersion=15.20.8
|
baseBetaVersion=15.20.8
|
||||||
|
|||||||
@@ -47,6 +47,13 @@
|
|||||||
|
|
||||||
<activity android:name="cc.winboll.studio.libappbase.activities.FTPBackupsActivity"/>
|
<activity android:name="cc.winboll.studio.libappbase.activities.FTPBackupsActivity"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".utils.ShareLogActivity"
|
||||||
|
android:label="ShareLogActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package cc.winboll.studio.libappbase.utils;
|
package cc.winboll.studio.libappbase.utils;
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
@@ -13,13 +13,19 @@ import cc.winboll.studio.libappbase.CrashHandler;
|
|||||||
import cc.winboll.studio.libappbase.LogUtils;
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
import cc.winboll.studio.libappbase.R;
|
import cc.winboll.studio.libappbase.R;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用崩溃处理通知实用工具集(类库兼容版)
|
* 应用崩溃处理通知实用工具集(类库兼容版)
|
||||||
* 核心功能:作为独立类库使用,发送崩溃通知,点击跳转宿主应用的 GlobalCrashActivity 并传递日志
|
* 核心功能:作为独立类库使用,发送崩溃通知,点击跳转宿主应用的 GlobalCrashActivity 并传递日志
|
||||||
* 适配说明:移除固定包名依赖,通过外部传入宿主包名,支持任意应用集成使用
|
* 适配说明:移除固定包名依赖,通过外部传入宿主包名,支持任意应用集成使用
|
||||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
* @CreateTime 2025/11/29 21:12:00
|
* @CreateTime 2025/11/29 21:12:00
|
||||||
* @EditTime 2026/05/11 15:38:21
|
* @EditTime 2026/05/11 21:55:00
|
||||||
*/
|
*/
|
||||||
public class CrashHandleNotifyUtils {
|
public class CrashHandleNotifyUtils {
|
||||||
|
|
||||||
@@ -35,17 +41,18 @@ public class CrashHandleNotifyUtils {
|
|||||||
private static final int API_LEVEL_ANDROID_12 = 31;
|
private static final int API_LEVEL_ANDROID_12 = 31;
|
||||||
/** PendingIntent.FLAG_IMMUTABLE 常量值(API 31+) */
|
/** PendingIntent.FLAG_IMMUTABLE 常量值(API 31+) */
|
||||||
private static final int FLAG_IMMUTABLE = 0x00000040;
|
private static final int FLAG_IMMUTABLE = 0x00000040;
|
||||||
/** 通知内容最大行数(控制在3行,超出部分省略) */
|
|
||||||
private static final int NOTIFICATION_MAX_LINES = 3;
|
|
||||||
/** 通知摘要最大长度 */
|
/** 通知摘要最大长度 */
|
||||||
private static final int SUMMARY_MAX_LENGTH = 200;
|
private static final int SUMMARY_MAX_LENGTH = 200;
|
||||||
/** 展开按钮广播Action */
|
/** 分享日志请求码 */
|
||||||
private static final String ACTION_EXPAND = "cc.winboll.studio.libappbase.ACTION_EXPAND_CRASH";
|
private static final int REQUEST_CODE_SHARE_LOG = 0x002;
|
||||||
|
/** 缓存崩溃日志子目录 */
|
||||||
|
private static final String CRASH_LOG_CACHE_SUBDIR = "crashnotify";
|
||||||
|
/** 缓存崩溃日志文件名 */
|
||||||
|
private static final String CRASH_LOG_CACHE_FILENAME = "crash_log.txt";
|
||||||
|
|
||||||
private static Context sHostContext = null;
|
// ====================== 静态成员 ======================
|
||||||
private static String sHostPackageName = "";
|
private static String sHostPackageName = "";
|
||||||
private static String sHostAppName = "";
|
private static String sCrashLogCacheFilePath = "";
|
||||||
private static Class<?> sReportCrashActivity = null;
|
|
||||||
|
|
||||||
// ====================== 正则表达式定义 ======================
|
// ====================== 正则表达式定义 ======================
|
||||||
private static final String REGEX_EXCEPTION_TYPE = "([\\w.]+Exception|[\\w.]+Error)";
|
private static final String REGEX_EXCEPTION_TYPE = "([\\w.]+Exception|[\\w.]+Error)";
|
||||||
@@ -61,18 +68,29 @@ public class CrashHandleNotifyUtils {
|
|||||||
* @param errorLog 崩溃日志内容
|
* @param errorLog 崩溃日志内容
|
||||||
* @param reportCrashActivity 崩溃详情跳转Activity类
|
* @param reportCrashActivity 崩溃详情跳转Activity类
|
||||||
*/
|
*/
|
||||||
public static void handleUncaughtException(final Application hostApp,
|
public static void handleUncaughtException(final android.app.Application hostApp,
|
||||||
final String hostPackageName,
|
final String hostPackageName,
|
||||||
final String errorLog,
|
final String errorLog,
|
||||||
final Class<?> reportCrashActivity) {
|
final Class<?> reportCrashActivity) {
|
||||||
LogUtils.d(TAG, "handleUncaughtException 进入方法");
|
LogUtils.d(TAG, "handleUncaughtException 进入方法");
|
||||||
// 校验入参
|
|
||||||
if (hostApp == null || TextUtils.isEmpty(hostPackageName) || TextUtils.isEmpty(errorLog)) {
|
if (hostApp == null || TextUtils.isEmpty(hostPackageName) || TextUtils.isEmpty(errorLog)) {
|
||||||
LogUtils.e(TAG, "handleUncaughtException 参数为空校验不通过");
|
LogUtils.e(TAG, "handleUncaughtException 参数为空校验不通过");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
sHostPackageName = hostPackageName;
|
||||||
final String hostAppName = getHostAppName(hostApp, hostPackageName);
|
final String hostAppName = getHostAppName(hostApp, hostPackageName);
|
||||||
sendCrashNotification(hostApp, hostPackageName, hostAppName, errorLog, reportCrashActivity);
|
final String crashLogFilePath = saveCrashLogToCache(hostApp, errorLog);
|
||||||
|
if (TextUtils.isEmpty(crashLogFilePath)) {
|
||||||
|
LogUtils.e(TAG, "保存崩溃日志到缓存文件失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sCrashLogCacheFilePath = crashLogFilePath;
|
||||||
|
final Intent shareIntent = new Intent(hostApp, ShareLogActivity.class);
|
||||||
|
shareIntent.putExtra(ShareLogActivity.EXTRA_CRASH_LOG_FILEPATH, crashLogFilePath);
|
||||||
|
shareIntent.putExtra(ShareLogActivity.EXTRA_CRASH_LOG_SUBJECT, "崩溃日志");
|
||||||
|
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
final PendingIntent sharePendingIntent = createSharePendingIntent(hostApp, shareIntent);
|
||||||
|
sendCrashNotification(hostApp, hostPackageName, hostAppName, errorLog, reportCrashActivity, sharePendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,9 +99,9 @@ public class CrashHandleNotifyUtils {
|
|||||||
* @param intent 携带崩溃信息Intent
|
* @param intent 携带崩溃信息Intent
|
||||||
* @param reportCrashActivity 崩溃详情Activity
|
* @param reportCrashActivity 崩溃详情Activity
|
||||||
*/
|
*/
|
||||||
public static void handleUncaughtException(final Application hostApp,
|
public static void handleUncaughtException(final android.app.Application hostApp,
|
||||||
final Intent intent,
|
final Intent intent,
|
||||||
final Class<?> reportCrashActivity) {
|
final Class<?> reportCrashActivity) {
|
||||||
LogUtils.d(TAG, "handleUncaughtException 重载方法进入");
|
LogUtils.d(TAG, "handleUncaughtException 重载方法进入");
|
||||||
String hostPackageName = intent.getStringExtra("EXTRA_HOST_PACKAGE_NAME");
|
String hostPackageName = intent.getStringExtra("EXTRA_HOST_PACKAGE_NAME");
|
||||||
if (TextUtils.isEmpty(hostPackageName)) {
|
if (TextUtils.isEmpty(hostPackageName)) {
|
||||||
@@ -94,6 +112,16 @@ public class CrashHandleNotifyUtils {
|
|||||||
handleUncaughtException(hostApp, hostPackageName, errorLog, reportCrashActivity);
|
handleUncaughtException(hostApp, hostPackageName, errorLog, reportCrashActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源释放
|
||||||
|
* @param hostContext 宿主上下文
|
||||||
|
*/
|
||||||
|
public static void release(final Context hostContext) {
|
||||||
|
LogUtils.d(TAG, "release 执行资源释放");
|
||||||
|
sHostPackageName = "";
|
||||||
|
sCrashLogCacheFilePath = "";
|
||||||
|
}
|
||||||
|
|
||||||
// ====================== 内部工具方法 ======================
|
// ====================== 内部工具方法 ======================
|
||||||
/**
|
/**
|
||||||
* 获取宿主应用名称
|
* 获取宿主应用名称
|
||||||
@@ -104,14 +132,87 @@ public class CrashHandleNotifyUtils {
|
|||||||
private static String getHostAppName(final Context hostContext, final String hostPackageName) {
|
private static String getHostAppName(final Context hostContext, final String hostPackageName) {
|
||||||
try {
|
try {
|
||||||
return hostContext.getPackageManager()
|
return hostContext.getPackageManager()
|
||||||
.getApplicationLabel(hostContext.getPackageManager()
|
.getApplicationLabel(hostContext.getPackageManager()
|
||||||
.getApplicationInfo(hostPackageName, 0)).toString();
|
.getApplicationInfo(hostPackageName, 0)).toString();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "获取宿主应用名称失败", e);
|
LogUtils.e(TAG, "获取宿主应用名称失败", e);
|
||||||
return "未知应用";
|
return "未知应用";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存崩溃日志到缓存文件
|
||||||
|
* @param hostContext 宿主上下文
|
||||||
|
* @param crashLog 崩溃日志内容
|
||||||
|
* @return 缓存文件路径,失败返回空字符串
|
||||||
|
*/
|
||||||
|
private static String saveCrashLogToCache(final Context hostContext, final String crashLog) {
|
||||||
|
if (hostContext == null || TextUtils.isEmpty(crashLog)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
BufferedReader reader = null;
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
final File cacheDir = new File(hostContext.getCacheDir(), CRASH_LOG_CACHE_SUBDIR);
|
||||||
|
if (!cacheDir.exists()) {
|
||||||
|
cacheDir.mkdirs();
|
||||||
|
}
|
||||||
|
final File cacheFile = new File(cacheDir, CRASH_LOG_CACHE_FILENAME);
|
||||||
|
if (cacheFile.exists()) {
|
||||||
|
cacheFile.delete();
|
||||||
|
}
|
||||||
|
cacheFile.createNewFile();
|
||||||
|
fos = new FileOutputStream(cacheFile);
|
||||||
|
fos.write(crashLog.getBytes("UTF-8"));
|
||||||
|
fos.flush();
|
||||||
|
LogUtils.d(TAG, "saveCrashLogToCache 保存崩溃日志到缓存: " + cacheFile.getAbsolutePath());
|
||||||
|
return cacheFile.getAbsolutePath();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "saveCrashLogToCache 异常", e);
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try { reader.close(); } catch (Exception e) {}
|
||||||
|
}
|
||||||
|
if (fos != null) {
|
||||||
|
try { fos.close(); } catch (Exception e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取缓存的崩溃日志文件内容
|
||||||
|
* @param hostContext 宿主上下文
|
||||||
|
* @return 日志内容,失败返回空字符串
|
||||||
|
*/
|
||||||
|
private static String readCachedCrashLog(final Context hostContext) {
|
||||||
|
if (hostContext == null || TextUtils.isEmpty(sCrashLogCacheFilePath)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
final File cacheFile = new File(sCrashLogCacheFilePath);
|
||||||
|
if (!cacheFile.exists()) {
|
||||||
|
LogUtils.w(TAG, "readCachedCrashLog 缓存文件不存在");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
reader = new BufferedReader(new InputStreamReader(new FileInputStream(cacheFile), "UTF-8"));
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "readCachedCrashLog 异常", e);
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try { reader.close(); } catch (Exception e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送崩溃系统通知
|
* 发送崩溃系统通知
|
||||||
* @param hostContext 宿主上下文
|
* @param hostContext 宿主上下文
|
||||||
@@ -119,45 +220,65 @@ public class CrashHandleNotifyUtils {
|
|||||||
* @param hostAppName 宿主应用名
|
* @param hostAppName 宿主应用名
|
||||||
* @param errorLog 崩溃日志
|
* @param errorLog 崩溃日志
|
||||||
* @param reportCrashActivity 跳转Activity
|
* @param reportCrashActivity 跳转Activity
|
||||||
|
* @param sharePendingIntent 分享日志PendingIntent
|
||||||
*/
|
*/
|
||||||
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 PendingIntent sharePendingIntent) {
|
||||||
final NotificationManager notificationManager =
|
final NotificationManager notificationManager =
|
||||||
(NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
(NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
if (notificationManager == null) {
|
if (notificationManager == null) {
|
||||||
LogUtils.e(TAG, "获取NotificationManager失败");
|
LogUtils.e(TAG, "获取NotificationManager失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 8.0以上创建通知渠道
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
createCrashNotifyChannel(hostContext, notificationManager);
|
createCrashNotifyChannel(hostContext, notificationManager);
|
||||||
}
|
}
|
||||||
final PendingIntent jumpIntent = getGlobalCrashPendingIntent(hostContext,
|
final PendingIntent jumpIntent = getGlobalCrashPendingIntent(hostContext,
|
||||||
hostPackageName, errorLog, reportCrashActivity);
|
hostPackageName, errorLog, reportCrashActivity);
|
||||||
if (jumpIntent == null) {
|
if (jumpIntent == null) {
|
||||||
LogUtils.e(TAG, "构建跳转PendingIntent失败");
|
LogUtils.e(TAG, "构建跳转PendingIntent失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Notification notification = buildNotification(hostContext, hostPackageName, hostAppName, errorLog, jumpIntent);
|
final Notification notification = buildNotification(hostContext, hostPackageName, hostAppName, errorLog, jumpIntent, sharePendingIntent);
|
||||||
notificationManager.notify(CRASH_NOTIFY_ID, notification);
|
notificationManager.notify(CRASH_NOTIFY_ID, notification);
|
||||||
LogUtils.d(TAG, "崩溃通知发送成功,宿主包名:" + hostPackageName);
|
LogUtils.d(TAG, "崩溃通知发送成功,宿主包名:" + hostPackageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分享日志PendingIntent
|
||||||
|
* @param hostContext 宿主上下文
|
||||||
|
* @param shareIntent 分享意图
|
||||||
|
* @return PendingIntent实例
|
||||||
|
*/
|
||||||
|
private static PendingIntent createSharePendingIntent(final Context hostContext, final Intent shareIntent) {
|
||||||
|
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
|
||||||
|
flags |= FLAG_IMMUTABLE;
|
||||||
|
}
|
||||||
|
return PendingIntent.getActivity(
|
||||||
|
hostContext,
|
||||||
|
REQUEST_CODE_SHARE_LOG,
|
||||||
|
shareIntent,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建通知渠道(适配Android O及以上)
|
* 创建通知渠道(适配Android O及以上)
|
||||||
* @param hostContext 宿主上下文
|
* @param hostContext 宿主上下文
|
||||||
* @param notificationManager 通知管理器
|
* @param notificationManager 通知管理器
|
||||||
*/
|
*/
|
||||||
private static void createCrashNotifyChannel(final Context hostContext,
|
private static void createCrashNotifyChannel(final Context hostContext,
|
||||||
final NotificationManager notificationManager) {
|
final NotificationManager notificationManager) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
android.app.NotificationChannel channel = new android.app.NotificationChannel(
|
android.app.NotificationChannel channel = new android.app.NotificationChannel(
|
||||||
CRASH_NOTIFY_CHANNEL_ID,
|
CRASH_NOTIFY_CHANNEL_ID,
|
||||||
CRASH_NOTIFY_CHANNEL_NAME,
|
CRASH_NOTIFY_CHANNEL_NAME,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
);
|
);
|
||||||
channel.setDescription("应用崩溃通知(由 WinBoLL Studio 类库提供,点击查看详情)");
|
channel.setDescription("应用崩溃通知(由 WinBoLL Studio 类库提供,点击查看详情)");
|
||||||
notificationManager.createNotificationChannel(channel);
|
notificationManager.createNotificationChannel(channel);
|
||||||
@@ -174,9 +295,9 @@ private static void sendCrashNotification(final Context hostContext,
|
|||||||
* @return PendingIntent实例
|
* @return PendingIntent实例
|
||||||
*/
|
*/
|
||||||
private static PendingIntent getGlobalCrashPendingIntent(final Context hostContext,
|
private static PendingIntent getGlobalCrashPendingIntent(final Context hostContext,
|
||||||
final String hostPackageName,
|
final String hostPackageName,
|
||||||
final String errorLog,
|
final String errorLog,
|
||||||
final Class<?> reportCrashActivity) {
|
final Class<?> reportCrashActivity) {
|
||||||
try {
|
try {
|
||||||
final Intent crashIntent = new Intent(hostContext, reportCrashActivity);
|
final Intent crashIntent = new Intent(hostContext, reportCrashActivity);
|
||||||
crashIntent.setPackage(hostPackageName);
|
crashIntent.setPackage(hostPackageName);
|
||||||
@@ -187,11 +308,11 @@ private static void sendCrashNotification(final Context hostContext,
|
|||||||
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,
|
||||||
flags
|
flags
|
||||||
);
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.e(TAG, "构建跳转Intent异常", e);
|
LogUtils.e(TAG, "构建跳转Intent异常", e);
|
||||||
@@ -206,32 +327,36 @@ return PendingIntent.getActivity(
|
|||||||
* @param hostAppName 宿主应用名
|
* @param hostAppName 宿主应用名
|
||||||
* @param errorLog 崩溃日志
|
* @param errorLog 崩溃日志
|
||||||
* @param jumpIntent 点击跳转意图
|
* @param jumpIntent 点击跳转意图
|
||||||
|
* @param shareIntent 分享日志意图
|
||||||
* @return 构建好的Notification
|
* @return 构建好的Notification
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private static Notification buildNotification(final Context hostContext,
|
private static Notification buildNotification(final Context hostContext,
|
||||||
final String hostPackageName,
|
final String hostPackageName,
|
||||||
final String hostAppName,
|
final String hostAppName,
|
||||||
final String errorLog,
|
final String errorLog,
|
||||||
final PendingIntent jumpIntent) {
|
final PendingIntent jumpIntent,
|
||||||
|
final PendingIntent shareIntent) {
|
||||||
Notification.Builder builder = new Notification.Builder(hostContext);
|
Notification.Builder builder = new Notification.Builder(hostContext);
|
||||||
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);
|
final String briefInfo = extractBriefInfo(errorLog);
|
||||||
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
|
final Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
|
||||||
bigTextStyle.setBigContentTitle(hostAppName + " 崩溃");
|
bigTextStyle.setBigContentTitle(hostAppName + " 崩溃");
|
||||||
bigTextStyle.bigText(briefInfo);
|
bigTextStyle.bigText(briefInfo);
|
||||||
bigTextStyle.setSummaryText("点击查看详情");
|
bigTextStyle.setSummaryText("点击查看详情");
|
||||||
builder.setStyle(bigTextStyle);
|
builder.setStyle(bigTextStyle);
|
||||||
builder.setSmallIcon(hostContext.getApplicationInfo().icon)
|
builder.setSmallIcon(hostContext.getApplicationInfo().icon)
|
||||||
.setContentTitle(hostAppName + " 崩溃")
|
.setContentTitle(hostAppName + " 崩溃")
|
||||||
.setContentText(briefInfo.split("\n")[0])
|
.setContentText(briefInfo.split("\n")[0])
|
||||||
.setContentIntent(jumpIntent)
|
.setContentIntent(jumpIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setWhen(System.currentTimeMillis())
|
.setWhen(System.currentTimeMillis())
|
||||||
.setPriority(Notification.PRIORITY_DEFAULT);
|
.setPriority(Notification.PRIORITY_DEFAULT);
|
||||||
|
if (shareIntent != null) {
|
||||||
|
builder.addAction(android.R.drawable.ic_menu_send, "分享日志", shareIntent);
|
||||||
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} else {
|
} else {
|
||||||
@@ -248,10 +373,10 @@ return PendingIntent.getActivity(
|
|||||||
if (errorLog == null || errorLog.isEmpty()) {
|
if (errorLog == null || errorLog.isEmpty()) {
|
||||||
return "无崩溃日志";
|
return "无崩溃日志";
|
||||||
}
|
}
|
||||||
String brief = extractBriefInfo(errorLog);
|
final String brief = extractBriefInfo(errorLog);
|
||||||
String firstLine = brief.split("\n")[0];
|
final String firstLine = brief.split("\n")[0];
|
||||||
if (firstLine.length() > 80) {
|
if (firstLine.length() > 80) {
|
||||||
firstLine = firstLine.substring(0, 80) + "...";
|
return firstLine.substring(0, 80) + "...";
|
||||||
}
|
}
|
||||||
return firstLine;
|
return firstLine;
|
||||||
}
|
}
|
||||||
@@ -265,7 +390,7 @@ return PendingIntent.getActivity(
|
|||||||
if (crashLog == null || crashLog.isEmpty()) {
|
if (crashLog == null || crashLog.isEmpty()) {
|
||||||
return "无崩溃日志";
|
return "无崩溃日志";
|
||||||
}
|
}
|
||||||
StringBuilder brief = new StringBuilder();
|
final StringBuilder brief = new StringBuilder();
|
||||||
try {
|
try {
|
||||||
java.util.regex.Pattern exceptionPattern = java.util.regex.Pattern.compile(REGEX_EXCEPTION_TYPE);
|
java.util.regex.Pattern exceptionPattern = java.util.regex.Pattern.compile(REGEX_EXCEPTION_TYPE);
|
||||||
java.util.regex.Matcher exceptionMatcher = exceptionPattern.matcher(crashLog);
|
java.util.regex.Matcher exceptionMatcher = exceptionPattern.matcher(crashLog);
|
||||||
@@ -315,13 +440,4 @@ return PendingIntent.getActivity(
|
|||||||
}
|
}
|
||||||
return brief.toString();
|
return brief.toString();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* 资源释放预留方法
|
|
||||||
* @param hostContext 宿主上下文
|
|
||||||
*/
|
|
||||||
public static void release(final Context hostContext) {
|
|
||||||
LogUtils.d(TAG, "CrashHandleNotifyUtils 执行资源释放");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package cc.winboll.studio.libappbase.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享崩溃日志窗口类
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @CreateTime 2026/05/11 22:30:00
|
||||||
|
*/
|
||||||
|
public class ShareLogActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String TAG = "ShareLogActivity";
|
||||||
|
public static final String EXTRA_CRASH_LOG_FILEPATH = "crash_log_filepath";
|
||||||
|
public static final String EXTRA_CRASH_LOG_SUBJECT = "crash_log_subject";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Log.d(TAG, "onCreate 进入方法");
|
||||||
|
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
if (intent == null) {
|
||||||
|
Log.e(TAG, "onCreate intent 为空");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String crashLogFilePath = intent.getStringExtra(EXTRA_CRASH_LOG_FILEPATH);
|
||||||
|
if (crashLogFilePath == null || crashLogFilePath.isEmpty()) {
|
||||||
|
Log.e(TAG, "onCreate crashLogFilePath 为空");
|
||||||
|
Toast.makeText(this, "日志文件路径无效", Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String subject = intent.getStringExtra(EXTRA_CRASH_LOG_SUBJECT);
|
||||||
|
handleShareCrashLog(crashLogFilePath, subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleShareCrashLog(final String crashLogFilePath, final String subject) {
|
||||||
|
Log.d(TAG, "handleShareCrashLog crashLogFilePath = " + crashLogFilePath);
|
||||||
|
|
||||||
|
final File crashLogFile = new File(crashLogFilePath);
|
||||||
|
if (!crashLogFile.exists()) {
|
||||||
|
Log.e(TAG, "handleShareCrashLog 文件不存在");
|
||||||
|
Toast.makeText(this, "日志文件不存在", Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new InputStreamReader(new FileInputStream(crashLogFile), "UTF-8"));
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
final String logContent = sb.toString();
|
||||||
|
|
||||||
|
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, logContent);
|
||||||
|
if (subject != null && !subject.isEmpty()) {
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||||
|
} else {
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "崩溃日志");
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(Intent.createChooser(shareIntent, "分享日志到"));
|
||||||
|
Log.d(TAG, "handleShareCrashLog 分享成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "handleShareCrashLog 异常", e);
|
||||||
|
Toast.makeText(this, "分享失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try { reader.close(); } catch (Exception e) {}
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user