Android之崩潰日誌管理(原創)

文章大綱

1、Android崩潰日誌管理簡介
2、崩潰日誌管理實戰
3、項目源碼下載php

 

1、Android崩潰日誌管理簡介

1. 什麼是android崩潰日誌管理

  開發中有些地方未注意可能形成異常拋出未能caught到,而後彈出系統對話框強制退出。這種交互很差,並且開發者也不能及時獲取到底哪裏出問題。所以咱們可使用android的UncaughtExceptionHandler來處理這種異常。java

2. 操做邏輯

用戶端(出現崩潰)
  咱們會封裝一個通用的jar包,該jar包包括日誌打印、捕獲異常信息邏輯、網絡傳輸、設置Debug和Release模式、獲取本機的相關信息等,當出現異常時,將異常信息以文件方式保存在用戶手機中,而且發送到後臺,當後臺接收成功時,自動刪除用戶手機的崩潰信息文件,若接收失敗,在下次發生崩潰時,將歷史發送失敗的崩潰一同發送。android

接收端(後臺)
  咱們會編寫一個地址,用於接收異常的具體信息,並儲存在本地文件中,以此做爲日誌進行管理。spring

2、崩潰日誌管理實戰

1. 後臺端

  在該實戰中,我以簡單的servlet進行講解,實際項目中,能夠以ssm或spring boot等框架進行操做。服務器

