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


猜你喜欢

转载自blog.csdn.net/qaz520929/article/details/80526930