Android 捕获异常上传

来源:互联网 发布:网络贷款影响征信吗 编辑:程序博客网 时间:2024/05/16 05:11
在Android应用程序开发中,不可避免的会出现异常,我们应该如何快速的在开发阶段捕获异常进行处理呢?在Java线程类中,可以在线程中捕捉未处理异常,本文我们详细介绍。

APP开发出现异常在所难名,甚至会导致应用程序崩溃。如果在debug模式下开发的时候,是可以通过查看logcat日志来查看异常消息,从而进行处理。但是,如果我们在发布版本之后,用户在使用的时候crash掉了,就无法查看异常信息,也就很难找出bug来解决问题。

还好在java线程类中,有一个针对上述问题的解决办法:在线程中捕捉未处理的异常。因为crash时,抛出的异常就是因为没有在app中catch处理,就会抛给系统,如果我们在这个时候对这个能够对这个异常进行处理,就最好不过了,这样就能打印异常信息,就能发布给服务器,供开发人员查看。

一,写一个CrashHanler类

package com.raise.wind.utils;import android.os.Environment;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;/** * Created by yu on 2015/7/15. */public class CrashHandler implements Thread.UncaughtExceptionHandler {    public static CrashHandler instance;    private CrashHandler() {    }    public static CrashHandler get_instance() {        if (instance == null)            new CrashHandler();        return instance;    }    public void init() {        Thread.setDefaultUncaughtExceptionHandler(this);    }    @Override    public void uncaughtException(Thread thread, Throwable ex) {        saveFile(ex.getMessage(), "crash.txt");        //退出程序        //这里由于是我们自己处理的异常,必须手动退出程序,不然系统出一只处于crash等待状态        android.os.Process.killProcess(android.os.Process.myPid());        System.exit(1);    }    public static void saveFile(String data, String file_name) {        File sdPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()                + File.separator + "aacrash" + File.separator + "cache");        if (!sdPath.exists()) {            sdPath.mkdirs();        }        File file = new File(sdPath, file_name);        FileOutputStream fos = null;        try {            fos = new FileOutputStream(file);            fos.write(data.getBytes("UTF-8"));        } catch (Exception e) {            e.printStackTrace();        } finally {            if (fos != null)                try {                    fos.close();                } catch (IOException e) {                    e.printStackTrace();                }        }    }}


二,在Application中声明

记得在Androidanifest.xml中配置

package com.raise.wind.app;import android.app.Application;import com.raise.wind.utils.CrashHandler;/** * Created by yu on 2015/7/15. */public class APP extends Application {    @Override    public void onCreate() {        super.onCreate();        CrashHandler.get_instance().init();    }}


三,测试

这样就声明了我们线程中出现的为捕捉异常交给CrashHandler类来处理。
现在我们写一个空指针异常来测试:

package com.raise.wind.crashproject;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.widget.TextView;public class MainActivity extends ActionBarActivity {    TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        textView.setText("text");    }}


打开app,发现程序立即crash,打开文件管理能找到在程序中保存的文件,里面有异常消息

Unable to start activity ComponentInfo{com.raise.wind.crashproject/com.raise.wind.crashproject.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

当然,这只是异常消息的开头的提示,如果要想全部打印出来,使用下面代码:

Could not execute method of the activityandroid.view.View$1.onClick(View.java:4010)android.view.View.performClick(View.java:4759)android.view.View$PerformClick.run(View.java:19770)android.os.Handler.handleCallback(Handler.java:739)android.os.Handler.dispatchMessage(Handler.java:95)android.os.Looper.loop(Looper.java:135)android.app.ActivityThread.main(ActivityThread.java:5235)java.lang.reflect.Method.invoke(Native Method)java.lang.reflect.Method.invoke(Method.java:372)com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)Caused by nulljava.lang.reflect.Method.invoke(Native Method)java.lang.reflect.Method.invoke(Method.java:372)android.view.View$1.onClick(View.java:4005)android.view.View.performClick(View.java:4759)android.view.View$PerformClick.run(View.java:19770)android.os.Handler.handleCallback(Handler.java:739)android.os.Handler.dispatchMessage(Handler.java:95)android.os.Looper.loop(Looper.java:135)android.app.ActivityThread.main(ActivityThread.java:5235)java.lang.reflect.Method.invoke(Native Method)java.lang.reflect.Method.invoke(Method.java:372)com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)Caused by Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object referencecom.raise.wind.crashproject.MainActivity.click_1(MainActivity.java:21)java.lang.reflect.Method.invoke(Native Method)java.lang.reflect.Method.invoke(Method.java:372)android.view.View$1.onClick(View.java:4005)android.view.View.performClick(View.java:4759)android.view.View$PerformClick.run(View.java:19770)android.os.Handler.handleCallback(Handler.java:739)android.os.Handler.dispatchMessage(Handler.java:95)android.os.Looper.loop(Looper.java:135)android.app.ActivityThread.main(ActivityThread.java:5235)java.lang.reflect.Method.invoke(Native Method)java.lang.reflect.Method.invoke(Method.java:372)com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)


