20260511_152720_122
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 14:45:59 HKT 2026
|
#Mon May 11 07:23:43 GMT 2026
|
||||||
stageCount=6
|
stageCount=6
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.20
|
baseVersion=15.20
|
||||||
publishVersion=15.20.5
|
publishVersion=15.20.5
|
||||||
buildCount=0
|
buildCount=4
|
||||||
baseBetaVersion=15.20.6
|
baseBetaVersion=15.20.6
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public class App extends GlobalApplication {
|
|||||||
if (isDebugging() != true) {
|
if (isDebugging() != true) {
|
||||||
setIsDebugging(BuildConfig.DEBUG);
|
setIsDebugging(BuildConfig.DEBUG);
|
||||||
}
|
}
|
||||||
|
// release 版调试码
|
||||||
|
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
|
||||||
#Mon May 11 14:45:48 HKT 2026
|
#Mon May 11 07:23:43 GMT 2026
|
||||||
stageCount=6
|
stageCount=6
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.20
|
baseVersion=15.20
|
||||||
publishVersion=15.20.5
|
publishVersion=15.20.5
|
||||||
buildCount=0
|
buildCount=4
|
||||||
baseBetaVersion=15.20.6
|
baseBetaVersion=15.20.6
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ public final class CrashHandler {
|
|||||||
* 检查当前保险丝是否有效(防护未熔断)
|
* 检查当前保险丝是否有效(防护未熔断)
|
||||||
* @return true:有效(等级 1~2);false:已熔断
|
* @return true:有效(等级 1~2);false:已熔断
|
||||||
*/
|
*/
|
||||||
boolean isAppCrashSafetyWireOK() {
|
public boolean isAppCrashSafetyWireOK() {
|
||||||
LogUtils.d(TAG, "isAppCrashSafetyWireOK()");
|
LogUtils.d(TAG, "isAppCrashSafetyWireOK()");
|
||||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
currentSafetyLevel = loadCurrentSafetyLevel();
|
||||||
return isSafetyWireWorking(currentSafetyLevel);
|
return isSafetyWireWorking(currentSafetyLevel);
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
package cc.winboll.studio.libappbase;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
|
||||||
* @CreateTime 2026-05-11 13:28:00
|
|
||||||
* @EditTime 2026-05-11 13:30:45
|
|
||||||
* @Describe 应用崩溃保险丝单例管理类
|
|
||||||
* 限制短时间内应用重复崩溃,通过分级熔断机制控制崩溃页面跳转策略;
|
|
||||||
* 防护等级区间 1~2,每次崩溃自动降级,等级溢出则判定为彻底熔断;
|
|
||||||
* 支持本地文件持久化等级、延迟自动恢复、手动重置防护等级能力。
|
|
||||||
*/
|
|
||||||
public final class GlobalAppCrashSafetyWire {
|
|
||||||
|
|
||||||
/** 日志标识标签 */
|
|
||||||
public static final String TAG = "GlobalAppCrashSafetyWire";
|
|
||||||
/** 最低防护等级阈值 */
|
|
||||||
private static final int _MINI = 1;
|
|
||||||
/** 最高防护等级阈值 */
|
|
||||||
private static final int _MAX = 2;
|
|
||||||
|
|
||||||
/** 单例实例对象 */
|
|
||||||
private static volatile GlobalAppCrashSafetyWire _AppCrashSafetyWire;
|
|
||||||
/** 当前防护熔断等级 */
|
|
||||||
private volatile Integer currentSafetyLevel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 私有构造方法
|
|
||||||
* 单例禁止外部实例化,初始化加载本地持久化防护等级
|
|
||||||
*/
|
|
||||||
private GlobalAppCrashSafetyWire() {
|
|
||||||
LogUtils.d(TAG, "构造方法执行:初始化崩溃保险丝实例");
|
|
||||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单例对象
|
|
||||||
* @return GlobalAppCrashSafetyWire 单例
|
|
||||||
*/
|
|
||||||
public static synchronized GlobalAppCrashSafetyWire getInstance() {
|
|
||||||
if (_AppCrashSafetyWire == null) {
|
|
||||||
_AppCrashSafetyWire = new GlobalAppCrashSafetyWire();
|
|
||||||
}
|
|
||||||
return _AppCrashSafetyWire;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前防护等级
|
|
||||||
* @param currentSafetyLevel 待设置的防护等级
|
|
||||||
*/
|
|
||||||
public void setCurrentSafetyLevel(final int currentSafetyLevel) {
|
|
||||||
this.currentSafetyLevel = currentSafetyLevel;
|
|
||||||
LogUtils.d(TAG, "setCurrentSafetyLevel:设置等级 = " + currentSafetyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前内存中防护等级
|
|
||||||
* @return 当前防护等级
|
|
||||||
*/
|
|
||||||
public int getCurrentSafetyLevel() {
|
|
||||||
return currentSafetyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存防护等级到本地文件持久化
|
|
||||||
* @param currentSafetyLevel 待保存防护等级
|
|
||||||
*/
|
|
||||||
public void saveCurrentSafetyLevel(final int currentSafetyLevel) {
|
|
||||||
LogUtils.d(TAG, "saveCurrentSafetyLevel:开始持久化等级 = " + currentSafetyLevel);
|
|
||||||
this.currentSafetyLevel = currentSafetyLevel;
|
|
||||||
|
|
||||||
ObjectOutputStream oos = null;
|
|
||||||
try {
|
|
||||||
oos = new ObjectOutputStream(new FileOutputStream(CrashHandler._CrashCountFilePath));
|
|
||||||
oos.writeInt(currentSafetyLevel);
|
|
||||||
oos.flush();
|
|
||||||
LogUtils.d(TAG, "saveCurrentSafetyLevel:写入文件成功,等级 = " + currentSafetyLevel);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogUtils.e(TAG, "saveCurrentSafetyLevel:写入文件异常", e);
|
|
||||||
} finally {
|
|
||||||
if (oos != null) {
|
|
||||||
try {
|
|
||||||
oos.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogUtils.e(TAG, "saveCurrentSafetyLevel:关闭流异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从本地文件读取防护等级
|
|
||||||
* @return 加载后的防护等级
|
|
||||||
*/
|
|
||||||
public int loadCurrentSafetyLevel() {
|
|
||||||
LogUtils.d(TAG, "loadCurrentSafetyLevel:加载本地防护等级");
|
|
||||||
File file = new File(CrashHandler._CrashCountFilePath);
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
currentSafetyLevel = _MAX;
|
|
||||||
LogUtils.d(TAG, "loadCurrentSafetyLevel:文件不存在,初始化为最高等级 = " + _MAX);
|
|
||||||
saveCurrentSafetyLevel(currentSafetyLevel);
|
|
||||||
return currentSafetyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectInputStream ois = null;
|
|
||||||
try {
|
|
||||||
ois = new ObjectInputStream(new FileInputStream(CrashHandler._CrashCountFilePath));
|
|
||||||
currentSafetyLevel = ois.readInt();
|
|
||||||
LogUtils.d(TAG, "loadCurrentSafetyLevel:读取文件成功,当前等级 = " + currentSafetyLevel);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogUtils.e(TAG, "loadCurrentSafetyLevel:读取文件异常", e);
|
|
||||||
currentSafetyLevel = _MAX;
|
|
||||||
} finally {
|
|
||||||
if (ois != null) {
|
|
||||||
try {
|
|
||||||
ois.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LogUtils.e(TAG, "loadCurrentSafetyLevel:关闭流异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentSafetyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行一次保险丝熔断降级
|
|
||||||
* @return 降级后是否仍在有效防护区间
|
|
||||||
*/
|
|
||||||
boolean burnSafetyWire() {
|
|
||||||
LogUtils.d(TAG, "burnSafetyWire:执行一次熔断降级");
|
|
||||||
final int safeLevel = loadCurrentSafetyLevel();
|
|
||||||
|
|
||||||
if (isSafetyWireWorking(safeLevel)) {
|
|
||||||
saveCurrentSafetyLevel(safeLevel - 1);
|
|
||||||
return isSafetyWireWorking(safeLevel - 1);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验指定等级是否在有效防护区间
|
|
||||||
* @param safetyLevel 待校验防护等级
|
|
||||||
* @return true=防护有效 false=已熔断
|
|
||||||
*/
|
|
||||||
boolean isSafetyWireWorking(final int safetyLevel) {
|
|
||||||
LogUtils.d(TAG, "isSafetyWireWorking:校验等级 = " + safetyLevel);
|
|
||||||
if (safetyLevel >= _MINI && safetyLevel <= _MAX) {
|
|
||||||
LogUtils.d(TAG, "isSafetyWireWorking:等级在合法区间内");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
LogUtils.d(TAG, "isSafetyWireWorking:等级超出合法区间,已熔断");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 立即恢复防护等级为最高等级
|
|
||||||
*/
|
|
||||||
void resumeToMaximumImmediately() {
|
|
||||||
LogUtils.d(TAG, "resumeToMaximumImmediately:重置为最高防护等级");
|
|
||||||
GlobalAppCrashSafetyWire.getInstance().saveCurrentSafetyLevel(_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭防护,设置为临界最低等级
|
|
||||||
*/
|
|
||||||
void off() {
|
|
||||||
LogUtils.d(TAG, "off:设置为临界最低防护等级");
|
|
||||||
saveCurrentSafetyLevel(_MINI);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查当前整体保险丝是否处于可用防护状态
|
|
||||||
* @return true=防护正常 false=已熔断
|
|
||||||
*/
|
|
||||||
boolean isAppCrashSafetyWireOK() {
|
|
||||||
LogUtils.d(TAG, "isAppCrashSafetyWireOK:检测当前防护状态");
|
|
||||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
|
||||||
return isSafetyWireWorking(currentSafetyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟异步检测并自动恢复防护等级
|
|
||||||
* @param context 上下文对象
|
|
||||||
*/
|
|
||||||
void postResumeCrashSafetyWireHandler(final Context context) {
|
|
||||||
LogUtils.d(TAG, "postResumeCrashSafetyWireHandler:开启延迟自动恢复任务");
|
|
||||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final int nextLevel = currentSafetyLevel - 1;
|
|
||||||
if (!GlobalAppCrashSafetyWire.getInstance().isSafetyWireWorking(nextLevel)) {
|
|
||||||
GlobalAppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
|
||||||
LogUtils.d(TAG, "postResumeCrashSafetyWireHandler:临近熔断,自动重置最高等级");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void testGlobalAppCrashSafetyWire(Context context) {
|
|
||||||
if (GlobalAppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) {
|
|
||||||
GlobalAppCrashSafetyWire.getInstance().burnSafetyWire();
|
|
||||||
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
|
|
||||||
context.getString(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -43,9 +43,7 @@ implements MenuItem.OnMenuItemClickListener {
|
|||||||
LogUtils.d(TAG, "onCreate: 初始化崩溃展示页面");
|
LogUtils.d(TAG, "onCreate: 初始化崩溃展示页面");
|
||||||
|
|
||||||
final Context appContext = getApplicationContext();
|
final Context appContext = getApplicationContext();
|
||||||
GlobalAppCrashSafetyWire.getInstance()
|
|
||||||
.postResumeCrashSafetyWireHandler(appContext);
|
|
||||||
|
|
||||||
mCrashLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_LOG);
|
mCrashLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_LOG);
|
||||||
setContentView(R.layout.activity_globalcrash);
|
setContentView(R.layout.activity_globalcrash);
|
||||||
|
|
||||||
@@ -96,7 +94,7 @@ implements MenuItem.OnMenuItemClickListener {
|
|||||||
copyCrashLogToClipboard();
|
copyCrashLogToClipboard();
|
||||||
break;
|
break;
|
||||||
case MENU_ITEM_RESTART:
|
case MENU_ITEM_RESTART:
|
||||||
GlobalAppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
CrashHandler.AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||||
restartApp();
|
restartApp();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -8,15 +8,10 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import cc.winboll.studio.libappbase.CrashHandler;
|
import cc.winboll.studio.libappbase.CrashHandler;
|
||||||
import cc.winboll.studio.libappbase.GlobalCrashActivity;
|
import cc.winboll.studio.libappbase.GlobalCrashActivity;
|
||||||
import cc.winboll.studio.libappbase.LogUtils;
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
* @Date 2025/11/29 21:12
|
* @Date 2025/11/29 21:12
|
||||||
@@ -173,7 +168,7 @@ public class CrashHandleNotifyUtils {
|
|||||||
private static PendingIntent getGlobalCrashPendingIntent(Context hostContext, String hostPackageName, String errorLog) {
|
private static PendingIntent getGlobalCrashPendingIntent(Context hostContext, String hostPackageName, String errorLog) {
|
||||||
try {
|
try {
|
||||||
// 1. 构建跳转宿主 GlobalCrashActivity 的显式意图(类库场景必须显式指定宿主包名)
|
// 1. 构建跳转宿主 GlobalCrashActivity 的显式意图(类库场景必须显式指定宿主包名)
|
||||||
Intent crashIntent = new Intent(hostContext, GlobalCrashActivity.class);
|
Intent crashIntent = new Intent(hostContext, CrashHandler.AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()?GlobalCrashActivity.class : CrashHandler.CrashActivity.class);
|
||||||
// 关键:绑定宿主包名,确保意图能正确匹配宿主的 Activity(避免类库包名干扰)
|
// 关键:绑定宿主包名,确保意图能正确匹配宿主的 Activity(避免类库包名干扰)
|
||||||
crashIntent.setPackage(hostPackageName);
|
crashIntent.setPackage(hostPackageName);
|
||||||
// 传递崩溃日志(键:EXTRA_CRASH_INFO,与宿主 GlobalCrashActivity 完全匹配)
|
// 传递崩溃日志(键:EXTRA_CRASH_INFO,与宿主 GlobalCrashActivity 完全匹配)
|
||||||
|
|||||||
@@ -12,5 +12,10 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/activityglobalcrashGlobalCrashReportView1"/>
|
android:id="@+id/activityglobalcrashGlobalCrashReportView1"/>
|
||||||
|
|
||||||
|
<Button2
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Button"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user