啰嗦两句,这两天项目中遇到问题是出现bug没有办法查看,我们的项目是放到定制设备上的,由于项目中没有用友盟统计,这边bug的收集遇到了一下问题,就想着将bug保存本地,然后,上传服务器,开始从网上找了一些资料,但是很多都是没有实现成功,之后多找了一些资料,也算是拼凑吧,但是总算将功能完善了,特在此记录下
对了,项目中,需要引入两个包,fastjson是我项目中导入的jar包,最下面demo里面有,你可以从网上自己下载
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
implementation files('libs/fastjson-1.2.9-SNAPSHOT.jar')
1、首先你要将项目中放入写入本地文件的权限,网络权限,读取内存权限,这里你需要注意一下,有个问题就是,当你的手机版本在6.0以上的时候,你这里设置的权限是没有效果的,不相信的话,你可以试一下,当你运行后,一定没有办法保存log到本地,你手机设置里面找到应用你会发现,里面存储的权限并没有打开,你需要自己设定下,让用户手动打开
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
2、代码我稍微粘贴下吧,当Activity记载的时候,调用这个方法
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
onCallPermission();
}
/**
* 判断权限是否有
* 没有就授权
*/
public void onCallPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//判断当前系统的SDK版本是否大于23
//如果当前申请的权限没有授权
if (!(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) {
//第一次请求权限的时候返回false,第二次shouldShowRequestPermissionRationale返回true
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Toast.makeText(this, "Please grant the permission this time", Toast.LENGTH_LONG).show();
}
//请求权限
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
initData();//这里是你获取到权限后的操作
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 1) {
if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//加载所有数据
initData();
} else {//没有获得到权限
Toast.makeText(this, "你没有获取到权限", Toast.LENGTH_SHORT).show();
}
}
}
3、下面就是我们需要写的一个工具类,功能就是要将我们的错误log进行手机,然后上传至后台服务器,CrashHandler.java
package com.example.administrator.demo.utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.alibaba.fastjson.JSON;
import com.example.administrator.demo.SpUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = CrashHandler.class.getSimpleName();
private static final String SINGLE_RETURN = "\n";
private static final String SINGLE_LINE = "--------------------------------";
private static CrashHandler mCrashHandler;
private Context mContext;
private UncaughtExceptionHandler mDefaultHandler;
private StringBuffer mErrorLogBuffer = new StringBuffer();
private String format;
private String path;
private String sendNetUrl = "http://1xxxxxxxxxxx";
/**
* 获取CrashHandler实例,单例模式。
*
* @return 返回CrashHandler实例
*/
public static CrashHandler getInstance() {
if (mCrashHandler == null) {
synchronized (CrashHandler.class) {
if (mCrashHandler == null) {
mCrashHandler = new CrashHandler();
}
}
}
return mCrashHandler;
}
public void init(Context context) {
mContext = context;
// 获取系统默认的uncaughtException处理类实例
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置成我们处理uncaughtException的类
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.d(TAG, "uncaughtException:" + ex);
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理异常就由系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
android.os.Process.killProcess(android.os.Process.myPid());
}
}
//处理异常事件
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT)
.show();
Looper.loop();
}
}).start();
// 收集设备参数信息
collectDeviceInfo(mContext);
// 收集错误日志
collectCrashInfo(ex);
// 保存错误日志
saveErrorLog();
//TODO: 这里可以加一个网络的请求,发送错误log给后台
sendErrorLog(sendNetUrl);
return true;
}
//保存日志到/mnt/sdcard/AppLog/目录下,文件名已时间yyyy-MM-dd_hh-mm-ss.log的形式保存
private void saveErrorLog() {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh-mm-ss", Locale.getDefault());
format = "荣耀5c" + sdf.format(new Date());
format += ".log";
path = Environment.getExternalStorageDirectory().getPath() + "/AppLog/";
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
} else {
clearExLogWhenMax(file);
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path + format);
fos.write(mErrorLogBuffer.toString().getBytes());
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Log.e("liushengjie", "over");
}
//收集错误信息
private void collectCrashInfo(Throwable ex) {
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
String result = info.toString();
printWriter.close();
//将错误信息加入mErrorLogBuffer中
append("", result);
mErrorLogBuffer.append(SINGLE_LINE + SINGLE_RETURN);
}
//收集应用和设备信息
private void collectDeviceInfo(Context context) {
//每次使用前,清掉mErrorLogBuffer里的内容
mErrorLogBuffer.setLength(0);
mErrorLogBuffer.append(SINGLE_RETURN + SINGLE_LINE + SINGLE_RETURN);
//获取应用的信息
PackageManager pm = context.getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo(context.getPackageName(),
PackageManager.GET_ACTIVITIES);
if (pi != null) {
append("versionCode", pi.versionCode);
append("versionName", pi.versionName);
append("packageName", pi.packageName);
}
} catch (NameNotFoundException e) {
e.printStackTrace();
}
mErrorLogBuffer.append(SINGLE_LINE + SINGLE_RETURN);
//获取设备的信息
Field[] fields = Build.class.getDeclaredFields();
getDeviceInfoByReflection(fields);
fields = Build.VERSION.class.getDeclaredFields();
getDeviceInfoByReflection(fields);
mErrorLogBuffer.append(SINGLE_LINE + SINGLE_RETURN);
}
//获取设备的信息通过反射方式
private void getDeviceInfoByReflection(Field[] fields) {
for (Field field : fields) {
try {
field.setAccessible(true);
append(field.getName(), field.get(null));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
//mErrorLogBuffer添加友好的log信息
private void append(String key, Object value) {
mErrorLogBuffer.append("" + key + ":" + value + SINGLE_RETURN);
}
/**
* 设置最大日志数量 10
*
* @param logDir 日志目录
*/
private void clearExLogWhenMax(File logDir) {
File[] logFileList = logDir.listFiles();
if (logFileList == null || logFileList.length == 0) {
return;
}
int length = logFileList.length;
if (length >= 5) {
for (File aFile : logFileList) {
try {
if (aFile.delete()) {
Log.d(TAG, "clearExLogWhenMax:" + aFile.getName());
}
} catch (Exception ex) {
Log.d(TAG, "clearExLogWhenMax:" + ex);
}
}
}
}
private String json = "";
private int a = 0;
private void sendErrorLog(String url) {
//创建OkHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
File file = new File(path, format);
//application/octet-stream 表示类型是二进制流,不知文件具体类型
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody requestBody = new MultipartBody.Builder("AaB03x")
.setType(MultipartBody.FORM)
.addFormDataPart("file", null, new MultipartBody.Builder("BbC04y")
.addPart(Headers.of("Content-Disposition", "form-data;name=\"mFile\";filename=" + format), fileBody)
.build())
.addFormDataPart("proname", "ssss")
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
SpUtil.saveFile("errorlog", format);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Map jsonObject = JSON.parseObject(response.body().string());
if (jsonObject.get("state").equals("0")) {
SpUtil.saveFile("errorlog", "null");
} else {
SpUtil.saveFile("errorlog", format);
}
}
});
}
}
package com.example.administrator.demo.utils;
4、这里用到了sp存储,那就再来个sp工具类吧,这是我自己写的,你可以用你自己的,会用就行SpUtil.java
import android.content.Context;
import android.content.SharedPreferences;
public class SpUtil {
private static Context mcontext;
private static String PROJECT = "SpSave";//我用项目名当作大标识
public static void initSp(Context context) {
mcontext = context;
}
public static SharedPreferences getSp(String file) {
SharedPreferences sp = mcontext.getSharedPreferences(PROJECT + file, 0);
return sp;
}
/**
* 保存上传的文件
*
* @param file
*/
public static void saveFile(String file, String fileName) {
SharedPreferences sp = getSp(file);
SharedPreferences.Editor editor = sp.edit();
editor.putString("filename", fileName);
editor.commit();
}
/**
* 获取文件名
*
* @param parking
* @return
*/
public static String getFileName(String file) {
SharedPreferences sp = getSp(file);
String filename = sp.getString("filename", "null");
return filename;
}
}
5、因为sp要初始化,所以,我们需要自己写一个MyApplication 来继承系统的Application,同时,里面需要初始化两个类,代码如下:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler.getInstance().init(this);
SpUtil.initSp(this);
}
}
6、如果你自定义了MyApplication,就要在AndroidManifest.xml里面的application里面配置name
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/icon_three"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
</application>
7、我们既然已经做了sp存储,那你就要在程序的入口处,将sp存储的数据进行发送,里面sendErrorLog的方法和CrashHandler的方法一样,这里你直接copy过来就行了
String errorlog = SpUtil.getFileName("errorlog");
if (!errorlog.equals("null")) {
sendErrorLog("http://xxx/xxx/xxx", errorlog);
}
8、至此,这个手机bug保存到本地以及上传至服务器的功能就实现了,不足之处,还希望大神能批评指正,我会继续提升,力求做到更好,我上传了一个demo,如果这里看不懂,需要的话,直接去demo中查看,!~!~!~