android 實現收集bug存儲本地併發送給後臺服務器

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

資源連接Demo


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章