diff --git a/appbase/build.properties b/appbase/build.properties index b02ade7..93f41e8 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Feb 06 07:12:44 HKT 2025 +#Fri Feb 07 09:04:42 GMT 2025 stageCount=1 libraryProject=libappbase baseVersion=1.2 publishVersion=1.2.0 -buildCount=0 +buildCount=172 baseBetaVersion=1.2.1 diff --git a/libappbase/build.properties b/libappbase/build.properties index bebcdb7..93f41e8 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Thu Feb 06 07:12:30 HKT 2025 +#Fri Feb 07 09:04:42 GMT 2025 stageCount=1 libraryProject=libappbase baseVersion=1.2 publishVersion=1.2.0 -buildCount=0 +buildCount=172 baseBetaVersion=1.2.1 diff --git a/libappbase/src/main/AndroidManifest.xml b/libappbase/src/main/AndroidManifest.xml index 7293655..8a9e6cf 100644 --- a/libappbase/src/main/AndroidManifest.xml +++ b/libappbase/src/main/AndroidManifest.xml @@ -7,6 +7,10 @@ android:name=".CrashHandler$CrashActiviy" android:label="CrashActiviy" android:launchMode="standard"/> + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java new file mode 100644 index 0000000..230a02a --- /dev/null +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/BaseBean.java @@ -0,0 +1,283 @@ +package cc.winboll.studio.libappbase; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2025/01/15 11:11:52 + * @Describe Json Bean 基础类。 + */ +import android.content.Context; +import android.util.JsonReader; +import android.util.JsonWriter; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; + +public abstract class BaseBean { + + public static final String TAG = "BaseBean"; + static final String BEAN_NAME = "BeanName"; + + public BaseBean() {} + + public abstract String getName(); + + public String getBeanJsonFilePath(Context context) { + + return context.getExternalFilesDir(TAG) + "/" + getName() + ".json"; + } + + public String getBeanListJsonFilePath(Context context) { + + return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json"; + } + + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + jsonWriter.name(BEAN_NAME).value(getName()); + } + + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + return false; + } + + abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException; + + public static String checkIsTheSameBeanListAndFile(String szFilePath, Class clazz) { + StringBuilder sbResult = new StringBuilder(); + String szErrorInfo = "Check Is The Same Bean List And File Error : "; + + try { + int nSameCount = 0; + int nBeanListCout = 0; + + T beanTemp = clazz.newInstance(); + String szBeanSimpleName = beanTemp.getName(); + String szListJson = FileUtils.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 (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(); + } + + 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; + } + + public static boolean parseStringToBeanList(String szBeanList, ArrayList beanList, Class clazz) { + try { + 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; + } + + @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 ""; + } + + 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 ""; + } + + + 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; + } + + public static T loadBeanFromFile(String szFilePath, Class clazz) { + try { + try { + File fTemp = new File(szFilePath); + if (fTemp.exists()) { + T beanTemp = clazz.newInstance();String szJson = FileUtils.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()); + } + + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return null; + } + + public static boolean saveBean(Context context, T bean) { + return saveBeanToFile(bean.getBeanJsonFilePath(context), bean); + } + + public static boolean saveBeanToFile(String szFilePath, T bean) { + try { + String szJson = bean.toString(); + FileUtils.writeStringToFile(szFilePath, szJson); + return true; + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return false; + } + + 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; + } + + public static boolean loadBeanListFromFile(String szFilePath, ArrayList beanList, Class clazz) { + try { + File fTemp = new File(szFilePath); + if (fTemp.exists()) { + String szListJson = FileUtils.readStringFromFile(szFilePath); + return parseStringToBeanList(szListJson, beanList, clazz); + } + } 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; + } + + public static boolean saveBeanListToFile(String szFilePath, ArrayList beanList) { + try { + String szJson = toStringByBeanList(beanList); + FileUtils.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; + } +} diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java index 2fce15d..7257c05 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/CrashHandler.java @@ -18,17 +18,24 @@ import android.content.res.Resources; import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; +import cc.winboll.studio.libappbase.GlobalApplication; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHandler; @@ -37,11 +44,22 @@ import java.util.Date; import java.util.Locale; public final class CrashHandler { + public static final String TAG = "CrashHandler"; + + final static String PREFS = CrashHandler.class.getName() + "PREFS"; + final static String PREFS_CRASHHANDLER_ISCRASHHAPPEN = "PREFS_CRASHHANDLER_ISCRASHHAPPEN"; + + public static String _CrashCountFilePath; public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler(); + + + public static void init(Application app) { + _CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat"; init(app, null); + LogUtils.d(TAG, "init"); } public static void init(final Application app, final String crashDir) { @@ -90,6 +108,7 @@ public final class CrashHandler { sb.append("App VersionName : ").append(versionName).append("\n"); sb.append("App VersionCode : ").append(versionCode).append("\n"); sb.append("AppBase GlobalApplication Debug Mode : ").append(GlobalApplication.isDebuging).append("\n"); + sb.append("CrashHandler CurrentSafeLevel : ").append(String.format("%d", AppCrashSafetyWire.getInstance().getCurrentSafetyLevel())).append("\n"); sb.append("************* Crash Head ****************\n"); sb.append("\n").append(fullStackTrace); @@ -100,12 +119,31 @@ public final class CrashHandler { } catch (IOException ignored) {} gotoCrashActiviy: { - Intent intent = new Intent(app, CrashActiviy.class); + Intent intent = new Intent(); + + if (AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK() && AppCrashSafetyWire.getInstance().postCrashSafetyWire(app)) { + AppCrashSafetyWire.getInstance().reset(); + intent.setClass(app, GlobalCrashActiviy.class); + intent.putExtra(GlobalCrashActiviy.EXTRA_CRASH_INFO, errorLog); + // 如果发生了 CrashHandler 内部崩溃, 就调用基础的应用崩溃显示类 +// intent.setClass(app, GlobalCrashActiviy.class); +// intent.putExtra(GlobalCrashActiviy.EXTRA_CRASH_INFO, errorLog); + } else { + AppCrashSafetyWire.getInstance().reset(); + // 正常状态调用进阶的应用崩溃显示页 + intent.setClass(app, CrashActiviy.class); + intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog); + } + +// intent.setClass(app, CrashActiviy.class); +// intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog); + + intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK ); - intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog); + try { app.startActivity(intent); android.os.Process.killProcess(android.os.Process.myPid()); @@ -114,6 +152,10 @@ public final class CrashHandler { e.printStackTrace(); if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); + } catch (Exception e) { + e.printStackTrace(); + if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) + DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); } } @@ -135,6 +177,113 @@ public final class CrashHandler { }); } + // + // 应用崩溃保险丝 + // + public static final class AppCrashSafetyWire { + + volatile static AppCrashSafetyWire _AppCrashSafetyWire; + + volatile int currentSafetyLevel; // 熔断值,为 0 表示熔断了。 + private static final int _MINI = 1; + private static final int _MAX = 5; + + AppCrashSafetyWire() { + currentSafetyLevel = loadCurrentSafetyLevel(); + } + + public static synchronized AppCrashSafetyWire getInstance() { + if (_AppCrashSafetyWire == null) { + _AppCrashSafetyWire = new AppCrashSafetyWire(); + } + return _AppCrashSafetyWire; + } + + public void setCurrentSafetyLevel(int currentSafetyLevel) { + this.currentSafetyLevel = currentSafetyLevel; + } + + public int getCurrentSafetyLevel() { + return currentSafetyLevel; + } + + public void saveCurrentSafetyLevel(int currentSafetyLevel) { + this.currentSafetyLevel = currentSafetyLevel; + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(_CrashCountFilePath)); + oos.writeInt(currentSafetyLevel); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public int loadCurrentSafetyLevel() { + try { + File f = new File(_CrashCountFilePath); + if (f.exists()) { + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(_CrashCountFilePath)); + currentSafetyLevel = ois.readInt(); + } else { + currentSafetyLevel = _MAX; + saveCurrentSafetyLevel(currentSafetyLevel); + } + } catch (IOException e) { + e.printStackTrace(); + } + return currentSafetyLevel; + } + + boolean putOnSafetyWire() { + // 崩溃计数进入崩溃保险值 + int safeLevel = loadCurrentSafetyLevel(); + if (isSafetyWireOK(safeLevel)) { + // 如果保险丝未熔断, 就减少一次熔断值 + saveCurrentSafetyLevel(safeLevel - 1); + return true; + } + + + return false; + } + + boolean isSafetyWireOK(int safetyLevel) { + if (safetyLevel >= _MINI && safetyLevel <= _MAX) { + // 如果在保险值之内 + return true; + } + return false; + } + + void reset() { + saveCurrentSafetyLevel(_MAX); + } + + void off() { + saveCurrentSafetyLevel(_MINI); + } + + boolean isAppCrashSafetyWireOK() { + return false; + } + + // 调用函数以启用持续崩溃保险,从而调用 CrashHandler 内部崩溃处理窗口 + boolean postCrashSafetyWire(final Context context) { + if (AppCrashSafetyWire.getInstance().putOnSafetyWire()) { + // 设置内部崩溃处理模块失效 + new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){ + @Override + public void run() { + // 进程持续运行时重置保险丝 + //AppCrashSafetyWire.getInstance().reset(); + } + }, 1000); + return true; + } + return false; + } + + } + public static final class CrashActiviy extends Activity implements MenuItem.OnMenuItemClickListener { private static final String EXTRA_CRASH_INFO = "crashInfo"; @@ -147,11 +296,13 @@ public final class CrashHandler { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO); setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); setContentView: { ScrollView contentView = new ScrollView(this); contentView.setFillViewport(true); + HorizontalScrollView hw = new HorizontalScrollView(this); hw.setBackgroundColor(Color.GRAY); TextView message = new TextView(this); { @@ -161,6 +312,7 @@ public final class CrashHandler { message.setTextIsSelectable(true); } hw.addView(message); + contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); setContentView(contentView); } @@ -215,5 +367,99 @@ public final class CrashHandler { return true; } } + + public static final class GlobalCrashActiviy extends Activity implements MenuItem.OnMenuItemClickListener { + + private static final String EXTRA_CRASH_INFO = "crashInfo"; + + private static final int MENUITEM_COPY = 0; + private static final int MENUITEM_RESTART = 1; + + private String mLog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO); + setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); + setContentView: { + ScrollView contentView = new ScrollView(this); + contentView.setFillViewport(true); + + LinearLayout llTitle = new LinearLayout(this); + TextView title = new TextView(this); { + int padding = dp2px(16); + title.setPadding(padding, padding, padding, padding); + title.setText("GlobalCrashActiviy"); + } + llTitle.addView(title, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + contentView.addView(llTitle, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + + HorizontalScrollView hw = new HorizontalScrollView(this); + hw.setBackgroundColor(Color.GRAY); + TextView message = new TextView(this); { + int padding = dp2px(16); + message.setPadding(padding, padding, padding, padding); + message.setText(mLog); + message.setTextIsSelectable(true); + } + hw.addView(message); + + contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.FILL_PARENT); + setContentView(contentView); + } + } + + @Override + public void onBackPressed() { + restart(); + } + + 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); + } + finish(); + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(0); + } + + private int dp2px(final float dpValue) { + final float scale = Resources.getSystem().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + 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(); + break; + case MENUITEM_RESTART: + restart(); + break; + } + return 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); + return true; + } + } + + } diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/FileUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/FileUtils.java new file mode 100644 index 0000000..bbf4003 --- /dev/null +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/FileUtils.java @@ -0,0 +1,120 @@ +package cc.winboll.studio.libappbase; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/07/19 14:30:57 + * @Describe 文件工具类 + */ +import android.content.Context; +import android.content.res.AssetManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileUtils { + + public static final String TAG = "FileUtils"; + + public static void copyAssetsToSD(Context context, String szSrcAssets, String szDstSD) { + LogUtils.d(TAG, "copyAssetsToSD [" + szSrcAssets + "] to [" + szDstSD + "]"); + AssetManager assetManager = context.getAssets(); + InputStream inputStream = null; + OutputStream outputStream = null; + try { + inputStream = assetManager.open(szSrcAssets); + File outputFile = new File(szDstSD); + outputStream = new FileOutputStream(outputFile); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + outputStream.flush(); + LogUtils.d(TAG, "copyAssetsToSD done."); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + } + } + } + + // + // 把字符串写入文件,指定 UTF-8 编码 + // + public static void writeStringToFile(String szFilePath, String szContent) throws IOException { + File file = new File(szFilePath); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + FileOutputStream outputStream = new FileOutputStream(file); + OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); + writer.write(szContent); + writer.close(); + } + + // + // 读取文件到字符串,指定 UTF-8 编码 + // + public static String readStringFromFile(String szFilePath) throws IOException { + File file = new File(szFilePath); + FileInputStream inputStream = new FileInputStream(file); + InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + StringBuilder content = new StringBuilder(); + int character; + while ((character = reader.read()) != -1) { + content.append((char) character); + } + reader.close(); + return content.toString(); + } + + public static boolean copyFile(File srcFile, File dstFile) { + if (!srcFile.exists()) { + LogUtils.d(TAG, "The original file does not exist."); + } else { + try { + // 源文件路径 + Path sourcePath = Paths.get(srcFile.getPath()); + // 目标文件路径 + Path destPath = Paths.get(dstFile.getPath()); + // 建立目标父级文件夹 + if (!dstFile.getParentFile().exists()) { + dstFile.getParentFile().mkdirs(); + } + // 删除旧的目标文件 + if (dstFile.exists()) { + dstFile.delete(); + } + // 拷贝文件 + Files.copy(sourcePath, destPath); + LogUtils.d(TAG, "File copy successfully."); + return true; + } catch (Exception e) { + LogUtils.d(TAG, e, 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 38c9da4..d48a3f7 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalApplication.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/GlobalApplication.java @@ -20,10 +20,13 @@ import java.io.InputStream; import java.io.OutputStream; public class GlobalApplication extends Application { - + + public static final String TAG = "GlobalApplication"; + final static String PREFS = GlobalApplication.class.getName() + "PREFS"; final static String PREFS_ISDEBUGING = "PREFS_ISDEBUGING"; + private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); // 是否处于调试状态 @@ -57,13 +60,15 @@ public class GlobalApplication extends Application { @Override public void onCreate() { super.onCreate(); - // 设置应用异常处理窗口 CrashHandler.init(this); - + // 设置应用调试状态 SharedPreferences sharedPreferences = getSharedPreferences(PREFS, Context.MODE_PRIVATE); GlobalApplication.isDebuging = sharedPreferences.getBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging); + + LogUtils.init(this); + LogUtils.d(TAG, "onCreate"); } public static void write(InputStream input, OutputStream output) throws IOException { diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java new file mode 100644 index 0000000..94b7827 --- /dev/null +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java @@ -0,0 +1,369 @@ +package cc.winboll.studio.libappbase; +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/08/12 13:44:06 + * @Describe LogUtils + * @Describe 应用日志类 + */ +import android.content.Context; +import cc.winboll.studio.libappbase.GlobalApplication; +import dalvik.system.DexFile; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + + +public class LogUtils { + + public static final String TAG = "LogUtils"; + + public static enum LOG_LEVEL { Off, Error, Warn, Info, Debug, Verbose } + + static volatile boolean _IsInited = false; + static Context _mContext; + // 日志显示时间格式 + static SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmSS]", Locale.getDefault()); + // 应用日志文件夹 + static File _mfLogCacheDir; + static File _mfLogDataDir; + // 应用日志文件 + static File _mfLogCatchFile; + static File _mfLogUtilsBeanFile; + static LogUtilsBean _mLogUtilsBean; + public static Map mapTAGList = new HashMap(); + + // + // 初始化函数 + // + public static void init(Context context) { + _mContext = context; + init(context, LOG_LEVEL.Off); + } + + // + // 初始化函数 + // + public static void init(Context context, LOG_LEVEL logLevel) { + if (GlobalApplication.isDebuging()) { + // 初始化日志缓存文件路径 + _mfLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG); + if (!_mfLogCacheDir.exists()) { + _mfLogCacheDir.mkdirs(); + } + _mfLogCatchFile = new File(_mfLogCacheDir, "log.txt"); + + // 初始化日志配置文件路径 + _mfLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG); + if (!_mfLogDataDir.exists()) { + _mfLogDataDir.mkdirs(); + } + _mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json"); + } else { + // 初始化日志缓存文件路径 + _mfLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG); + if (!_mfLogCacheDir.exists()) { + _mfLogCacheDir.mkdirs(); + } + _mfLogCatchFile = new File(_mfLogCacheDir, "log.txt"); + + // 初始化日志配置文件路径 + _mfLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG); + if (!_mfLogDataDir.exists()) { + _mfLogDataDir.mkdirs(); + } + _mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json"); + } + +// Toast.makeText(context, +// "_mfLogUtilsBeanFile : " + _mfLogUtilsBeanFile +// + "\n_mfLogCatchFile : " + _mfLogCatchFile, +// Toast.LENGTH_SHORT).show(); +// + _mLogUtilsBean = LogUtilsBean.loadBeanFromFile(_mfLogUtilsBeanFile.getPath(), LogUtilsBean.class); + if (_mLogUtilsBean == null) { + _mLogUtilsBean = new LogUtilsBean(); + _mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean); + } + + // 加载当前应用下的所有类的 TAG + addClassTAGList(); + loadTAGBeanSettings(); + _IsInited = true; + LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); + } + + public static Map getMapTAGList() { + return mapTAGList; + } + + static void loadTAGBeanSettings() { + ArrayList list = new ArrayList(); + LogUtilsClassTAGBean.loadBeanList(_mContext, list, LogUtilsClassTAGBean.class); + for (int i = 0; i < list.size(); i++) { + LogUtilsClassTAGBean beanSetting = list.get(i); + for (Map.Entry entry : mapTAGList.entrySet()) { + if (entry.getKey().equals(beanSetting.getTag())) { + entry.setValue(beanSetting.getEnable()); + } + } + + } + } + + static void saveTAGBeanSettings() { + ArrayList list = new ArrayList(); + for (Map.Entry entry : mapTAGList.entrySet()) { + list.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue())); + } + LogUtilsClassTAGBean.saveBeanList(_mContext, list, LogUtilsClassTAGBean.class); + } + + static void addClassTAGList() { + //ClassLoader classLoader = getClass().getClassLoader(); + try { + //String packageName = context.getPackageName(); + String packageNamePrefix = "cc.winboll.studio"; + List classNames = new ArrayList<>(); + String apkPath = _mContext.getPackageCodePath(); + //Log.d("APK_PATH", "The APK path is: " + apkPath); + LogUtils.d(TAG, String.format("apkPath : %s", apkPath)); + //String apkPath = "/data/app/" + packageName + "-"; + + //DexFile dexfile = new DexFile(apkPath + "1/base.apk"); + DexFile dexfile = new DexFile(apkPath); + + int countTemp = 0; + Enumeration entries = dexfile.entries(); + while (entries.hasMoreElements()) { + countTemp++; + String className = entries.nextElement(); + if (className.startsWith(packageNamePrefix)) { + classNames.add(className); + } + } + + LogUtils.d(TAG, String.format("countTemp : %d\nClassNames size : %d", countTemp, classNames.size())); + + for (String className : classNames) { + try { + Class clazz = Class.forName(className); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && field.getType() == String.class && "TAG".equals(field.getName())) { + String tagValue = (String) field.get(null); + //Log.d("TAG_INFO", "Class: " + className + ", TAG value: " + tagValue); + //LogUtils.d(TAG, String.format("Tag Value : %s", tagValue)); + //mapTAGList.put(tagValue, true); + mapTAGList.put(tagValue, false); + } + } + } catch (NoClassDefFoundError | ClassNotFoundException | IllegalAccessException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + //LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + //Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + //Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + public static void setTAGListEnable(String tag, boolean isEnable) { + Iterator> iterator = mapTAGList.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (tag.equals(entry.getKey())) { + entry.setValue(isEnable); + //System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); + break; + } + } + saveTAGBeanSettings(); + LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); + } + + public static void setALlTAGListEnable(boolean isEnable) { + Iterator> iterator = mapTAGList.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + entry.setValue(isEnable); + //System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); + } + saveTAGBeanSettings(); + LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); + } + + public static void setLogLevel(LOG_LEVEL logLevel) { + LogUtils._mLogUtilsBean.setLogLevel(logLevel); + _mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean); + } + + public static LOG_LEVEL getLogLevel() { + return LogUtils._mLogUtilsBean.getLogLevel(); + } + + static boolean isLoggable(String tag, LOG_LEVEL logLevel) { + return _IsInited && mapTAGList.get(tag) && isInTheLevel(logLevel); + } + + static boolean isInTheLevel(LOG_LEVEL logLevel) { + return (LogUtils._mLogUtilsBean.getLogLevel().ordinal() == logLevel.ordinal() + || LogUtils._mLogUtilsBean.getLogLevel().ordinal() > logLevel.ordinal()); + } + + // + // 获取应用日志文件夹 + // + public static File getLogCacheDir() { + return _mfLogCacheDir; + } + + // + // 调试日志写入函数 + // + public static void e(String szTAG, String szMessage) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Error)) { + saveLog(szTAG, LogUtils.LOG_LEVEL.Error, szMessage); + } + } + + // + // 调试日志写入函数 + // + public static void w(String szTAG, String szMessage) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Warn)) { + saveLog(szTAG, LogUtils.LOG_LEVEL.Warn, szMessage); + } + } + + // + // 调试日志写入函数 + // + public static void i(String szTAG, String szMessage) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Info)) { + saveLog(szTAG, LogUtils.LOG_LEVEL.Info, szMessage); + } + } + + // + // 调试日志写入函数 + // + public static void d(String szTAG, String szMessage) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { + saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, szMessage); + } + } + + // + // 调试日志写入函数 + // 包含线程调试堆栈信息 + // + public static void d(String szTAG, String szMessage, StackTraceElement[] listStackTrace) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { + StringBuilder sbMessage = new StringBuilder(szMessage); + sbMessage.append(" \nAt "); + sbMessage.append(listStackTrace[2].getMethodName()); + sbMessage.append(" ("); + sbMessage.append(listStackTrace[2].getFileName()); + sbMessage.append(":"); + sbMessage.append(listStackTrace[2].getLineNumber()); + sbMessage.append(")"); + saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString()); + } + } + + // + // 调试日志写入函数 + // 包含异常信息和线程调试堆栈信息 + // + public static void d(String szTAG, Exception e, StackTraceElement[] listStackTrace) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { + StringBuilder sbMessage = new StringBuilder(e.getClass().toGenericString()); + sbMessage.append(" : "); + sbMessage.append(e.getMessage()); + sbMessage.append(" \nAt "); + sbMessage.append(listStackTrace[2].getMethodName()); + sbMessage.append(" ("); + sbMessage.append(listStackTrace[2].getFileName()); + sbMessage.append(":"); + sbMessage.append(listStackTrace[2].getLineNumber()); + sbMessage.append(")"); + saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString()); + } + } + + // + // 调试日志写入函数 + // + public static void v(String szTAG, String szMessage) { + if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Verbose)) { + saveLog(szTAG, LogUtils.LOG_LEVEL.Verbose, szMessage); + } + } + + // + // 日志文件保存函数 + // + static void saveLog(String szTAG, LogUtils.LOG_LEVEL logLevel, String szMessage) { + try { + BufferedWriter out = null; + out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_mfLogCatchFile, true), "UTF-8")); + out.write("[" + logLevel + "] " + mSimpleDateFormat.format(System.currentTimeMillis()) + " [" + szTAG + "]\n" + szMessage + "\n"); + out.close(); + } catch (IOException e) { + LogUtils.d(TAG, "IOException : " + e.getMessage()); + } + } + + // + // 历史日志加载函数 + // + public static String loadLog() { + if (_mfLogCatchFile.exists()) { + StringBuffer sb = new StringBuffer(); + try { + BufferedReader in = null; + in = new BufferedReader(new InputStreamReader(new FileInputStream(_mfLogCatchFile), "UTF-8")); + String line = ""; + while ((line = in.readLine()) != null) { + sb.append(line); + sb.append("\n"); + } + } catch (IOException e) { + LogUtils.d(TAG, "IOException : " + e.getMessage()); + } + return sb.toString(); + } + return ""; + } + + // + // 清理日志函数 + // + public static void cleanLog() { + if (_mfLogCatchFile.exists()) { + try { + FileUtils.writeStringToFile(_mfLogCatchFile.getPath(), ""); + //LogUtils.d(TAG, "cleanLog"); + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + } +} diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtilsBean.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtilsBean.java new file mode 100644 index 0000000..6cc415d --- /dev/null +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtilsBean.java @@ -0,0 +1,70 @@ +package cc.winboll.studio.libappbase; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/08/23 15:39:07 + * @Describe LogUtils 数据配置类。 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import java.io.IOException; +public class LogUtilsBean extends BaseBean { + + public static final String TAG = "LogUtilsBean"; + + LogUtils.LOG_LEVEL logLevel; + + public LogUtilsBean() { + this.logLevel = LogUtils.LOG_LEVEL.Off; + } + + public LogUtilsBean(LogUtils.LOG_LEVEL logLevel) { + this.logLevel = logLevel; + } + + public void setLogLevel(LogUtils.LOG_LEVEL logLevel) { + this.logLevel = logLevel; + } + + public LogUtils.LOG_LEVEL getLogLevel() { + return logLevel; + } + + @Override + public String getName() { + return LogUtilsBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + LogUtilsBean bean = this; + jsonWriter.name("logLevel").value(bean.getLogLevel().ordinal()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("logLevel")) { + setLogLevel(LogUtils.LOG_LEVEL.values()[jsonReader.nextInt()]); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtilsClassTAGBean.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtilsClassTAGBean.java new file mode 100644 index 0000000..d3d4505 --- /dev/null +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtilsClassTAGBean.java @@ -0,0 +1,87 @@ +package cc.winboll.studio.libappbase; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2025/01/04 14:17:02 + * @Describe 日志类class TAG 标签数据类 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import java.io.IOException; + +public class LogUtilsClassTAGBean extends BaseBean { + + public static final String TAG = "LogUtilsClassTAGBean"; + + // 标签名 + String tag; + // 是否启用 + Boolean enable; + + public LogUtilsClassTAGBean() { + this.tag = TAG; + this.enable = true; + } + + public LogUtilsClassTAGBean(String tag, Boolean enable) { + this.tag = tag; + this.enable = enable; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getTag() { + return tag; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } + + public Boolean getEnable() { + return enable; + } + + @Override + public String getName() { + return LogUtilsClassTAGBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + LogUtilsClassTAGBean bean = this; + jsonWriter.name("tag").value(bean.getTag()); + jsonWriter.name("enable").value(bean.getEnable()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("tag")) { + setTag(jsonReader.nextString()); + } else if (name.equals("enable")) { + setEnable(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +}