20251130_014538_531
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sat Nov 29 13:38:56 GMT 2025
|
#Sat Nov 29 17:16:46 GMT 2025
|
||||||
stageCount=3
|
stageCount=3
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.11
|
baseVersion=15.11
|
||||||
publishVersion=15.11.2
|
publishVersion=15.11.2
|
||||||
buildCount=2
|
buildCount=4
|
||||||
baseBetaVersion=15.11.3
|
baseBetaVersion=15.11.3
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class App extends GlobalApplication {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate(); // 调用父类初始化逻辑(如基础库配置、全局上下文设置)
|
super.onCreate(); // 调用父类初始化逻辑(如基础库配置、全局上下文设置)
|
||||||
setIsDebugging(BuildConfig.DEBUG);
|
setIsDebugging(!BuildConfig.DEBUG);
|
||||||
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
|
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
|
||||||
ToastUtils.init(getApplicationContext());
|
ToastUtils.init(getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sat Nov 29 13:38:56 GMT 2025
|
#Sat Nov 29 17:16:46 GMT 2025
|
||||||
stageCount=3
|
stageCount=3
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.11
|
baseVersion=15.11
|
||||||
publishVersion=15.11.2
|
publishVersion=15.11.2
|
||||||
buildCount=2
|
buildCount=4
|
||||||
baseBetaVersion=15.11.3
|
baseBetaVersion=15.11.3
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="cc.winboll.studio.libappbase">
|
package="cc.winboll.studio.libappbase">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".CrashHandler$CrashActivity"
|
android:name=".CrashHandler$CrashActivity"
|
||||||
android:label="CrashActivity"
|
android:label="CrashActivity"
|
||||||
|
|||||||
@@ -121,6 +121,18 @@ public class ToastUtils {
|
|||||||
LogUtils.d(TAG, "ToastUtils 初始化完成,上下文已设置");
|
LogUtils.d(TAG, "ToastUtils 初始化完成,上下文已设置");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================================== 新增:isInited() 方法 =====================================
|
||||||
|
/**
|
||||||
|
* 判断 ToastUtils 是否已初始化(供外部调用,如 CrashHandleNotifyUtils 中的复制提示)
|
||||||
|
* @return true:已初始化(可正常显示吐司);false:未初始化/已释放(无法正常显示)
|
||||||
|
*/
|
||||||
|
public static boolean isInited() {
|
||||||
|
ToastUtils instance = getInstance();
|
||||||
|
// 双重校验:1. 未释放 2. 上下文已设置(确保初始化完成)
|
||||||
|
return !instance.isReleased && instance.mContext != null;
|
||||||
|
}
|
||||||
|
// ===================================== 新增结束 =====================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外部接口:显示短时长吐司
|
* 外部接口:显示短时长吐司
|
||||||
* @param message 吐司内容
|
* @param message 吐司内容
|
||||||
@@ -243,7 +255,6 @@ public class ToastUtils {
|
|||||||
instance.mWorkerThread.join(1000);
|
instance.mWorkerThread.join(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
//LogUtils.e(TAG, "线程退出异常", e);
|
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
instance.mWorkerThread = null;
|
instance.mWorkerThread = null;
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ 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.content.IntentFilter;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
* @Date 2025/11/29 21:12
|
* @Date 2025/11/29 21:12
|
||||||
* @Describe 应用崩溃处理通知实用工具集
|
* @Describe 应用崩溃处理通知实用工具集
|
||||||
* 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏,方便用户查看崩溃信息
|
* 核心功能:应用崩溃时捕获错误日志,发送通知到系统通知栏(3行内容省略+复制按钮),点击复制按钮可将完整崩溃日志复制到剪贴板
|
||||||
*/
|
*/
|
||||||
public class CrashHandleNotifyUtils {
|
public class CrashHandleNotifyUtils {
|
||||||
|
|
||||||
@@ -30,11 +32,22 @@ 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+),避免依赖高版本 SDK */
|
/** PendingIntent.FLAG_IMMUTABLE 常量值(API 31+),避免依赖高版本 SDK */
|
||||||
private static final int FLAG_IMMUTABLE = 0x00000040;
|
private static final int FLAG_IMMUTABLE = 0x00000040;
|
||||||
|
/** 通知内容最大行数(控制在3行,超出部分省略) */
|
||||||
|
private static final int NOTIFICATION_MAX_LINES = 3;
|
||||||
|
/** 复制按钮 Action(用于区分通知按钮点击事件) */
|
||||||
|
private static final String ACTION_COPY_CRASH_LOG = "cc.winboll.studio.action.COPY_CRASH_LOG";
|
||||||
|
/** 复制按钮请求码(区分多个 PendingIntent) */
|
||||||
|
private static final int REQUEST_CODE_COPY = 0x002;
|
||||||
|
/** 复制成功提示文本 */
|
||||||
|
private static final String COPY_SUCCESS_TIP = "崩溃日志已复制到剪贴板";
|
||||||
|
|
||||||
|
// 静态广播接收器(避免重复注册,确保崩溃后仍能接收点击事件)
|
||||||
|
private static CopyCrashLogReceiver sCopyReceiver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理未捕获异常(核心方法)
|
* 处理未捕获异常(核心方法)
|
||||||
* 1. 提取应用名称和崩溃日志;
|
* 1. 提取应用名称和崩溃日志;
|
||||||
* 2. 创建并发送系统通知(标题:应用名称,内容:崩溃日志);
|
* 2. 创建并发送系统通知(3行内容省略+复制按钮);
|
||||||
* 3. 兼容 Android 8.0+ 通知渠道机制,适配低版本系统。
|
* 3. 兼容 Android 8.0+ 通知渠道机制,适配低版本系统。
|
||||||
* @param app 应用全局 Application 实例(用于获取上下文、应用信息)
|
* @param app 应用全局 Application 实例(用于获取上下文、应用信息)
|
||||||
* @param intent 存储崩溃信息的意图(extra 中携带崩溃日志)
|
* @param intent 存储崩溃信息的意图(extra 中携带崩溃日志)
|
||||||
@@ -51,7 +64,10 @@ public class CrashHandleNotifyUtils {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 发送崩溃通知到通知栏
|
// 3. 注册静态广播接收器(仅注册一次,确保崩溃后能接收点击事件)
|
||||||
|
registerCopyReceiver(app);
|
||||||
|
|
||||||
|
// 4. 发送崩溃通知到通知栏(3行省略+复制按钮)
|
||||||
sendCrashNotification(app, appName, errorLog);
|
sendCrashNotification(app, appName, errorLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +90,7 @@ public class CrashHandleNotifyUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送崩溃通知到系统通知栏
|
* 发送崩溃通知到系统通知栏(核心修改:3行内容+复制按钮)
|
||||||
* @param context 上下文(Application 实例,确保后台也能发送)
|
* @param context 上下文(Application 实例,确保后台也能发送)
|
||||||
* @param title 通知标题(应用名称)
|
* @param title 通知标题(应用名称)
|
||||||
* @param content 通知内容(崩溃日志)
|
* @param content 通知内容(崩溃日志)
|
||||||
@@ -92,11 +108,12 @@ public class CrashHandleNotifyUtils {
|
|||||||
createCrashNotifyChannel(notificationManager);
|
createCrashNotifyChannel(notificationManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 构建通知意图(点击通知时可跳转,此处默认跳转应用主界面,可自定义)
|
// 3. 构建通知意图(点击通知跳转主界面 + 点击复制按钮复制日志)
|
||||||
PendingIntent pendingIntent = getNotificationPendingIntent(context);
|
PendingIntent launchPendingIntent = getLaunchPendingIntent(context); // 主界面跳转意图
|
||||||
|
PendingIntent copyPendingIntent = getCopyPendingIntent(context, content); // 复制日志意图
|
||||||
|
|
||||||
// 4. 构建通知实例(兼容低版本,使用 Notification.Builder 构建)
|
// 4. 构建通知实例(核心修复:3行内容省略+复制按钮,修复setLines报错)
|
||||||
Notification notification = buildNotification(context, title, content, pendingIntent);
|
Notification notification = buildNotification(context, title, content, launchPendingIntent, copyPendingIntent);
|
||||||
|
|
||||||
// 5. 发送通知(指定通知ID,重复发送同ID会覆盖原通知)
|
// 5. 发送通知(指定通知ID,重复发送同ID会覆盖原通知)
|
||||||
notificationManager.notify(CRASH_NOTIFY_ID, notification);
|
notificationManager.notify(CRASH_NOTIFY_ID, notification);
|
||||||
@@ -118,7 +135,7 @@ public class CrashHandleNotifyUtils {
|
|||||||
NotificationManager.IMPORTANCE_DEFAULT // 重要性:默认(不会弹窗,有声音提示)
|
NotificationManager.IMPORTANCE_DEFAULT // 重要性:默认(不会弹窗,有声音提示)
|
||||||
);
|
);
|
||||||
// 可选:设置渠道描述(用户在设置中可见)
|
// 可选:设置渠道描述(用户在设置中可见)
|
||||||
channel.setDescription("用于显示应用崩溃信息,帮助定位问题");
|
channel.setDescription("用于显示应用崩溃信息,支持复制日志");
|
||||||
// 注册通知渠道到系统
|
// 注册通知渠道到系统
|
||||||
notificationManager.createNotificationChannel(channel);
|
notificationManager.createNotificationChannel(channel);
|
||||||
LogUtils.d(TAG, "崩溃通知渠道创建成功:" + CRASH_NOTIFY_CHANNEL_ID);
|
LogUtils.d(TAG, "崩溃通知渠道创建成功:" + CRASH_NOTIFY_CHANNEL_ID);
|
||||||
@@ -126,12 +143,11 @@ public class CrashHandleNotifyUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建通知点击意图(PendingIntent)
|
* 构建通知点击跳转意图(跳转应用主界面)
|
||||||
* 点击通知后跳转应用主界面(可根据需求修改为跳转崩溃日志详情页)
|
|
||||||
* @param context 上下文
|
* @param context 上下文
|
||||||
* @return 封装好的 PendingIntent(用于通知点击跳转)
|
* @return 主界面跳转 PendingIntent
|
||||||
*/
|
*/
|
||||||
private static PendingIntent getNotificationPendingIntent(Context context) {
|
private static PendingIntent getLaunchPendingIntent(Context context) {
|
||||||
// 1. 获取应用主界面 Intent(从包名启动默认 launcher Activity)
|
// 1. 获取应用主界面 Intent(从包名启动默认 launcher Activity)
|
||||||
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(
|
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(
|
||||||
context.getPackageName()
|
context.getPackageName()
|
||||||
@@ -141,55 +157,223 @@ public class CrashHandleNotifyUtils {
|
|||||||
launchIntent = new Intent();
|
launchIntent = new Intent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 构建 PendingIntent(延迟执行的意图,FLAG_UPDATE_CURRENT 表示更新已存在的意图)
|
// 2. 构建 PendingIntent(延迟执行的意图)
|
||||||
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
// 适配 Android 12+(API 31+):添加 FLAG_IMMUTABLE 避免安全警告(用常量值替代高版本 API)
|
// 适配 Android 12+(API 31+):添加 FLAG_IMMUTABLE 避免安全警告
|
||||||
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(
|
||||||
context,
|
context,
|
||||||
0, // 请求码(可忽略,用于区分多个 PendingIntent)
|
0, // 请求码(可忽略)
|
||||||
launchIntent,
|
launchIntent,
|
||||||
flags
|
flags
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建通知实例(兼容 Android 4.0+ 所有版本)
|
* 构建复制按钮意图(点击复制崩溃日志到剪贴板)
|
||||||
* @param context 上下文
|
* @param context 上下文
|
||||||
* @param title 通知标题
|
* @param errorLog 崩溃日志(需要复制的内容)
|
||||||
* @param content 通知内容
|
* @return 复制日志 PendingIntent
|
||||||
* @param pendingIntent 通知点击意图
|
*/
|
||||||
|
private static PendingIntent getCopyPendingIntent(Context context, String errorLog) {
|
||||||
|
// 1. 构建复制日志的隐式意图(指定 Action,用于 BroadcastReceiver 接收)
|
||||||
|
Intent copyIntent = new Intent(ACTION_COPY_CRASH_LOG);
|
||||||
|
copyIntent.putExtra("EXTRA_CRASH_LOG", errorLog); // 携带崩溃日志
|
||||||
|
copyIntent.setPackage(context.getPackageName()); // 限制仅当前应用接收,避免安全问题
|
||||||
|
|
||||||
|
// 2. 构建 PendingIntent(使用广播类型,崩溃后仍能触发)
|
||||||
|
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
|
||||||
|
flags |= FLAG_IMMUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
REQUEST_CODE_COPY, // 唯一请求码,区分主界面意图
|
||||||
|
copyIntent,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册静态广播接收器(仅注册一次,确保崩溃后能接收点击事件)
|
||||||
|
* 解决动态广播崩溃后被销毁的问题
|
||||||
|
* @param context 上下文(Application 实例)
|
||||||
|
*/
|
||||||
|
private static void registerCopyReceiver(Context context) {
|
||||||
|
// 避免重复注册(静态接收器仅注册一次)
|
||||||
|
if (sCopyReceiver != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建广播过滤器(仅接收复制日志的 Action)
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_COPY_CRASH_LOG);
|
||||||
|
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
|
||||||
|
|
||||||
|
// 初始化静态广播接收器
|
||||||
|
sCopyReceiver = new CopyCrashLogReceiver();
|
||||||
|
// 注册广播(使用 Application 上下文,确保生命周期与应用一致)
|
||||||
|
context.registerReceiver(sCopyReceiver, filter);
|
||||||
|
LogUtils.d(TAG, "复制日志广播接收器注册成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静态广播接收器(处理复制按钮点击事件)
|
||||||
|
* 静态内部类避免内存泄漏,且崩溃后仍能接收系统发送的广播
|
||||||
|
*/
|
||||||
|
private static class CopyCrashLogReceiver extends android.content.BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
// 验证 Action,确保是复制日志的点击事件
|
||||||
|
if (ACTION_COPY_CRASH_LOG.equals(intent.getAction())) {
|
||||||
|
// 从意图中获取完整崩溃日志
|
||||||
|
String crashLog = intent.getStringExtra("EXTRA_CRASH_LOG");
|
||||||
|
if (crashLog != null && !crashLog.isEmpty()) {
|
||||||
|
// 复制日志到剪贴板
|
||||||
|
copyTextToClipboard(context, "崩溃日志", crashLog);
|
||||||
|
// 复制成功后显示提示(可选,提升用户体验)
|
||||||
|
showCopySuccessTip(context);
|
||||||
|
LogUtils.d(TAG, "崩溃日志复制成功,长度:" + crashLog.length() + "字符");
|
||||||
|
} else {
|
||||||
|
LogUtils.e(TAG, "复制崩溃日志失败:日志为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制文本到系统剪贴板(修复适配逻辑,确保全版本可用)
|
||||||
|
* @param context 上下文
|
||||||
|
* @param label 剪贴板文本标签(用户不可见,用于区分剪贴板内容)
|
||||||
|
* @param text 需要复制的文本(崩溃日志)
|
||||||
|
*/
|
||||||
|
private static void copyTextToClipboard(Context context, String label, String text) {
|
||||||
|
try {
|
||||||
|
// 适配 Android 11+(API 30+)剪贴板 API,兼容低版本
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
android.content.ClipData clipData = android.content.ClipData.newPlainText(label, text);
|
||||||
|
clipboard.setPrimaryClip(clipData);
|
||||||
|
} else {
|
||||||
|
// 低版本剪贴板 API(Android 10 及以下)
|
||||||
|
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setText(text);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "复制文本到剪贴板失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示复制成功提示(Toast,提升用户体验)
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
private static void showCopySuccessTip(Context context) {
|
||||||
|
try {
|
||||||
|
// 若项目中 ToastUtils 支持后台显示,用 ToastUtils;否则用系统 Toast
|
||||||
|
if (cc.winboll.studio.libappbase.ToastUtils.isInited()) {
|
||||||
|
cc.winboll.studio.libappbase.ToastUtils.show(COPY_SUCCESS_TIP);
|
||||||
|
} else {
|
||||||
|
// 系统 Toast 适配(确保后台能显示)
|
||||||
|
android.widget.Toast.makeText(context, COPY_SUCCESS_TIP, android.widget.Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "显示复制成功提示失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建通知实例(核心修复:3行内容省略+复制按钮,修复setLines报错)
|
||||||
|
* @param context 上下文
|
||||||
|
* @param title 通知标题(应用名称)
|
||||||
|
* @param content 通知内容(崩溃日志)
|
||||||
|
* @param launchPendingIntent 通知点击跳转意图
|
||||||
|
* @param copyPendingIntent 复制按钮点击意图
|
||||||
* @return 构建完成的 Notification 对象
|
* @return 构建完成的 Notification 对象
|
||||||
*/
|
*/
|
||||||
private static Notification buildNotification(Context context, String title, String content, PendingIntent pendingIntent) {
|
private static Notification buildNotification(Context context, String title, String content, PendingIntent launchPendingIntent, PendingIntent copyPendingIntent) {
|
||||||
// 兼容 Android 8.0+:指定通知渠道ID
|
// 兼容 Android 8.0+:指定通知渠道ID
|
||||||
Notification.Builder builder = new Notification.Builder(context);
|
Notification.Builder builder = new Notification.Builder(context);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置通知核心参数
|
// 核心修复1:用BigTextStyle控制“默认3行省略,下拉显示完整”
|
||||||
builder
|
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
|
||||||
.setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,从应用图标获取)
|
bigTextStyle.setSummaryText("日志已省略,下拉查看完整内容"); // 底部省略提示
|
||||||
.setContentTitle(title) // 通知标题(应用名称)
|
bigTextStyle.bigText(content); // 完整日志(下拉时显示)
|
||||||
.setContentText(content) // 通知内容(崩溃日志)
|
bigTextStyle.setBigContentTitle(title); // 下拉后的标题(与主标题一致)
|
||||||
.setContentIntent(pendingIntent) // 通知点击意图
|
builder.setStyle(bigTextStyle);
|
||||||
.setAutoCancel(true) // 点击通知后自动取消
|
|
||||||
.setWhen(System.currentTimeMillis()) // 通知创建时间(当前时间)
|
|
||||||
.setPriority(Notification.PRIORITY_DEFAULT); // 通知优先级(默认)
|
|
||||||
|
|
||||||
// 可选:长文本适配(当崩溃日志过长时,显示完整文本)
|
// 核心修改2:添加复制按钮(Android 4.1+ 支持通知按钮)
|
||||||
if (content.length() > 100) { // 超过100字符时,设置长文本样式
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
|
// 复制按钮:自定义图标+文本+点击意图(确保图标存在)
|
||||||
bigTextStyle.bigText(content); // 显示完整崩溃日志
|
builder.addAction(
|
||||||
builder.setStyle(bigTextStyle);
|
R.drawable.ic_content_copy, // 自定义复制图标(需确保drawable目录下存在,否则替换为系统图标)
|
||||||
|
"复制日志", // 按钮文本
|
||||||
|
copyPendingIntent // 按钮点击意图(绑定复制广播)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建通知并返回(getNotification() 兼容低版本,build() 是 Android 4.1+ 方法)
|
// 配置通知核心参数(移除setLines,避免报错)
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ? builder.build() : builder.getNotification();
|
builder
|
||||||
|
.setSmallIcon(context.getApplicationInfo().icon) // 通知小图标(必需,否则通知不显示)
|
||||||
|
.setContentTitle(title) // 通知主标题(应用名称)
|
||||||
|
.setContentText(getShortContent(content)) // 核心:3行内缩略文本
|
||||||
|
.setContentIntent(launchPendingIntent) // 通知主体点击跳转主界面
|
||||||
|
.setAutoCancel(true) // 点击通知后自动取消
|
||||||
|
.setWhen(System.currentTimeMillis()) // 通知创建时间
|
||||||
|
.setPriority(Notification.PRIORITY_DEFAULT); // 通知优先级
|
||||||
|
|
||||||
|
// 适配 Android 4.1+:确保文本显示正常
|
||||||
|
// 构建通知并返回(兼容低版本,区分 API 等级避免报错)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
return builder.build();
|
||||||
|
} else {
|
||||||
|
// Android 4.0 及以下版本,使用 getNotification() 方法
|
||||||
|
return builder.getNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法:截取日志文本,确保显示在3行内(按字符数估算,适配大多数设备)
|
||||||
|
* 一行约20-30字符,3行约80字符(留冗余,取80字符,超出加省略号)
|
||||||
|
* @param content 完整崩溃日志
|
||||||
|
* @return 3行内的缩略文本
|
||||||
|
*/
|
||||||
|
private static String getShortContent(String content) {
|
||||||
|
if (content == null || content.isEmpty()) {
|
||||||
|
return "无崩溃日志";
|
||||||
|
}
|
||||||
|
// 估算3行字符数(80字符,可根据设备屏幕调整,避免因字符过长导致换行超3行)
|
||||||
|
int maxLength = 80;
|
||||||
|
if (content.length() <= maxLength) {
|
||||||
|
return content; // 不足3行,直接返回完整文本
|
||||||
|
} else {
|
||||||
|
// 超出3行,截取前80字符并加省略号(确保视觉上仅显示3行)
|
||||||
|
return content.substring(0, maxLength) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放资源(可选,在 Application 销毁时调用,避免内存泄漏)
|
||||||
|
* @param context 上下文(Application 实例)
|
||||||
|
*/
|
||||||
|
public static void release(Context context) {
|
||||||
|
// 注销静态广播接收器,避免内存泄漏
|
||||||
|
if (sCopyReceiver != null && context != null) {
|
||||||
|
try {
|
||||||
|
context.unregisterReceiver(sCopyReceiver);
|
||||||
|
sCopyReceiver = null;
|
||||||
|
LogUtils.d(TAG, "复制日志广播接收器已注销");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "注销广播接收器失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
libappbase/src/main/res/drawable/ic_content_copy.xml
Normal file
11
libappbase/src/main/res/drawable/ic_content_copy.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ff000000"
|
||||||
|
android:pathData="M19,21H8V7H19M19,5H8A2,2 0,0 0,6 7V21A2,2 0,0 0,8 23H19A2,2 0,0 0,21 21V7A2,2 0,0 0,19 5M16,1H4A2,2 0,0 0,2 3V17H4V3H16V1Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user