187 lines
8.4 KiB
Java
187 lines
8.4 KiB
Java
package cc.winboll.studio.libappbase;
|
||
|
||
import android.app.Activity;
|
||
import android.content.ClipData;
|
||
import android.content.ClipboardManager;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.content.pm.PackageManager;
|
||
import android.os.Bundle;
|
||
import android.view.Menu;
|
||
import android.view.MenuItem;
|
||
import android.widget.Toast;
|
||
import cc.winboll.studio.libappbase.R;
|
||
|
||
/**
|
||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||
* @Date 2025/11/11 19:58
|
||
* @Describe 应用异常报告观察活动窗口类
|
||
* 核心功能:应用发生未捕获崩溃时,由 CrashHandler 启动此页面,展示崩溃日志详情,
|
||
* 并提供「复制日志」「重启应用」操作入口,便于开发者定位问题和用户恢复应用
|
||
*/
|
||
public final class GlobalCrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
|
||
|
||
/** 日志标签(用于调试日志输出,唯一标识当前 Activity) */
|
||
public static final String TAG = "GlobalCrashActivity";
|
||
|
||
/** 菜单标识:复制崩溃日志(用于区分菜单项点击事件) */
|
||
private static final int MENU_ITEM_COPY = 0;
|
||
/** 菜单标识:重启应用(用于区分菜单项点击事件) */
|
||
private static final int MENU_ITEM_RESTART = 1;
|
||
|
||
/** 崩溃报告展示自定义视图 */
|
||
// 负责渲染崩溃日志文本、提供 Toolbar 容器,封装了日志展示和菜单样式控制逻辑
|
||
private GlobalCrashReportView mCrashReportView;
|
||
|
||
/** 崩溃日志文本内容 */
|
||
// 从 CrashHandler 通过 Intent 传递过来,包含异常堆栈、设备信息等完整崩溃数据
|
||
private String mCrashLog;
|
||
|
||
/**
|
||
* Activity 创建时初始化(生命周期核心方法,仅执行一次)
|
||
* @param savedInstanceState 保存的实例状态(崩溃页面无需恢复状态,此处仅作兼容)
|
||
*/
|
||
@Override
|
||
protected void onCreate(Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
|
||
// 初始化崩溃安全防护机制
|
||
// 作用:防止应用重启后短时间内再次崩溃,由 CrashHandler 内部实现防护逻辑
|
||
CrashHandler.AppCrashSafetyWire.getInstance()
|
||
.postResumeCrashSafetyWireHandler(getApplicationContext());
|
||
|
||
// 从 Intent 中获取崩溃日志数据(EXTRA_CRASH_INFO 为 CrashHandler 定义的常量键)
|
||
mCrashLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_LOG);
|
||
|
||
// 设置当前 Activity 的布局文件(展示崩溃报告的 UI 结构)
|
||
setContentView(R.layout.activity_globalcrash);
|
||
|
||
// 初始化崩溃报告展示视图(通过布局 ID 找到自定义 View 实例)
|
||
mCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1);
|
||
// 将崩溃日志设置到视图中,由自定义 View 负责排版和显示
|
||
mCrashReportView.setReport(mCrashLog);
|
||
|
||
// 设置页面的 ActionBar(复用自定义 View 中的 Toolbar 作为系统 ActionBar)
|
||
setActionBar(mCrashReportView.getToolbar());
|
||
|
||
// 配置 ActionBar 标题和副标题(非空判断避免空指针异常)
|
||
if (getActionBar() != null) {
|
||
// 设置标题:使用 CrashHandler 中定义的统一标题(如 "应用崩溃报告")
|
||
getActionBar().setTitle(CrashHandler.TITTLE);
|
||
// 设置副标题:显示当前应用名称(从全局 Application 工具方法获取)
|
||
getActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重写返回键点击事件
|
||
* 逻辑:点击手机返回键时,直接重启应用(而非返回上一页,因崩溃后上一页状态可能异常)
|
||
*/
|
||
@Override
|
||
public void onBackPressed() {
|
||
restartApp();
|
||
}
|
||
|
||
/**
|
||
* 重启当前应用(核心工具方法)
|
||
* 实现逻辑:
|
||
* 1. 获取应用的启动意图(默认启动 AndroidManifest 中配置的主 Activity)
|
||
* 2. 设置意图标志,清除原有任务栈,避免残留异常页面
|
||
* 3. 启动主 Activity 并终止当前进程,确保应用完全重启
|
||
*/
|
||
private void restartApp() {
|
||
// 获取 PackageManager 实例(用于获取应用相关信息和意图)
|
||
PackageManager packageManager = getPackageManager();
|
||
// 获取应用的启动意图(参数为当前应用包名,返回主 Activity 的意图)
|
||
Intent launchIntent = packageManager.getLaunchIntentForPackage(getPackageName());
|
||
|
||
if (launchIntent != null) {
|
||
// 设置意图标志:
|
||
// FLAG_ACTIVITY_NEW_TASK:创建新的任务栈启动 Activity
|
||
// FLAG_ACTIVITY_CLEAR_TOP:清除目标 Activity 之上的所有 Activity
|
||
// FLAG_ACTIVITY_CLEAR_TASK:清除当前任务栈中的所有 Activity
|
||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||
| Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||
// 启动应用主 Activity
|
||
startActivity(launchIntent);
|
||
}
|
||
|
||
// 关闭当前崩溃报告页面
|
||
finish();
|
||
// 终止当前应用进程(确保释放所有资源,避免内存泄漏)
|
||
android.os.Process.killProcess(android.os.Process.myPid());
|
||
// 强制退出虚拟机(彻底终止应用,防止残留线程继续运行)
|
||
System.exit(0);
|
||
}
|
||
|
||
/**
|
||
* 菜单项点击事件回调(实现 MenuItem.OnMenuItemClickListener 接口)
|
||
* @param item 被点击的菜单项实例
|
||
* @return boolean:true 表示事件已消费,不再向下传递;false 表示未消费
|
||
*/
|
||
@Override
|
||
public boolean onMenuItemClick(MenuItem item) {
|
||
// 根据菜单项 ID 判断点击的是哪个功能
|
||
switch (item.getItemId()) {
|
||
case MENU_ITEM_COPY:
|
||
// 点击「复制」菜单,执行复制崩溃日志到剪贴板
|
||
copyCrashLogToClipboard();
|
||
break;
|
||
case MENU_ITEM_RESTART:
|
||
// 点击「重启」菜单:先恢复崩溃防护机制到最大等级,再重启应用
|
||
CrashHandler.AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||
restartApp();
|
||
break;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 创建页面顶部菜单(ActionBar 菜单)
|
||
* @param menu 菜单容器,用于添加菜单项
|
||
* @return boolean:true 表示显示菜单;false 表示不显示
|
||
*/
|
||
@Override
|
||
public boolean onCreateOptionsMenu(Menu menu) {
|
||
// 添加「复制」菜单项:
|
||
// 参数说明:菜单组 ID(0 表示默认组)、菜单项 ID(MENU_ITEM_COPY)、排序号(0)、菜单文本("Copy")
|
||
// setOnMenuItemClickListener(this):绑定点击事件到当前 Activity
|
||
// setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM):有空间时显示在 ActionBar 上,否则放入溢出菜单
|
||
menu.add(0, MENU_ITEM_COPY, 0, "Copy")
|
||
.setOnMenuItemClickListener(this)
|
||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||
|
||
// 添加「重启」菜单项(参数含义同上)
|
||
menu.add(0, MENU_ITEM_RESTART, 0, "Restart")
|
||
.setOnMenuItemClickListener(this)
|
||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||
|
||
// 调用自定义视图的方法,更新菜单文字样式(如颜色、字体大小等,由自定义 View 内部实现)
|
||
mCrashReportView.updateMenuStyle();
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 将崩溃日志复制到系统剪贴板(工具方法)
|
||
* 功能:用户点击复制菜单后,将完整崩溃日志存入剪贴板,方便粘贴到聊天工具或文档中
|
||
*/
|
||
private void copyCrashLogToClipboard() {
|
||
// 获取系统剪贴板服务(需通过 getSystemService 方法获取)
|
||
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||
|
||
// 创建剪贴板数据:
|
||
// 参数 1:标签(用于标识剪贴板内容来源,此处用应用包名)
|
||
// 参数 2:实际复制的文本内容(崩溃日志)
|
||
ClipData clipData = ClipData.newPlainText(getPackageName(), mCrashLog);
|
||
|
||
// 将数据设置到剪贴板(完成复制操作)
|
||
clipboardManager.setPrimaryClip(clipData);
|
||
|
||
// 显示复制成功的 Toast 提示(告知用户操作结果)
|
||
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
|
||
}
|
||
}
|
||
|