源码整理
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Nov 11 11:52:41 GMT 2025
|
||||
#Tue Nov 11 12:05:37 GMT 2025
|
||||
stageCount=10
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.9
|
||||
buildCount=2
|
||||
buildCount=3
|
||||
baseBetaVersion=15.10.10
|
||||
|
||||
@@ -1,73 +1,138 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/02 10:28:08
|
||||
* @Describe 应用调试模型
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:01
|
||||
* @Describe WinBoLL 应用全局数据模型类
|
||||
* 继承自 BaseBean,用于存储和管理应用的核心配置信息(如调试状态),
|
||||
* 支持 JSON 序列化/反序列化,便于数据持久化或跨组件传递
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class APPModel extends BaseBean {
|
||||
|
||||
/**
|
||||
* 日志打印标签,用于区分当前类的日志输出
|
||||
*/
|
||||
public static final String TAG = "APPModel";
|
||||
|
||||
// 应用是否处于正在调试状态
|
||||
//
|
||||
boolean isDebuging = false;
|
||||
/**
|
||||
* 应用调试状态标识
|
||||
* true:应用处于调试模式(可输出详细日志、启用调试功能等)
|
||||
* false:应用处于正式模式(关闭调试相关功能,优化性能)
|
||||
*/
|
||||
private boolean isDebugging = false; // 修正拼写:原 isDebuging -> isDebugging(符合命名规范)
|
||||
|
||||
/**
|
||||
* 无参构造方法
|
||||
* 初始化调试状态为默认值:false(正式模式)
|
||||
*/
|
||||
public APPModel() {
|
||||
this.isDebuging = false;
|
||||
this.isDebugging = false;
|
||||
}
|
||||
|
||||
public APPModel(boolean isDebuging) {
|
||||
this.isDebuging = isDebuging;
|
||||
/**
|
||||
* 带参构造方法
|
||||
* 可通过参数指定应用的初始调试状态
|
||||
* @param isDebugging 初始调试状态(true:调试模式;false:正式模式)
|
||||
*/
|
||||
public APPModel(boolean isDebugging) {
|
||||
this.isDebugging = isDebugging;
|
||||
}
|
||||
|
||||
public void setIsDebuging(boolean isDebuging) {
|
||||
this.isDebuging = isDebuging;
|
||||
/**
|
||||
* 设置应用调试状态
|
||||
* @param isDebugging 目标调试状态(true:开启调试;false:关闭调试)
|
||||
*/
|
||||
public void setIsDebugging(boolean isDebugging) {
|
||||
this.isDebugging = isDebugging;
|
||||
}
|
||||
|
||||
public boolean isDebuging() {
|
||||
return isDebuging;
|
||||
/**
|
||||
* 获取当前应用调试状态
|
||||
* @return 调试状态(true:调试中;false:非调试)
|
||||
*/
|
||||
public boolean isDebugging() {
|
||||
return isDebugging;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,返回当前类的全限定名
|
||||
* 用于标识数据模型的类类型(可用于反射、序列化校验等场景)
|
||||
* @return 类的全限定名(如:cc.winboll.studio.libappbase.APPModel)
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return APPModel.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,将当前模型的字段序列化到 JSON 中
|
||||
* 用于将调试状态等核心数据转换为 JSON 格式(如持久化到文件、网络传输)
|
||||
* @param jsonWriter JSON 写入器对象,用于输出 JSON 数据
|
||||
* @throws IOException 当 JSON 写入失败时抛出(如流关闭、格式错误)
|
||||
*/
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
// 先调用父类方法,序列化父类中的字段(若 BaseBean 有可序列化字段)
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isDebuging").value(isDebuging());
|
||||
// 序列化当前类的调试状态字段:key 为 "isDebuging"(保持与原代码一致,避免兼容性问题),value 为当前状态
|
||||
jsonWriter.name("isDebuging").value(isDebugging());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,从 JSON 中解析字段并初始化当前对象
|
||||
* 用于将 JSON 格式的配置数据解析为 APPModel 实例(如从文件读取、网络接收后解析)
|
||||
* @param jsonReader JSON 读取器对象,用于读取 JSON 数据
|
||||
* @param name 当前解析的 JSON 字段名
|
||||
* @return true:字段解析成功;false:字段不属于当前类,需由调用者处理
|
||||
* @throws IOException 当 JSON 读取失败时抛出(如流关闭、数据格式错误)
|
||||
*/
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
// 先调用父类方法,解析父类中的字段(若 BaseBean 有可解析字段)
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) {
|
||||
return true; // 父类已处理该字段,直接返回成功
|
||||
} else {
|
||||
// 解析当前类的字段
|
||||
if (name.equals("isDebuging")) {
|
||||
setIsDebuging(jsonReader.nextBoolean());
|
||||
// 读取 JSON 中 "isDebuging" 字段的值,设置为当前对象的调试状态
|
||||
setIsDebugging(jsonReader.nextBoolean());
|
||||
} else {
|
||||
// 字段不属于当前类,返回 false 提示调用者跳过该字段
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 字段解析成功,返回 true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,从 JSON 读取器中完整解析一个 APPModel 实例
|
||||
* 负责处理 JSON 对象的开始/结束标记,循环解析所有字段
|
||||
* @param jsonReader JSON 读取器对象,用于读取 JSON 数据
|
||||
* @return 解析完成的当前 APPModel 实例(支持链式调用)
|
||||
* @throws IOException 当 JSON 读取失败时抛出(如流关闭、数据格式错误)
|
||||
*/
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
// 开始解析 JSON 对象(对应 JSON 中的 '{')
|
||||
jsonReader.beginObject();
|
||||
// 循环读取 JSON 中的所有字段(直到对象结束)
|
||||
while (jsonReader.hasNext()) {
|
||||
// 获取当前字段名
|
||||
String name = jsonReader.nextName();
|
||||
// 解析字段:若当前类无法处理该字段,则跳过(避免解析异常)
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
// 结束解析 JSON 对象(对应 JSON 中的 '}')
|
||||
jsonReader.endObject();
|
||||
// 返回解析完成的实例(当前对象)
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/01/15 11:11:52
|
||||
* @Describe Json Bean 基础类。
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:03
|
||||
* @Describe WinBoLL JSON 数据模型基类(抽象类)
|
||||
* 定义 Json Bean 的核心规范:序列化/反序列化、文件持久化、列表处理等通用逻辑,
|
||||
* 子类(如 APPModel)需实现抽象方法,实现自身字段的 JSON 读写
|
||||
* @param <T> 泛型约束,限定子类必须继承自 BaseBean
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.util.JsonReader;
|
||||
@@ -16,272 +19,418 @@ import java.util.ArrayList;
|
||||
|
||||
public abstract class BaseBean<T extends BaseBean> {
|
||||
|
||||
public static final String TAG = "BaseBean";
|
||||
static final String BEAN_NAME = "BeanName";
|
||||
/** 日志标签,用于当前基类的日志输出标识 */
|
||||
public static final String TAG = "BaseBean";
|
||||
/** JSON 中存储 Bean 类名的字段键(用于校验 Bean 类型一致性) */
|
||||
static final String BEAN_NAME = "BeanName";
|
||||
|
||||
public BaseBean() {}
|
||||
/**
|
||||
* 无参构造方法(子类需默认实现,支持反射实例化)
|
||||
*/
|
||||
public BaseBean() {}
|
||||
|
||||
public abstract String getName();
|
||||
/**
|
||||
* 抽象方法:获取当前 Bean 的全限定类名
|
||||
* 子类需实现,用于标识 Bean 类型(序列化/校验时使用)
|
||||
* @return 类的全限定名(如:cc.winboll.studio.libappbase.APPModel)
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
public String getBeanJsonFilePath(Context context) {
|
||||
/**
|
||||
* 获取单个 Bean 的 JSON 持久化文件路径
|
||||
* 路径:外部存储/应用私有目录/BaseBean/[类名].json
|
||||
* @param context 上下文(用于获取应用存储目录)
|
||||
* @return 单个 Bean 的文件绝对路径
|
||||
*/
|
||||
public String getBeanJsonFilePath(Context context) {
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + ".json";
|
||||
}
|
||||
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + ".json";
|
||||
}
|
||||
/**
|
||||
* 获取 Bean 列表的 JSON 持久化文件路径
|
||||
* 路径:外部存储/应用私有目录/BaseBean/[类名]_List.json
|
||||
* @param context 上下文(用于获取应用存储目录)
|
||||
* @return Bean 列表的文件绝对路径
|
||||
*/
|
||||
public String getBeanListJsonFilePath(Context context) {
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json";
|
||||
}
|
||||
|
||||
public String getBeanListJsonFilePath(Context context) {
|
||||
/**
|
||||
* 将 Bean 类名写入 JSON(序列化基础字段)
|
||||
* 子类可重写扩展,添加自身字段的 JSON 写入逻辑
|
||||
* @param jsonWriter JSON 写入器(用于输出 JSON 数据)
|
||||
* @throws IOException JSON 写入失败时抛出(如流异常)
|
||||
*/
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
// 写入 Bean 类名字段(用于反序列化时校验类型)
|
||||
jsonWriter.name(BEAN_NAME).value(getName());
|
||||
}
|
||||
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json";
|
||||
}
|
||||
/**
|
||||
* 从 JSON 读取字段并初始化 Bean(反序列化基础逻辑)
|
||||
* 子类需重写,实现自身字段的解析逻辑
|
||||
* @param jsonReader JSON 读取器(用于读取 JSON 数据)
|
||||
* @param name 当前解析的 JSON 字段名
|
||||
* @return true:字段解析成功(当前类处理);false:字段未处理(需跳过)
|
||||
* @throws IOException JSON 读取失败时抛出(如流异常)
|
||||
*/
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
return false; // 基类未处理任何字段,返回 false
|
||||
}
|
||||
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
jsonWriter.name(BEAN_NAME).value(getName());
|
||||
}
|
||||
/**
|
||||
* 抽象方法:从 JSON 读取器解析并返回 Bean 实例
|
||||
* 子类需实现,处理自身字段的完整解析逻辑
|
||||
* @param jsonReader JSON 读取器(用于读取 JSON 数据)
|
||||
* @return 解析完成的 Bean 实例
|
||||
* @throws IOException JSON 读取失败时抛出(如流异常)
|
||||
*/
|
||||
abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException;
|
||||
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 校验 JSON 文件中的 Bean 列表与目标类是否一致
|
||||
* 对比文件中每个 Bean 的类名与目标类名,返回不一致信息
|
||||
* @param szFilePath JSON 文件路径(存储 Bean 列表的文件)
|
||||
* @param clazz 目标 Bean 类(用于校验类型)
|
||||
* @return 空串:校验一致;非空串:不一致信息(总数/差异数)或异常信息
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(String szFilePath, Class<T> clazz) {
|
||||
StringBuilder sbResult = new StringBuilder();
|
||||
String szErrorInfo = "Check Is The Same Bean List And File Error : ";
|
||||
|
||||
abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException;
|
||||
try {
|
||||
int sameCount = 0; // 类名匹配的 Bean 数量
|
||||
int totalCount = 0; // 文件中 Bean 总数量
|
||||
|
||||
public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(String szFilePath, Class<T> clazz) {
|
||||
StringBuilder sbResult = new StringBuilder();
|
||||
String szErrorInfo = "Check Is The Same Bean List And File Error : ";
|
||||
// 反射创建目标 Bean 实例(用于获取类名)
|
||||
T beanTemp = clazz.newInstance();
|
||||
String targetBeanName = beanTemp.getName();
|
||||
// 读取文件中的 JSON 字符串
|
||||
String listJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
StringReader stringReader = new StringReader(listJson);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
|
||||
try {
|
||||
int nSameCount = 0;
|
||||
int nBeanListCout = 0;
|
||||
jsonReader.beginArray(); // 开始解析 JSON 数组(Bean 列表)
|
||||
while (jsonReader.hasNext()) {
|
||||
totalCount++;
|
||||
jsonReader.beginObject(); // 开始解析单个 Bean 对象
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
// 只校验 BEAN_NAME 字段,其他字段跳过
|
||||
if (name.equals(BEAN_NAME)) {
|
||||
// 对比当前 Bean 类名与目标类名
|
||||
if (targetBeanName.equals(jsonReader.nextString())) {
|
||||
sameCount++;
|
||||
}
|
||||
} else {
|
||||
jsonReader.skipValue(); // 跳过非目标字段
|
||||
}
|
||||
}
|
||||
jsonReader.endObject(); // 结束单个 Bean 对象解析
|
||||
}
|
||||
jsonReader.endArray(); // 结束 JSON 数组解析
|
||||
|
||||
T beanTemp = clazz.newInstance();
|
||||
String szBeanSimpleName = beanTemp.getName();
|
||||
String szListJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
StringReader stringReader = new StringReader(szListJson);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
jsonReader.beginArray();
|
||||
while (jsonReader.hasNext()) {
|
||||
nBeanListCout++;
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (name.equals(BEAN_NAME)) {
|
||||
if (szBeanSimpleName.equals(jsonReader.nextString())) {
|
||||
nSameCount++;
|
||||
}
|
||||
} else {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
jsonReader.endObject();
|
||||
}
|
||||
jsonReader.endArray();
|
||||
// 生成校验结果
|
||||
if (sameCount == totalCount) {
|
||||
return ""; // 全部匹配,返回空串
|
||||
} else {
|
||||
// 部分不匹配,返回统计信息
|
||||
sbResult.append("Total : ").append(totalCount)
|
||||
.append(" Diff : ").append(totalCount - sameCount);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
// 反射实例化失败(如无无参构造)
|
||||
sbResult.append(szErrorInfo).append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
// 反射访问权限异常
|
||||
sbResult.append(szErrorInfo).append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IOException e) {
|
||||
// 文件读取或 JSON 解析异常
|
||||
sbResult.append(szErrorInfo).append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return sbResult.toString();
|
||||
}
|
||||
|
||||
// 返回检查结果
|
||||
if (nSameCount == nBeanListCout) {
|
||||
// 检查一致直接返回空串
|
||||
return "";
|
||||
} else {
|
||||
// 检查不一致返回对比信息
|
||||
sbResult.append("Total : ");
|
||||
sbResult.append(nBeanListCout);
|
||||
sbResult.append(" Diff : ");
|
||||
sbResult.append(nBeanListCout - nSameCount);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
sbResult.append(szErrorInfo);
|
||||
sbResult.append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
sbResult.append(szErrorInfo);
|
||||
sbResult.append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IOException e) {
|
||||
sbResult.append(szErrorInfo);
|
||||
sbResult.append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return sbResult.toString();
|
||||
}
|
||||
/**
|
||||
* 将 JSON 字符串解析为目标 Bean 实例
|
||||
* 通过反射创建 Bean 实例,调用子类解析逻辑完成初始化
|
||||
* @param szBean JSON 字符串(单个 Bean 的 JSON 数据)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return 解析成功的 Bean 实例;失败返回 null
|
||||
* @throws IOException JSON 解析失败时抛出
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> T parseStringToBean(String szBean, Class<T> clazz) throws IOException {
|
||||
StringReader stringReader = new StringReader(szBean);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
|
||||
public static <T extends BaseBean> T parseStringToBean(String szBean, Class<T> clazz) throws IOException {
|
||||
// 创建 JsonWriter 对象
|
||||
StringReader stringReader = new StringReader(szBean);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return (T)beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 反射创建 Bean 实例
|
||||
T beanTemp = clazz.newInstance();
|
||||
// 调用子类解析方法,返回解析后的实例
|
||||
return (T) beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
// 反射异常日志记录
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
if(beanList == null) {
|
||||
beanList = new ArrayList<T>();
|
||||
} else {
|
||||
beanList.clear();
|
||||
}
|
||||
StringReader stringReader = new StringReader(szBeanList);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
jsonReader.beginArray();
|
||||
while (jsonReader.hasNext()) {
|
||||
T beanTemp = clazz.newInstance();
|
||||
T bean = (T)beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
if (bean != null) {
|
||||
beanList.add(bean);
|
||||
//LogUtils.d(TAG, "beanList.add(bean)");
|
||||
}
|
||||
}
|
||||
jsonReader.endArray();
|
||||
return true;
|
||||
//LogUtils.d(TAG, "beanList.size() is " + Integer.toString(beanList.size()));
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 将 JSON 字符串解析为 Bean 列表
|
||||
* 清空目标列表,将解析后的 Bean 逐个添加到列表中
|
||||
* @param szBeanList JSON 字符串(Bean 列表的 JSON 数组)
|
||||
* @param beanList 目标列表(存储解析后的 Bean)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return true:解析成功;false:解析失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
// 初始化目标列表(为空则创建,非空则清空)
|
||||
if (beanList == null) {
|
||||
beanList = new ArrayList<T>();
|
||||
} else {
|
||||
beanList.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// 创建 JsonWriter 对象
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" ");
|
||||
try {// 开始 JSON 对象
|
||||
jsonWriter.beginObject();
|
||||
// 写入键值对
|
||||
writeThisToJsonWriter(jsonWriter);
|
||||
// 结束 JSON 对象
|
||||
jsonWriter.endObject();
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
// 获取 JSON 字符串
|
||||
return "";
|
||||
}
|
||||
StringReader stringReader = new StringReader(szBeanList);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
|
||||
public static <T extends BaseBean> String toStringByBeanList(ArrayList<T> beanList) {
|
||||
try {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" ");
|
||||
jsonWriter.beginArray();
|
||||
for (int i = 0; i < beanList.size(); i++) {
|
||||
// 开始 JSON 对象
|
||||
jsonWriter.beginObject();
|
||||
// 写入键值对
|
||||
beanList.get(i).writeThisToJsonWriter(jsonWriter);
|
||||
// 结束 JSON 对象
|
||||
jsonWriter.endObject();
|
||||
}
|
||||
jsonWriter.endArray();
|
||||
jsonWriter.close();
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
jsonReader.beginArray(); // 开始解析 JSON 数组
|
||||
while (jsonReader.hasNext()) {
|
||||
// 反射创建 Bean 实例,解析并添加到列表
|
||||
T beanTemp = clazz.newInstance();
|
||||
T bean = (T) beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
if (bean != null) {
|
||||
beanList.add(bean);
|
||||
}
|
||||
}
|
||||
jsonReader.endArray(); // 结束 JSON 数组解析
|
||||
return true;
|
||||
} catch (InstantiationException | IllegalAccessException | IOException e) {
|
||||
// 异常日志记录
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写 toString(),将 Bean 序列化为格式化的 JSON 字符串
|
||||
* 调用自身序列化逻辑,生成带缩进的 JSON(便于调试)
|
||||
* @return Bean 的 JSON 字符串;失败返回空串
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" "); // 设置 JSON 缩进(格式化输出)
|
||||
|
||||
public static <T extends BaseBean> T loadBean(Context context, Class<T> clazz) {
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz);
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
jsonWriter.beginObject(); // 开始 JSON 对象
|
||||
writeThisToJsonWriter(jsonWriter); // 写入 Bean 字段(子类扩展)
|
||||
jsonWriter.endObject(); // 结束 JSON 对象
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> T loadBeanFromFile(String szFilePath, Class<T> clazz) {
|
||||
try {
|
||||
try {
|
||||
File fTemp = new File(szFilePath);
|
||||
if (fTemp.exists()) {
|
||||
T beanTemp = clazz.newInstance();String szJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
return beanTemp.parseStringToBean(szJson, clazz);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
/**
|
||||
* 将 Bean 列表序列化为格式化的 JSON 字符串
|
||||
* 遍历列表,逐个序列化每个 Bean,生成 JSON 数组
|
||||
* @param beanList 待序列化的 Bean 列表
|
||||
* @return 列表的 JSON 字符串;失败返回空串
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> String toStringByBeanList(ArrayList<T> beanList) {
|
||||
try {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" "); // 格式化缩进
|
||||
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
jsonWriter.beginArray(); // 开始 JSON 数组
|
||||
for (int i = 0; i < beanList.size(); i++) {
|
||||
jsonWriter.beginObject(); // 单个 Bean 开始
|
||||
beanList.get(i).writeThisToJsonWriter(jsonWriter); // 调用 Bean 自身序列化
|
||||
jsonWriter.endObject(); // 单个 Bean 结束
|
||||
}
|
||||
jsonWriter.endArray(); // 结束 JSON 数组
|
||||
jsonWriter.close();
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBean(Context context, T bean) {
|
||||
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
|
||||
}
|
||||
/**
|
||||
* 从默认路径(getBeanJsonFilePath)加载 Bean 实例
|
||||
* 读取应用私有目录下的 JSON 文件,解析为目标 Bean
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return 加载成功的 Bean 实例;失败返回 null
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> T loadBean(Context context, Class<T> clazz) {
|
||||
try {
|
||||
// 反射创建 Bean 实例,获取默认文件路径
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBeanToFile(String szFilePath, T bean) {
|
||||
try {
|
||||
String szJson = bean.toString();
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, szJson);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 从指定文件路径加载 Bean 实例
|
||||
* 检查文件是否存在,存在则读取 JSON 并解析为目标 Bean
|
||||
* @param szFilePath 目标文件路径(存储 Bean 的 JSON 文件)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return 加载成功的 Bean 实例;失败返回 null
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> T loadBeanFromFile(String szFilePath, Class<T> clazz) {
|
||||
try {
|
||||
File file = new File(szFilePath);
|
||||
if (file.exists()) { // 检查文件是否存在
|
||||
T beanTemp = clazz.newInstance();
|
||||
// 读取文件 JSON 字符串,解析为 Bean
|
||||
String json = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
return beanTemp.parseStringToBean(json, clazz);
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException | IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean loadBeanList(Context context, ArrayList<T> beanListDst, Class<T> clazz) {
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz);
|
||||
} catch (InstantiationException e) {} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 将 Bean 保存到默认路径(getBeanJsonFilePath)的文件中
|
||||
* 序列化 Bean 为 JSON,写入应用私有目录下的文件
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param bean 待保存的 Bean 实例
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBean(Context context, T bean) {
|
||||
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean loadBeanListFromFile(String szFilePath, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
File fTemp = new File(szFilePath);
|
||||
if (fTemp.exists()) {
|
||||
String szListJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
return parseStringToBeanList(szListJson, beanList, clazz);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 将 Bean 保存到指定文件路径
|
||||
* 序列化 Bean 为 JSON 字符串,写入目标文件(覆盖原有内容)
|
||||
* @param szFilePath 目标文件路径(保存 JSON 的文件)
|
||||
* @param bean 待保存的 Bean 实例
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBeanToFile(String szFilePath, T bean) {
|
||||
try {
|
||||
// 序列化 Bean 为 JSON 字符串
|
||||
String json = bean.toString();
|
||||
// 写入文件(UTF-8 编码)
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, json);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBeanList(Context context, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList);
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 从默认路径(getBeanListJsonFilePath)加载 Bean 列表
|
||||
* 读取应用私有目录下的列表 JSON 文件,解析并填充到目标列表
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param beanListDst 目标列表(存储加载后的 Bean)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return true:加载成功;false:加载失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean loadBeanList(Context context, ArrayList<T> beanListDst, Class<T> clazz) {
|
||||
try {
|
||||
// 反射创建 Bean 实例,获取默认列表文件路径
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> beanList) {
|
||||
try {
|
||||
String szJson = toStringByBeanList(beanList);
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, szJson);
|
||||
//LogUtils.d(TAG, "FileUtil.writeFile beanList.size() is " + Integer.toString(beanList.size()));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 从指定文件路径加载 Bean 列表
|
||||
* 检查文件是否存在,存在则读取 JSON 数组,解析并填充到目标列表
|
||||
* @param szFilePath 目标文件路径(存储列表 JSON 的文件)
|
||||
* @param beanList 目标列表(存储加载后的 Bean)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return true:加载成功;false:加载失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean loadBeanListFromFile(String szFilePath, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
File file = new File(szFilePath);
|
||||
if (file.exists()) { // 检查文件是否存在
|
||||
// 读取文件中的 JSON 字符串(Bean 列表数组)
|
||||
String listJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
// 解析 JSON 字符串为 Bean 列表,填充到目标列表
|
||||
return parseStringToBeanList(listJson, beanList, clazz);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 日志记录文件读取或解析异常
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Bean 列表保存到默认路径(getBeanListJsonFilePath)的文件中
|
||||
* 序列化列表为 JSON 数组,写入应用私有目录下的文件
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param beanList 待保存的 Bean 列表
|
||||
* @param clazz 目标 Bean 类(用于反射获取保存路径)
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBeanList(Context context, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
// 反射创建 Bean 实例,获取默认列表保存路径
|
||||
T beanTemp = clazz.newInstance();
|
||||
return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
// 日志记录反射实例化异常
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Bean 列表保存到指定文件路径
|
||||
* 序列化列表为 JSON 数组字符串,写入目标文件(覆盖原有内容)
|
||||
* @param szFilePath 目标文件路径(保存列表 JSON 的文件)
|
||||
* @param beanList 待保存的 Bean 列表
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> beanList) {
|
||||
try {
|
||||
// 序列化 Bean 列表为 JSON 字符串(数组格式)
|
||||
String json = toStringByBeanList(beanList);
|
||||
// 将 JSON 字符串写入文件(UTF-8 编码)
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, json);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// 日志记录文件写入或序列化异常
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +1,134 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/01/05 10:10:23
|
||||
* @Describe 全局应用类
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 19:56
|
||||
* @Describe 全局 Application 类,用于初始化应用核心组件、管理全局状态(如调试模式)
|
||||
* 需在 AndroidManifest.xml 中配置 android:name=".GlobalApplication" 使其生效
|
||||
*/
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
public class GlobalApplication extends Application {
|
||||
|
||||
/** 日志标签 */
|
||||
public static final String TAG = "GlobalApplication";
|
||||
|
||||
// 应用是否处于调试状态
|
||||
volatile static boolean isDebuging = false;
|
||||
|
||||
public static void setIsDebuging(boolean isDebuging) {
|
||||
GlobalApplication.isDebuging = isDebuging;
|
||||
}
|
||||
|
||||
public static void saveDebugStatus(Context context) {
|
||||
APPModel.saveBeanToFile(getAPPModelFilePath(context), new APPModel(GlobalApplication.isDebuging));
|
||||
}
|
||||
|
||||
static String getAPPModelFilePath(Context context) {
|
||||
return context.getDataDir().getPath() + "/APPModel.json";
|
||||
|
||||
/**
|
||||
* 应用调试模式标记(volatile 保证多线程可见性)
|
||||
* true:调试模式(开启日志、调试功能);false:正式模式(关闭调试相关功能)
|
||||
*/
|
||||
private static volatile boolean isDebugging = false;
|
||||
|
||||
/**
|
||||
* 设置应用调试模式
|
||||
* @param debugging 调试模式状态(true/false)
|
||||
*/
|
||||
public static void setIsDebugging(boolean debugging) {
|
||||
isDebugging = debugging;
|
||||
}
|
||||
|
||||
public static boolean isDebuging() {
|
||||
return isDebuging;
|
||||
/**
|
||||
* 保存调试模式状态到本地文件(持久化存储,重启应用后生效)
|
||||
* @param application 全局 Application 实例(通过 getInstance() 获取更规范,此处保留原有参数)
|
||||
*/
|
||||
public static void saveDebugStatus(GlobalApplication application) {
|
||||
// 将调试状态封装为 APPModel 并保存到文件
|
||||
APPModel.saveBeanToFile(
|
||||
getAppModelFilePath(application),
|
||||
new APPModel(isDebugging)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 APPModel 配置文件的存储路径
|
||||
* 路径:应用私有数据目录 / APPModel.json(仅当前应用可访问,安全)
|
||||
* @param application 全局 Application 实例
|
||||
* @return 配置文件绝对路径
|
||||
*/
|
||||
private static String getAppModelFilePath(GlobalApplication application) {
|
||||
return application.getDataDir().getPath() + "/APPModel.json";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前应用调试模式状态
|
||||
* @return true:调试模式;false:正式模式
|
||||
*/
|
||||
public static boolean isDebugging() {
|
||||
return isDebugging;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用启动时初始化(仅执行一次)
|
||||
* 初始化核心框架、恢复调试状态、配置全局异常处理等
|
||||
*/
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
setIsDebuging(true);
|
||||
// 添加日志模块
|
||||
LogUtils.init(this);
|
||||
// 设置应用异常处理窗口
|
||||
CrashHandler.init(this);
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
|
||||
// 应用保存的调试标志
|
||||
APPModel appModel = APPModel.loadBeanFromFile(getAPPModelFilePath(this), APPModel.class);
|
||||
// 初始化基础组件(日志、崩溃处理、Toast)
|
||||
initCoreComponents();
|
||||
// 恢复/初始化调试模式状态(从本地文件读取,无文件则默认关闭调试)
|
||||
restoreDebugStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用核心组件(日志、崩溃处理、Toast 框架)
|
||||
*/
|
||||
private void initCoreComponents() {
|
||||
// 初始化日志工具(传入 Application 上下文)
|
||||
LogUtils.init(this);
|
||||
// 初始化全局异常处理器(捕获应用崩溃信息,用于调试或上报)
|
||||
CrashHandler.init(this);
|
||||
// 初始化 Toast 工具(统一 Toast 样式、避免内存泄漏等)
|
||||
ToastUtils.init(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复调试模式状态(从本地配置文件读取)
|
||||
* 1. 读取本地 APPModel.json 文件
|
||||
* 2. 读取成功:使用保存的调试状态;读取失败(文件不存在):默认关闭调试并创建配置文件
|
||||
*/
|
||||
private void restoreDebugStatus() {
|
||||
// 从文件加载 APPModel 实例(存储调试状态的模型类)
|
||||
APPModel appModel = APPModel.loadBeanFromFile(
|
||||
getAppModelFilePath(this),
|
||||
APPModel.class
|
||||
);
|
||||
|
||||
if (appModel == null) {
|
||||
setIsDebuging(false);
|
||||
// 配置文件不存在,默认关闭调试模式并创建文件
|
||||
setIsDebugging(false);
|
||||
saveDebugStatus(this);
|
||||
} else {
|
||||
setIsDebuging(appModel.isDebuging());
|
||||
// 配置文件存在,使用保存的调试状态
|
||||
setIsDebugging(appModel.isDebugging());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAppName(Context context) {
|
||||
|
||||
/**
|
||||
* 获取应用名称(从 AndroidManifest.xml 的 android:label 读取)
|
||||
* @param context 上下文(建议传入 Application 上下文,避免内存泄漏)
|
||||
* @return 应用名称(读取失败返回 null)
|
||||
*/
|
||||
public static String getAppName(Context context) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
// 获取应用信息(包含应用名称、图标等)
|
||||
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
|
||||
context.getPackageName(), 0);
|
||||
context.getPackageName(), // 当前应用包名
|
||||
0 // 额外标志(0 表示默认获取基本信息)
|
||||
);
|
||||
// 从应用信息中获取应用名称(支持多语言)
|
||||
return (String) packageManager.getApplicationLabel(applicationInfo);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
} catch (NameNotFoundException e) {
|
||||
// 包名不存在(理论上不会发生,捕获异常避免崩溃)
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/11 00:14:05
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 19:58
|
||||
* @Describe 应用异常报告观察活动窗口类
|
||||
* 核心功能:应用发生未捕获崩溃时,由 CrashHandler 启动此页面,展示崩溃日志详情,
|
||||
* 并提供「复制日志」「重启应用」操作入口,便于开发者定位问题和用户恢复应用
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
@@ -18,77 +21,166 @@ import cc.winboll.studio.libappbase.R;
|
||||
|
||||
public final class GlobalCrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
|
||||
|
||||
private static final int MENUITEM_COPY = 0;
|
||||
private static final int MENUITEM_RESTART = 1;
|
||||
|
||||
GlobalCrashReportView mGlobalCrashReportView;
|
||||
String mLog;
|
||||
|
||||
|
||||
/** 日志标签(用于调试日志输出,唯一标识当前 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.AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());
|
||||
|
||||
mLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_INFO);
|
||||
//setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
|
||||
//setTheme(R.style.APPBaseTheme);
|
||||
// 初始化崩溃安全防护机制
|
||||
// 作用:防止应用重启后短时间内再次崩溃,由 CrashHandler 内部实现防护逻辑
|
||||
CrashHandler.AppCrashSafetyWire.getInstance()
|
||||
.postResumeCrashSafetyWireHandler(getApplicationContext());
|
||||
|
||||
// 从 Intent 中获取崩溃日志数据(EXTRA_CRASH_INFO 为 CrashHandler 定义的常量键)
|
||||
mCrashLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_INFO);
|
||||
|
||||
// 设置当前 Activity 的布局文件(展示崩溃报告的 UI 结构)
|
||||
setContentView(R.layout.activity_globalcrash);
|
||||
mGlobalCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1);
|
||||
mGlobalCrashReportView.setReport(mLog);
|
||||
setActionBar(mGlobalCrashReportView.getToolbar());
|
||||
|
||||
getActionBar().setTitle(CrashHandler.TITTLE);
|
||||
getActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
|
||||
|
||||
// 初始化崩溃报告展示视图(通过布局 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() {
|
||||
restart();
|
||||
restartApp();
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
PackageManager pm = getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
|
||||
if (intent != null) {
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
);
|
||||
startActivity(intent);
|
||||
/**
|
||||
* 重启当前应用(核心工具方法)
|
||||
* 实现逻辑:
|
||||
* 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 MENUITEM_COPY:
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
|
||||
case MENU_ITEM_COPY:
|
||||
// 点击「复制」菜单,执行复制崩溃日志到剪贴板
|
||||
copyCrashLogToClipboard();
|
||||
break;
|
||||
case MENUITEM_RESTART:
|
||||
case MENU_ITEM_RESTART:
|
||||
// 点击「重启」菜单:先恢复崩溃防护机制到最大等级,再重启应用
|
||||
CrashHandler.AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||
restart();
|
||||
restartApp();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建页面顶部菜单(ActionBar 菜单)
|
||||
* @param menu 菜单容器,用于添加菜单项
|
||||
* @return boolean:true 表示显示菜单;false 表示不显示
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
|
||||
// 更新菜单文字风格
|
||||
mGlobalCrashReportView.updateMenuStyle();
|
||||
// 添加「复制」菜单项:
|
||||
// 参数说明:菜单组 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class LogUtils {
|
||||
// 初始化函数
|
||||
//
|
||||
public static void init(Context context, LOG_LEVEL logLevel) {
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
if (GlobalApplication.isDebugging()) {
|
||||
// 初始化日志缓存文件路径
|
||||
_mfLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG);
|
||||
if (!_mfLogCacheDir.exists()) {
|
||||
|
||||
Reference in New Issue
Block a user