囉嗦兩句,這兩天項目中遇到問題是出現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中查看,!~!~!~