Android之崩潰日誌管理

来源:https://www.cnblogs.com/WUXIAOCHANG/archive/2019/04/07/10665002.html
-Advertisement-
Play Games

文章大綱 一、Android崩潰日誌管理簡介二、崩潰日誌管理實戰三、項目源碼下載 一、Android崩潰日誌管理簡介 1. 什麼是android崩潰日誌管理 開發中有些地方未註意可能造成異常拋出未能caught到,然後彈出系統對話框強制退出。這種交互不好,而且開發者也不能及時獲取到底哪裡出問題。因此 ...


文章大綱

一、Android崩潰日誌管理簡介
二、崩潰日誌管理實戰
三、項目源碼下載

 

一、Android崩潰日誌管理簡介

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

  開發中有些地方未註意可能造成異常拋出未能caught到,然後彈出系統對話框強制退出。這種交互不好,而且開發者也不能及時獲取到底哪裡出問題。因此我們可以使用android的UncaughtExceptionHandler來處理這種異常。

2. 操作邏輯

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

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

二、崩潰日誌管理實戰

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.java

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) 方法中。

/**
 * 
 * 如果需要捕獲系統的未捕獲異常(如系統拋出了未知錯誤,這種異常沒有捕獲,這將導致系統莫名奇妙的關閉,使得用戶體驗差),
 * 可以通過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.java

/**
 * 
 * 執行文件上傳相關的類
 * 
 * 
 * @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--

三、項目源碼下載

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

 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、hbase與列式存儲 hbase最早起源於谷歌的一篇BigTable的論文,它是由java編寫的、開源的一個nosql資料庫,同時它也是一個列式存儲的、支持分散式(基於hdfs)的資料庫。什麼是列式存儲呢?簡單來講就是:傳統的關係資料庫幾乎都是行式存儲的,這種存儲的特點是,將每一行的數據連起來進 ...
  • 與聚合函數一樣,開窗函數也是對行集組進行聚合計算,但是普通聚合函數每組只能返回一個值,而開窗函數可以每組返回多個值。 實驗一比如我們想查詢每個工資小於5000元的員工信息(城市以及年齡),並且在每行中都顯示所有工資小於5000元的員工個數,執行下麵的SQL語句 這個語句顯然是錯誤的,因為count( ...
  • 最近想安裝一個本地資料庫, 發現網上寫的沒一個能安裝成功的, 各種蛋疼, 我還是自己寫一個吧 參考鏈接: https://www.cnblogs.com/by330326/p/5608290.html https://blog.csdn.net/baidu_41909653/article/deta ...
  • 文章大綱 一、什麼是CircleImageView二、代碼實戰三、項目源碼下載 一、什麼是CircleImageView 圓角 ImageView,在我們的 App 中這個想必是太常見了,也許我們可以有無數種展示圓角圖片的方法,但是 CircleImageView 絕對是我們在開發時需要優先考慮的, ...
  • 文章大綱 一、什麼是PhotoView二、代碼實戰三、項目源碼下載 一、什麼是PhotoView 一款 ImageView 展示框架,支持縮放,響應手勢,位於圖片排行榜的第五位,PhotoView 與上面不同的是圖片的展示功能,可以實現類似微信頭像的放大功能,還有就是很多 App 的圖片顯示響應手勢 ...
  • OCR圖文識別App 是一款準確高效的 OCR文字識別與掃描軟體,識別準確度高,速度快,掃描文件清晰,可導出TXT、Excel。 免費使用。 技術 基於百度在業界領先的人工智慧與深度學習技術,提供對身份證、銀行卡、營業執照等常用卡片及證照的文字內容進行結構化識別的服務。 功能 身份證識別 銀行卡識別 ...
  • 文章大綱 一、什麼是Material Dialogs二、Material Dialogs實戰三、項目源碼下載 一、什麼是Material Dialogs Material Dialogs是一個漂亮、流暢、可定製的對話框,核心模塊包含創建基本、列表、單/多選項、進度、輸入等對話框。 二、Materia ...
  • 文章大綱 一、Lottie介紹二、Lottie實戰三、項目源碼下載四、參考文章 一、Lottie介紹 1. 什麼是Lottie Lottie是Android和iOS的移動庫,用於解析Adobe After Effects動畫與Bodymovin一起導出為json 併在移動設備上呈現它們!其實在移動端 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...