/**
-
将Log信息写入文件
-
isDouble为是否连续两次写入,防止连续两次上传服务器。
-
@param level
-
@param type
-
@param logData
*/
public static void writeToFile(String level, String logData) {
if (null == logPath) {
LogUtil.e(TAG, “logPath == null ,未初始化LogToFile”);
return;
}
String fileName = logPath + “/AppLogs_Android.log”;
StackTraceElement caller = getCallerStackTraceElement();
// 获取到类名
String callerClazzName = caller.getClassName();
callerClazzName = callerClazzName.substring(callerClazzName
.lastIndexOf(“.”) + 1);
//要写入的LOG内容
String log = type + " - " + callerClazzName + " - " + caller.getMethodName() + " - line " + caller.getLineNumber() + " - " + logData + “\n”;
LogUtil.d(TAG, log);
//如果父路径不存在
File file = new File(logPath);
if (!file.exists()) {
file.mkdirs();//创建父路径
}
FileOutputStream fos = null;
BufferedWriter bw = null;
try {
fos = new FileOutputStream(fileName, true);
bw = new BufferedWriter(new OutputStreamWriter(fos));
bw.write(log);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();//关闭缓冲流
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (getLogsFileSize(fileName) >= 1f) {
sendToServer(fileName);
} else if (level == CRASH_LEVEL || level == ERROR_LEVEL || level == WARNING_LEVEL) {
sendToServer(fileName);
}
}
上面有一个巨顶的方法:
Thread.currentThread().getStackTrace()[5]
这行函数能够得离当前执行代码的指令。(注:不一定是5)
因为在执行命令时,指令栈保存当前线程最近执行的代码。
它的原理是这样的:
1、MainActivity : LogTool.d(xxx,xxx)
跳转-》
2、LogTool: d(xxx,xx){wirteToFile(xxx,xxx)}
跳转-》
3、LogTool:writeToFile(xxx){}
假如上面是一个指令栈,那么我就去获取这个栈的栈底元素 [1] MainActivity… 这一行,得到的是个StackTraceElement
对象。
得到该对象之后,我们可以将该指令反射,通过getClassName()
获取类名,通过getMethodName()
获取方法名、通过getLineNumber()
获取当前行数,就省的我们一步一步去找具体哪行了。
所以上面代码中的第5行只是我这边的情况,别的代码就要自己去推理具体在哪一行咯。
接下来就是上传到服务器,这里使用Okhttp,上传的格式是包括文件+一些附带String信息,所以使用mutipart来提交表单,并且开启一个线程来提交。
/**
-
上传Log文件至服务器
-
@return
*/
public static void sendToServer(final String pathName) {
new Thread(new Runnable() {
@Override
public void run() {
final File file = new File(pathName);
MediaType MEDIA_TYPE_TXT = MediaType.parse(“text/plain”);
RequestBody fileBody = MultipartBody.create(MEDIA_TYPE_TXT, file);
MultipartBody multiBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(“log_file”, file.getName(), fileBody)
.addFormDataPart(“system”, “Android”)
.addFormDataPart(“phone_number”, “110”).build();
Request request = new Request.Builder().url(apiUrl).post(multiBuilder).build();
OkHttpClient okHttpClient = new OkHttpClient();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败的处理
LogUtil.e(TAG, "上传Log文件失败 : " + e.toString());
if (getLogsFileSize(pathName) > 1f) {
file.delete();
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功的处理
LogUtil.d(TAG, "上传Log文件成功 : " + response.body().string());
//清空文件
file.delete();
}
});
}
}).start();
}
到这里就上传完啦。
但是我们还没有关注Crash的捕捉,因为我们自己无法判断Crash是出现在什么地方的,所以我们要写一个全局的Crash捕捉器,而Android提供了这个类,叫:Thread.UncaughtExceptionHandler,我们通过实现该接口,重写uncaughtException()方法,来处理遇到异常的情况。
该类如下(也需要在App初始化时init):
public class CrashHandlerManager implements Thread.UncaughtExceptionHandler {
private static final String TAG = “CrashHandlerManager”;
private static CrashHandlerManager instance;
private Context context;
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
//收集信息集合
private Map<String, String> infoMap = new HashMap<>();
public synchronized static CrashHandlerManager getInstance() {
if (instance == null) {
instance = new CrashHandlerManager();
}
return instance;
}
private CrashHandlerManager() {
}
/**
-
初始化程序异常处理器
-
@param context
*/
public void initCrash(Context context) {
this.context = context;
//获取系统默认的UncaughtException处理器
uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序得默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
-
当UncaughtException发生会进入此方法
-
@param t
-
@param e
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
if (!handleException(e) && uncaughtExceptionHandler != null) {
//如果自定义没有处理就交给系统去处理
uncaughtExceptionHandler.uncaughtException(t, e);
}
}
private boolean handleException(Throwable e) {
if (e == null) {
return false;
}
collectionDeviceInfo(context, e.toString());
return true;
}
/**
-
收集错误处理信息
-
@param context
-
@param e
*/
private void collectionDeviceInfo(Context context, String e) {
//获得包管理器
try {
PackageManager pm = context.getPackageManager();
//获取该应用信息
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
e.printStackTrace(printWriter);
StringBuffer stringBuffer = stringWriter.getBuffer();
e.printStackTrace();
String versionName = pi.versionName == null ? “null” : pi.versionName;
String versionCode = pi.versionCode + “”;
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
[外链图片转存中…(img-Em1WXYIa-1719501458726)]
[外链图片转存中…(img-6eMMjJpc-1719501458727)]
[外链图片转存中…(img-fOVZr5NT-1719501458727)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。