/** * 接收崩潰信息,並進行打印(實際項目中,須要以文件形式歸檔) * @author wxc * */ public class Test extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲取客戶端傳送過來的信息流 BufferedReader in=new BufferedReader(new InputStreamReader(request.getInputStream())); StringBuilder sb = new StringBuilder(); String line = null; while ((line = in.readLine()) != null) { //將信息流進行打印 System.out.println(line); } } } 

2. 客戶端通用項目

網絡請求相關的配置管理類:HttpManager.java網絡

/** * * 網絡請求相關的配置管理 * * @author 吳曉暢 * */ public class HttpManager { private static final int SET_CONNECTION_TIMEOUT = 5 * 1000; private static final int SET_SOCKET_TIMEOUT = 20 * 1000; private static final String BOUNDARY = getBoundry();// UUID.randomUUID().toString(); private static final String MP_BOUNDARY = "--" + BOUNDARY; private static final String END_MP_BOUNDARY = "--" + BOUNDARY + "--"; private static final String LINEND = "\r\n"; private static final String CHARSET = "UTF-8"; public static String uploadFile(String url, HttpParameters params, File logFile) throws IOException{ HttpClient client = getHttpClient(); HttpPost post = new HttpPost(url); ByteArrayOutputStream bos = null; FileInputStream logFileInputStream = null; String result = null; try { bos = new ByteArrayOutputStream(); if(params != null){ String key = ""; for (int i = 0; i < params.size(); i++) { key = params.getKey(i); StringBuilder temp = new StringBuilder(10); temp.setLength(0); temp.append(MP_BOUNDARY).append(LINEND); temp.append("content-disposition: form-data; name=\"").append(key) .append("\"").append(LINEND + LINEND); temp.append(params.getValue(key)).append(LINEND); bos.write(temp.toString().getBytes()); } } StringBuilder temp = new StringBuilder(); temp.append(MP_BOUNDARY).append(LINEND); temp.append( "content-disposition: form-data; name=\"logfile\"; filename=\"") .append(logFile.getName()).append("\"").append(LINEND); temp.append("Content-Type: application/octet-stream; charset=utf-8").append(LINEND + LINEND); bos.write(temp.toString().getBytes()); logFileInputStream = new FileInputStream(logFile); byte[] buffer = new byte[1024*8];//8k while(true){ int count = logFileInputStream.read(buffer); if(count == -1){ break; } bos.write(buffer, 0, count); } bos.write((LINEND+LINEND).getBytes()); bos.write((END_MP_BOUNDARY+LINEND).getBytes()); ByteArrayEntity formEntity = new ByteArrayEntity(bos.toByteArray()); post.setEntity(formEntity); HttpResponse response = client.execute(post); StatusLine status = response.getStatusLine(); int statusCode = status.getStatusCode(); Log.i("HttpManager", "返回結果爲"+statusCode); if(statusCode == HttpStatus.SC_OK){ result = readHttpResponse(response); } } catch (IOException e) { throw e; }finally{ if(bos != null){ try { bos.close(); } catch (IOException e) { throw e; } } if(logFileInputStream != null){ try { logFileInputStream.close(); } catch (IOException e) { throw e; } } } return result; } private static String readHttpResponse(HttpResponse response){ String result = null; HttpEntity entity = response.getEntity(); InputStream inputStream; try { inputStream = entity.getContent(); ByteArrayOutputStream content = new ByteArrayOutputStream(); int readBytes = 0; byte[] sBuffer = new byte[512]; while ((readBytes = inputStream.read(sBuffer)) != -1) { content.write(sBuffer, 0, readBytes); } result = new String(content.toByteArray(), CHARSET); return result; } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } private static HttpClient getHttpClient() { try { KeyStore trustStore = KeyStore.getInstance(KeyStore .getDefaultType()); trustStore.load(null, null); SSLSocketFactory sf = new MySSLSocketFactory(trustStore); sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, 10000); HttpConnectionParams.setSoTimeout(params, 10000); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory .getSocketFactory(), 80)); registry.register(new Scheme("https", sf, 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager( params, registry); HttpConnectionParams.setConnectionTimeout(params, SET_CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, SET_SOCKET_TIMEOUT); HttpClient client = new DefaultHttpClient(ccm, params); return client; } catch (Exception e) { // e.printStackTrace(); return new DefaultHttpClient(); } } private static class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { // TODO Auto-generated method stub return null; } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { // TODO Auto-generated method stub } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // TODO Auto-generated method stub } }; sslContext.init(null, new TrustManager[] { tm }, null); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } } private static String getBoundry() { StringBuffer _sb = new StringBuffer(); for (int t = 1; t < 12; t++) { long time = System.currentTimeMillis() + t; if (time % 3 == 0) { _sb.append((char) time % 9); } else if (time % 3 == 1) { _sb.append((char) (65 + time % 26)); } else { _sb.append((char) (97 + time % 26)); } } return _sb.toString(); } } 

文件上傳相關類:UploadLogManager.javaapp

package com.qihoo.linker.logcollector.upload; import java.io.File; import java.io.IOException; import java.util.logging.Logger; import com.qihoo.linker.logcollector.capture.LogFileStorage; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; /** * * @author 吳曉暢 * */ public class UploadLogManager { private static final String TAG = UploadLogManager.class.getName(); private static UploadLogManager sInstance; private Context mContext; private HandlerThread mHandlerThread; private static volatile MyHandler mHandler; private volatile Looper mLooper; private volatile boolean isRunning = false; private String url; private HttpParameters params; private UploadLogManager(Context c){ mContext = c.getApplicationContext(); mHandlerThread = new HandlerThread(TAG + ":HandlerThread"); mHandlerThread.start(); } //初始化UploadLogManager類 public static synchronized UploadLogManager getInstance(Context c){ if(sInstance == null){ sInstance = new UploadLogManager(c); } return sInstance; } /** * 執行文件上傳具體操做 * * @param url * @param params */ public void uploadLogFile(String url , HttpParameters params){ this.url = url; this.params = params; mLooper = mHandlerThread.getLooper(); mHandler = new MyHandler(mLooper); if(mHandlerThread == null){ return; } if(isRunning){ return; } mHandler.sendMessage(mHandler.obtainMessage()); isRunning = true; } //用於uploadLogFile方法調用的線程 private final class MyHandler extends Handler{ public MyHandler(Looper looper) { super(looper); // TODO Auto-generated constructor stub } @Override public void handleMessage(Message msg) { File logFile = LogFileStorage.getInstance(mContext).getUploadLogFile(); if(logFile == null){ isRunning = false; return; } try { String result = HttpManager.uploadFile(url, params, logFile); Log.i("UpLoad", "服務端返回數據爲"+result); if(result != null){ Boolean isSuccess = LogFileStorage.getInstance(mContext).deleteUploadLogFile(); Log.i("UpLoad", "刪除文件結果爲"+isSuccess); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ isRunning = false; } } } } 

客戶端崩潰日誌文件的刪除,保存等操做類:LogFileStorage.java
文件保存在Android/data/包名/Log/下框架

package com.qihoo.linker.logcollector.capture; import java.io.File; import java.io.FileOutputStream; import com.qihoo.linker.logcollector.utils.LogCollectorUtility; import com.qihoo.linker.logcollector.utils.LogHelper; import android.content.Context; import android.util.Log; /** * * 客戶端崩潰日誌文件的刪除,保存等操做 * * @author 吳曉暢 * */ public class LogFileStorage { private static final String TAG = LogFileStorage.class.getName(); public static final String LOG_SUFFIX = ".log"; private static final String CHARSET = "UTF-8"; private static LogFileStorage sInstance; private Context mContext; private LogFileStorage(Context ctx) { mContext = ctx.getApplicationContext(); } public static synchronized LogFileStorage getInstance(Context ctx) { if (ctx == null) { LogHelper.e(TAG, "Context is null"); return null; } if (sInstance == null) { sInstance = new LogFileStorage(ctx); } return sInstance; } public File getUploadLogFile(){ File dir = mContext.getFilesDir(); File logFile = new File(dir, LogCollectorUtility.getMid(mContext) + LOG_SUFFIX); if(logFile.exists()){ return logFile; }else{ return null; } } //刪除客戶端中崩潰日誌文件 public boolean deleteUploadLogFile(){ File dir = mContext.getFilesDir(); File logFile = new File(dir, LogCollectorUtility.getMid(mContext) + LOG_SUFFIX); Log.i("Log", LogCollectorUtility.getMid(mContext) + LOG_SUFFIX); return logFile.delete(); } //保存文件 public boolean saveLogFile2Internal(String logString) { try { File dir = mContext.getFilesDir(); if (!dir.exists()) { dir.mkdirs(); } File logFile = new File(dir, LogCollectorUtility.getMid(mContext) + LOG_SUFFIX); FileOutputStream fos = new FileOutputStream(logFile , true); fos.write(logString.getBytes(CHARSET)); fos.close(); } catch (Exception e) { e.printStackTrace(); LogHelper.e(TAG, "saveLogFile2Internal failed!"); return false; } return true; } public boolean saveLogFile2SDcard(String logString, boolean isAppend) { if (!LogCollectorUtility.isSDcardExsit()) { LogHelper.e(TAG, "sdcard not exist"); return false; } try { File logDir = getExternalLogDir(); if (!logDir.exists()) { logDir.mkdirs(); } File logFile = new File(logDir, LogCollectorUtility.getMid(mContext) + LOG_SUFFIX); /*if (!isAppend) { if (logFile.exists() && !logFile.isFile()) logFile.delete(); }*/ LogHelper.d(TAG, logFile.getPath()); FileOutputStream fos = new FileOutputStream(logFile , isAppend); fos.write(logString.getBytes(CHARSET)); fos.close(); } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "saveLogFile2SDcard failed!"); return false; } return true; } private File getExternalLogDir() { File logDir = LogCollectorUtility.getExternalDir(mContext, "Log"); LogHelper.d(TAG, logDir.getPath()); return logDir; } } 

UncaughtExceptionHandler實現類:CrashHandler.java
  當出現異常時,會進入public void uncaughtException(Thread thread, Throwable ex) 方法中。dom

/** * * 若是須要捕獲系統的未捕獲異常(如系統拋出了未知錯誤,這種異常沒有捕獲,這將致使系統莫名奇妙的關閉,使得用戶體驗差), * 能夠經過UncaughtExceptionHandler來處理這種異常。 * * @author 吳曉暢 * */ public class CrashHandler implements UncaughtExceptionHandler { private static final String TAG = CrashHandler.class.getName(); private static final String CHARSET = "UTF-8"; private static CrashHandler sInstance; private Context mContext; private Thread.UncaughtExceptionHandler mDefaultCrashHandler; String appVerName; String appVerCode; String OsVer; String vendor; String model; String mid; //初始化該類 private CrashHandler(Context c) { mContext = c.getApplicationContext(); // mContext = c; appVerName = "appVerName:" + LogCollectorUtility.getVerName(mContext); appVerCode = "appVerCode:" + LogCollectorUtility.getVerCode(mContext); OsVer = "OsVer:" + Build.VERSION.RELEASE; vendor = "vendor:" + Build.MANUFACTURER; model = "model:" + Build.MODEL; mid = "mid:" + LogCollectorUtility.getMid(mContext); } //初始化該類 public static CrashHandler getInstance(Context c) { if (c == null) { LogHelper.e(TAG, "Context is null"); return null; } if (sInstance == null) { sInstance = new CrashHandler(c); } return sInstance; } public void init() { if (mContext == null) { return; } boolean b = LogCollectorUtility.hasPermission(mContext); if (!b) { return; } mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); } /** * 發生異常時候進來這裏 */ @Override public void uncaughtException(Thread thread, Throwable ex) { // handleException(ex); // ex.printStackTrace(); if (mDefaultCrashHandler != null) { mDefaultCrashHandler.uncaughtException(thread, ex); } else { Process.killProcess(Process.myPid()); // System.exit(1); } } //將異常信息保存成文件 private void handleException(Throwable ex) { String s = fomatCrashInfo(ex); // String bes = fomatCrashInfoEncode(ex); LogHelper.d(TAG, s); // LogHelper.d(TAG, bes); //LogFileStorage.getInstance(mContext).saveLogFile2Internal(bes); LogFileStorage.getInstance(mContext).saveLogFile2Internal(s); if(Constants.DEBUG){ LogFileStorage.getInstance(mContext).saveLogFile2SDcard(s, true); } } private String fomatCrashInfo(Throwable ex) { /* * String lineSeparator = System.getProperty("line.separator"); * if(TextUtils.isEmpty(lineSeparator)){ lineSeparator = "\n"; } */ String lineSeparator = "\r\n"; StringBuilder sb = new StringBuilder(); String logTime = "logTime:" + LogCollectorUtility.getCurrentTime(); String exception = "exception:" + ex.toString(); Writer info = new StringWriter(); PrintWriter printWriter = new PrintWriter(info); ex.printStackTrace(printWriter); String dump = info.toString(); String crashMD5 = "crashMD5:" + LogCollectorUtility.getMD5Str(dump); String crashDump = "crashDump:" + "{" + dump + "}"; printWriter.close(); sb.append("&start---").append(lineSeparator); sb.append(logTime).append(lineSeparator); sb.append(appVerName).append(lineSeparator); sb.append(appVerCode).append(lineSeparator); sb.append(OsVer).append(lineSeparator); sb.append(vendor).append(lineSeparator); sb.append(model).append(lineSeparator); sb.append(mid).append(lineSeparator); sb.append(exception).append(lineSeparator); sb.append(crashMD5).append(lineSeparator); sb.append(crashDump).append(lineSeparator); sb.append("&end---").append(lineSeparator).append(lineSeparator) .append(lineSeparator); return sb.toString(); } private String fomatCrashInfoEncode(Throwable ex) { /* * String lineSeparator = System.getProperty("line.separator"); * if(TextUtils.isEmpty(lineSeparator)){ lineSeparator = "\n"; } */ String lineSeparator = "\r\n"; StringBuilder sb = new StringBuilder(); String logTime = "logTime:" + LogCollectorUtility.getCurrentTime(); String exception = "exception:" + ex.toString(); Writer info = new StringWriter(); PrintWriter printWriter = new PrintWriter(info); ex.printStackTrace(printWriter); String dump = info.toString(); String crashMD5 = "crashMD5:" + LogCollectorUtility.getMD5Str(dump); try { dump = URLEncoder.encode(dump, CHARSET); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } String crashDump = "crashDump:" + "{" + dump + "}"; printWriter.close(); sb.append("&start---").append(lineSeparator); sb.append(logTime).append(lineSeparator); sb.append(appVerName).append(lineSeparator); sb.append(appVerCode).append(lineSeparator); sb.append(OsVer).append(lineSeparator); sb.append(vendor).append(lineSeparator); sb.append(model).append(lineSeparator); sb.append(mid).append(lineSeparator); sb.append(exception).append(lineSeparator); sb.append(crashMD5).append(lineSeparator); sb.append(crashDump).append(lineSeparator); sb.append("&end---").append(lineSeparator).append(lineSeparator) .append(lineSeparator); String bes = Base64.encodeToString(sb.toString().getBytes(), Base64.NO_WRAP); return bes; } } 

項目調用封裝類:LogCollector.javasocket

/** * * 執行文件上傳相關的類 * * * @author 吳曉暢 * */ public class LogCollector { private static final String TAG = LogCollector.class.getName(); private static String Upload_Url; private static Context mContext; private static boolean isInit = false; private static HttpParameters mParams; //初始化文件上傳的url,數據等內容 public static void init(Context c , String upload_url , HttpParameters params){ if(c == null){ return; } if(isInit){ return; } Upload_Url = upload_url; mContext = c; mParams = params; //初始化本身定義的異常處理 CrashHandler crashHandler = CrashHandler.getInstance(c); crashHandler.init(); isInit = true; } /** * 執行文件上傳的網路請求 * * if(isWifiOnly && !isWifiMode){ return; }表示只在wifi狀態下執行文件上傳 * * @param isWifiOnly */ public static void upload(boolean isWifiOnly){ if(mContext == null || Upload_Url == null){ Log.d(TAG, "please check if init() or not"); return; } if(!LogCollectorUtility.isNetworkConnected(mContext)){ return; } boolean isWifiMode = LogCollectorUtility.isWifiConnected(mContext); if(isWifiOnly && !isWifiMode){ return; } UploadLogManager.getInstance(mContext).uploadLogFile(Upload_Url, mParams); } /** * 用於設置是否爲測試狀態 * * @param isDebug true爲是,false爲否 若是是,能看到LOG日誌,同時可以在將文件夾看到崩潰日誌 */ public static void setDebugMode(boolean isDebug){ Constants.DEBUG = isDebug; LogHelper.enableDefaultLog = isDebug; } } 

3. 客戶端接入使用

爲通用項目設置is Library模式

 
 

實際android項目使用

添加Library

 
 

在Application子類中進行初始化

public class MyApplication extends Application { //後臺地址地址 private static final String UPLOAD_URL = "http://192.168.3.153:8080/bengkuitest/servlet/Test"; @Override public void onCreate() { super.onCreate(); boolean isDebug = true; //設置是否爲測試模式,若是是,同時可以在將文件夾看到崩潰日誌 LogCollector.setDebugMode(isDebug); //params的數據能夠爲空 初始化LogCollector的相關數據,用於文件上傳到服務器 LogCollector.init(getApplicationContext(), UPLOAD_URL, null); } } 

編寫異常並上傳異常

public class MainActivity extends Activity implements OnClickListener { private Button btn_crash; private Button btn_upload; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_crash = (Button) findViewById(R.id.button1); btn_upload = (Button) findViewById(R.id.button2); btn_crash.setOnClickListener(this); btn_upload.setOnClickListener(this); } //產生異常 private void causeCrash(){ String s = null; s.split("1"); } //上傳文件 private void uploadLogFile(){ //設置爲只在wifi下上傳文件 boolean isWifiOnly = true;//only wifi mode can upload //執行文件上傳服務器 LogCollector.upload(isWifiOnly);//upload at the right time } @Override public void onClick(View v) { switch (v.getId()) { case R.id.button1: causeCrash(); break; case R.id.button2: //上傳文件 uploadLogFile(); break; default: break; } } } 

運行結果以下圖所示

--No1Qr4Tu7Wx

content-disposition: form-data; name="logfile"; filename="c5c63fec3651fdebdd411582793fa40c.log" Content-Type: application/octet-stream; charset=utf-8 &start--- logTime:2019-04-07 10:54:47 appVerName:1.0 appVerCode:1 OsVer:5.1.1 vendor:samsung model:SM-G955F mid:c5c63fec3651fdebdd411582793fa40c exception:java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] java.lang.String.split(java.lang.String)' on a null object reference crashMD5:74861b8fb97ef57b82a87a826ab6b08f crashDump:{java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] java.lang.String.split(java.lang.String)' on a null object reference at com.jiabin.logcollectorexample.MainActivity.causeCrash(MainActivity.java:32) at com.jiabin.logcollectorexample.MainActivity.onClick(MainActivity.java:45) at android.view.View.performClick(View.java:4780) at android.view.View$PerformClick.run(View.java:19866) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5293) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) } &end--- --No1Qr4Tu7Wx-- 

3、項目源碼下載

連接:https://pan.baidu.com/s/1kEGfJ3PSoDnsyulCAoimjg密碼:xy0l

相關文章
相關標籤/搜索