添加日志,文件,BaseBean类,未调试。
This commit is contained in:
parent
b26b9dbbc4
commit
ea7a0bda14
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#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
|
stageCount=1
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=1.2
|
baseVersion=1.2
|
||||||
publishVersion=1.2.0
|
publishVersion=1.2.0
|
||||||
buildCount=0
|
buildCount=172
|
||||||
baseBetaVersion=1.2.1
|
baseBetaVersion=1.2.1
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#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
|
stageCount=1
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=1.2
|
baseVersion=1.2
|
||||||
publishVersion=1.2.0
|
publishVersion=1.2.0
|
||||||
buildCount=0
|
buildCount=172
|
||||||
baseBetaVersion=1.2.1
|
baseBetaVersion=1.2.1
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
android:name=".CrashHandler$CrashActiviy"
|
android:name=".CrashHandler$CrashActiviy"
|
||||||
android:label="CrashActiviy"
|
android:label="CrashActiviy"
|
||||||
android:launchMode="standard"/>
|
android:launchMode="standard"/>
|
||||||
|
<activity
|
||||||
|
android:name=".CrashHandler$GlobalCrashActiviy"
|
||||||
|
android:label="GlobalCrashActiviy"
|
||||||
|
android:launchMode="standard"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -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<T extends 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 <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 : ";
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> 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 <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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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 <T extends BaseBean> boolean saveBean(Context context, T bean) {
|
||||||
|
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends BaseBean> 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 <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = FileUtils.readStringFromFile(szFilePath);
|
||||||
|
return parseStringToBeanList(szListJson, beanList, clazz);
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -18,17 +18,24 @@ import android.content.res.Resources;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.HorizontalScrollView;
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
@ -37,11 +44,22 @@ import java.util.Date;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public final class CrashHandler {
|
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 final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void init(Application app) {
|
public static void init(Application app) {
|
||||||
|
_CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat";
|
||||||
init(app, null);
|
init(app, null);
|
||||||
|
LogUtils.d(TAG, "init");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init(final Application app, final String crashDir) {
|
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 VersionName : ").append(versionName).append("\n");
|
||||||
sb.append("App VersionCode : ").append(versionCode).append("\n");
|
sb.append("App VersionCode : ").append(versionCode).append("\n");
|
||||||
sb.append("AppBase GlobalApplication Debug Mode : ").append(GlobalApplication.isDebuging).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("************* Crash Head ****************\n");
|
||||||
sb.append("\n").append(fullStackTrace);
|
sb.append("\n").append(fullStackTrace);
|
||||||
|
|
||||||
@ -100,12 +119,31 @@ public final class CrashHandler {
|
|||||||
} catch (IOException ignored) {}
|
} catch (IOException ignored) {}
|
||||||
|
|
||||||
gotoCrashActiviy: {
|
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.addFlags(
|
||||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
|
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
);
|
);
|
||||||
intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog);
|
|
||||||
try {
|
try {
|
||||||
app.startActivity(intent);
|
app.startActivity(intent);
|
||||||
android.os.Process.killProcess(android.os.Process.myPid());
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
@ -114,6 +152,10 @@ public final class CrashHandler {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
|
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
|
||||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
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 {
|
public static final class CrashActiviy extends Activity implements MenuItem.OnMenuItemClickListener {
|
||||||
|
|
||||||
private static final String EXTRA_CRASH_INFO = "crashInfo";
|
private static final String EXTRA_CRASH_INFO = "crashInfo";
|
||||||
@ -147,11 +296,13 @@ public final class CrashHandler {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
|
mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
|
||||||
setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
|
setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
|
||||||
setContentView: {
|
setContentView: {
|
||||||
ScrollView contentView = new ScrollView(this);
|
ScrollView contentView = new ScrollView(this);
|
||||||
contentView.setFillViewport(true);
|
contentView.setFillViewport(true);
|
||||||
|
|
||||||
HorizontalScrollView hw = new HorizontalScrollView(this);
|
HorizontalScrollView hw = new HorizontalScrollView(this);
|
||||||
hw.setBackgroundColor(Color.GRAY);
|
hw.setBackgroundColor(Color.GRAY);
|
||||||
TextView message = new TextView(this); {
|
TextView message = new TextView(this); {
|
||||||
@ -161,6 +312,7 @@ public final class CrashHandler {
|
|||||||
message.setTextIsSelectable(true);
|
message.setTextIsSelectable(true);
|
||||||
}
|
}
|
||||||
hw.addView(message);
|
hw.addView(message);
|
||||||
|
|
||||||
contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
setContentView(contentView);
|
setContentView(contentView);
|
||||||
}
|
}
|
||||||
@ -215,5 +367,99 @@ public final class CrashHandler {
|
|||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -21,9 +21,12 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
public class GlobalApplication extends Application {
|
public class GlobalApplication extends Application {
|
||||||
|
|
||||||
|
public static final String TAG = "GlobalApplication";
|
||||||
|
|
||||||
final static String PREFS = GlobalApplication.class.getName() + "PREFS";
|
final static String PREFS = GlobalApplication.class.getName() + "PREFS";
|
||||||
final static String PREFS_ISDEBUGING = "PREFS_ISDEBUGING";
|
final static String PREFS_ISDEBUGING = "PREFS_ISDEBUGING";
|
||||||
|
|
||||||
|
|
||||||
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
// 是否处于调试状态
|
// 是否处于调试状态
|
||||||
@ -57,13 +60,15 @@ public class GlobalApplication extends Application {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
// 设置应用异常处理窗口
|
// 设置应用异常处理窗口
|
||||||
CrashHandler.init(this);
|
CrashHandler.init(this);
|
||||||
|
|
||||||
// 设置应用调试状态
|
// 设置应用调试状态
|
||||||
SharedPreferences sharedPreferences = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
SharedPreferences sharedPreferences = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||||
GlobalApplication.isDebuging = sharedPreferences.getBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging);
|
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 {
|
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||||
|
@ -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<String, Boolean> mapTAGList = new HashMap<String, Boolean>();
|
||||||
|
|
||||||
|
//
|
||||||
|
// 初始化函数
|
||||||
|
//
|
||||||
|
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<String, Boolean> getMapTAGList() {
|
||||||
|
return mapTAGList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadTAGBeanSettings() {
|
||||||
|
ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>();
|
||||||
|
LogUtilsClassTAGBean.loadBeanList(_mContext, list, LogUtilsClassTAGBean.class);
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
LogUtilsClassTAGBean beanSetting = list.get(i);
|
||||||
|
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
|
||||||
|
if (entry.getKey().equals(beanSetting.getTag())) {
|
||||||
|
entry.setValue(beanSetting.getEnable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void saveTAGBeanSettings() {
|
||||||
|
ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>();
|
||||||
|
for (Map.Entry<String, Boolean> 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<String> 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<String> 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<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<String, Boolean> 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<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<String, Boolean> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user