啰嗦两句,这两天项目中遇到问题是出现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中查看,!~!~!~