这个类一般结合log4j来记录日志,并且在发生crash时,将log文件发送到服务器。
这样程序就可以查看用户手机端的crash消息了,方便我们处理在debug模式开发时未发现的异常。


Android APP级异常捕获实现方式 

描述:App级异常捕获,并记录下CrashLog到文件。

以下,代码。

在Application的,onCreate中,初始化自定义的CrashHandler

import android.app.Application;import com.tjd.appexceptioncatch.exception.CrashHandler;public class MyApplication extends Application {    private static MyApplication instance;    @Override    public void onCreate() {        super.onCreate();        CrashHandler.getInstance().init(getApplicationContext());    }    public static MyApplication getInstance() {        if (instance == null) {            instance = new MyApplication();        }        return instance;    }}


自定义CrashHandler如下

import java.io.File;import java.io.FileOutputStream;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.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;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.tjd.appexceptioncatch.application.MyApplication;/** * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告. * 需要在Application中注册,为了要在程序启动器就监控整个程序。 */public class CrashHandler implements UncaughtExceptionHandler {    public static final String TAG = "CrashHandler";    //系统默认的UncaughtException处理类    private Thread.UncaughtExceptionHandler mDefaultHandler;    //CrashHandler实例    private static CrashHandler instance;    //程序的Context对象    private Context mContext;    //用来存储设备信息和异常信息    private Map<String, String> infos = new HashMap<String, String>();    //用于格式化日期,作为日志文件名的一部分    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");    /** 保证只有一个CrashHandler实例 */    private CrashHandler() {    }    /** 获取CrashHandler实例 ,单例模式 */    public static CrashHandler getInstance() {        if (instance == null)            instance = new CrashHandler();        return instance;    }    /**     * 初始化     */    public void init(Context context) {        mContext = context;        //获取系统默认的UncaughtException处理器        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();        //设置该CrashHandler为程序的默认处理器            Thread.setDefaultUncaughtExceptionHandler(this);    }    /**     * 当UncaughtException发生时会转入该函数来处理     */    @Override    public void uncaughtException(Thread thread, Throwable ex) {        if (!handleException(ex) && mDefaultHandler != null) {            //如果用户没有处理则让系统默认的异常处理器来处理            mDefaultHandler.uncaughtException(thread, ex);        } else {            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                Log.e(TAG, "error : ", e);            }            //退出程序                android.os.Process.killProcess(android.os.Process.myPid());            System.exit(1);        }    }    /**     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.     * @param ex     * @return true:如果处理了该异常信息;否则返回false.     */    private boolean handleException(Throwable ex) {        if (ex == null) {            return false;        }        //收集设备参数信息        collectDeviceInfo(mContext);        //使用Toast来显示异常信息        new Thread() {            @Override            public void run() {                Looper.prepare();                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();                Looper.loop();            }        }.start();        //保存日志文件             saveCatchInfo2File(ex);        return true;    }    /**     * 收集设备参数信息     * @param ctx     */    public void collectDeviceInfo(Context ctx) {        try {            PackageManager pm = ctx.getPackageManager();            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);            if (pi != null) {                String versionName = pi.versionName == null ? "null" : pi.versionName;                String versionCode = pi.versionCode + "";                infos.put("versionName", versionName);                infos.put("versionCode", versionCode);            }        } catch (NameNotFoundException e) {            Log.e(TAG, "an error occured when collect package info", e);        }        Field[] fields = Build.class.getDeclaredFields();        for (Field field : fields) {            try {                field.setAccessible(true);                infos.put(field.getName(), field.get(null).toString());                Log.d(TAG, field.getName() + " : " + field.get(null));            } catch (Exception e) {                Log.e(TAG, "an error occured when collect crash info", e);            }        }    }    private String getFilePath() {        String file_dir = "";        // SD卡是否存在        boolean isSDCardExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());        // Environment.getExternalStorageDirectory()相当于File file=new File("/sdcard")        boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();        if (isSDCardExist && isRootDirExist) {            file_dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crashlog/";        } else {            // MyApplication.getInstance().getFilesDir()返回的路劲为/data/data/PACKAGE_NAME/files,其中的包就是我们建立的主Activity所在的包            file_dir = MyApplication.getInstance().getFilesDir().getAbsolutePath() + "/crashlog/";        }        return file_dir;    }    /**     * 保存错误信息到文件中     * @param ex     * @return 返回文件名称,便于将文件传送到服务器     */    private String saveCatchInfo2File(Throwable ex) {        StringBuffer sb = new StringBuffer();        for (Map.Entry<String, String> entry : infos.entrySet()) {            String key = entry.getKey();            String value = entry.getValue();            sb.append(key + "=" + value + "\n");        }        Writer writer = new StringWriter();        PrintWriter printWriter = new PrintWriter(writer);        ex.printStackTrace(printWriter);        Throwable cause = ex.getCause();        while (cause != null) {            cause.printStackTrace(printWriter);            cause = cause.getCause();        }        printWriter.close();        String result = writer.toString();        sb.append(result);        try {            long timestamp = System.currentTimeMillis();            String time = formatter.format(new Date());            String fileName = "crash-" + time + "-" + timestamp + ".log";            String file_dir = getFilePath();            //            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {            File dir = new File(file_dir);            if (!dir.exists()) {                dir.mkdirs();            }            File file = new File(file_dir + fileName);            if (!file.exists()) {                file.createNewFile();            }            FileOutputStream fos = new FileOutputStream(file);            fos.write(sb.toString().getBytes());            //发送给开发人员            sendCrashLog2PM(file_dir + fileName);            fos.close();            //            }            return fileName;        } catch (Exception e) {            Log.e(TAG, "an error occured while writing file...", e);        }        return null;    }    /**     * 将捕获的导致崩溃的错误信息发送给开发人员     * 目前只将log日志保存在sdcard 和输出到LogCat中,并未发送给后台。     */    private void sendCrashLog2PM(String fileName) {        //        if (!new File(fileName).exists()) {        //            Toast.makeText(mContext, "日志文件不存在!", Toast.LENGTH_SHORT).show();        //            return;        //        }        //        FileInputStream fis = null;        //        BufferedReader reader = null;        //        String s = null;        //        try {        //            fis = new FileInputStream(fileName);        //            reader = new BufferedReader(new InputStreamReader(fis, "GBK"));        //            while (true) {        //                s = reader.readLine();        //                if (s == null)        //                    break;        //                //由于目前尚未确定以何种方式发送,所以先打出log日志。        //                Log.i("info", s.toString());        //            }        //        } catch (FileNotFoundException e) {        //            e.printStackTrace();        //        } catch (IOException e) {        //            e.printStackTrace();        //        } finally { // 关闭流        //            try {        //                reader.close();        //                fis.close();        //            } catch (IOException e) {        //                e.printStackTrace();        //            }        //        }    }}


在MainActivity中触发异常

import android.app.Activity;import android.os.Bundle;public class MainActivity extends Activity {    final String str = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        str.equals("exception");    }}