From 02c9776062c683fde4f2b7c82a0bd8b78cb7fd1b Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Tue, 11 Nov 2025 20:06:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BA=90=E7=A0=81=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appbase/build.properties | 4 +- libappbase/build.properties | 4 +- .../winboll/studio/libappbase/APPModel.java | 101 ++- .../winboll/studio/libappbase/BaseBean.java | 637 +++++++++++------- .../studio/libappbase/GlobalApplication.java | 136 +++- .../libappbase/GlobalCrashActivity.java | 176 +++-- .../winboll/studio/libappbase/LogUtils.java | 2 +- 7 files changed, 715 insertions(+), 345 deletions(-) diff --git a/appbase/build.properties b/appbase/build.properties index 31f0faf7..599611e2 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -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 diff --git a/libappbase/build.properties b/libappbase/build.properties index 31f0faf7..599611e2 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -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 diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/APPModel.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/APPModel.java index 8d934696..46b2ee62 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/APPModel.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/APPModel.java @@ -1,73 +1,138 @@ package cc.winboll.studio.libappbase; /** - * @Author ZhanGSKen - * @Date 2025/03/02 10:28:08 - * @Describe 应用调试模型 + * @Author ZhanGSKen&豆包大模型 + * @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; } } + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java index e1b5e7b3..b8b1f2f3 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java @@ -1,9 +1,12 @@ package cc.winboll.studio.libappbase; /** - * @Author ZhanGSKen - * @Date 2025/01/15 11:11:52 - * @Describe Json Bean 基础类。 + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/11/11 20:03 + * @Describe WinBoLL JSON 数据模型基类(抽象类) + * 定义 Json Bean 的核心规范:序列化/反序列化、文件持久化、列表处理等通用逻辑, + * 子类(如 APPModel)需实现抽象方法,实现自身字段的 JSON 读写 + * @param 泛型约束,限定子类必须继承自 BaseBean */ import android.content.Context; import android.util.JsonReader; @@ -16,272 +19,418 @@ import java.util.ArrayList; public abstract class 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 泛型约束,限定为 BaseBean 子类 + */ + public static String checkIsTheSameBeanListAndFile(String szFilePath, Class 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 String checkIsTheSameBeanListAndFile(String szFilePath, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static T parseStringToBean(String szBean, Class clazz) throws IOException { + StringReader stringReader = new StringReader(szBean); + JsonReader jsonReader = new JsonReader(stringReader); - public static T parseStringToBean(String szBean, Class 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 boolean parseStringToBeanList(String szBeanList, ArrayList beanList, Class clazz) { - try { - if(beanList == null) { - beanList = new ArrayList(); - } 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 泛型约束,限定为 BaseBean 子类 + */ + public static boolean parseStringToBeanList(String szBeanList, ArrayList beanList, Class clazz) { + try { + // 初始化目标列表(为空则创建,非空则清空) + if (beanList == null) { + beanList = new ArrayList(); + } 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 String toStringByBeanList(ArrayList 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 loadBean(Context context, Class 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 loadBeanFromFile(String szFilePath, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static String toStringByBeanList(ArrayList 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 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 泛型约束,限定为 BaseBean 子类 + */ + public static T loadBean(Context context, Class 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 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 泛型约束,限定为 BaseBean 子类 + */ + public static T loadBeanFromFile(String szFilePath, Class 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 boolean loadBeanList(Context context, ArrayList beanListDst, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static boolean saveBean(Context context, T bean) { + return saveBeanToFile(bean.getBeanJsonFilePath(context), bean); + } - public static boolean loadBeanListFromFile(String szFilePath, ArrayList beanList, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static 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 boolean saveBeanList(Context context, ArrayList beanList, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static boolean loadBeanList(Context context, ArrayList beanListDst, Class 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 boolean saveBeanListToFile(String szFilePath, ArrayList 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 泛型约束,限定为 BaseBean 子类 + */ + public static boolean loadBeanListFromFile(String szFilePath, ArrayList beanList, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static boolean saveBeanList(Context context, ArrayList beanList, Class 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 泛型约束,限定为 BaseBean 子类 + */ + public static boolean saveBeanListToFile(String szFilePath, ArrayList 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; + } } + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalApplication.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalApplication.java index 8c211866..eba41715 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalApplication.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalApplication.java @@ -1,70 +1,134 @@ package cc.winboll.studio.libappbase; /** - * @Author ZhanGSKen - * @Date 2025/01/05 10:10:23 - * @Describe 全局应用类 + * @Author ZhanGSKen&豆包大模型 + * @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; } } + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalCrashActivity.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalCrashActivity.java index b4ecda5a..5fcd340f 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalCrashActivity.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalCrashActivity.java @@ -1,8 +1,11 @@ package cc.winboll.studio.libappbase; /** - * @Author ZhanGSKen - * @Date 2025/02/11 00:14:05 + * @Author ZhanGSKen&豆包大模型 + * @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(); + } } + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java index 4525cc28..536fdde0 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java @@ -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()) {