Android之崩溃日志管理(非原创)

文章大纲

一、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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容