Crash: 处理UncaughtExcption,捕获未处理异常信息,界面友好提示用户

来源:互联网 发布:优衣库网站的seo分析 编辑:程序博客网 时间:2024/05/16 18:33

Crash: 处理UncaughtExcption,友好提示用户,捕获错误信息

相信大家在APP使用过程都遇到过,应用程序异常崩溃,屏幕一黑闪退,这种情况称之为Crash。出现的原因是由于程序运行过程中产生了未知异常UncaughtException,当程序发生Crash时,系统会杀死程序,出现闪退,这种情况的用户体验不好,而且开发人员也不能知道用户发生了何种异常。
那么问题来了,发生Crash时我们需要给用户提供一个友好的提醒,并且捕获用户的错误信息。
错误信息Activity

捕获用户Crash信息的两种思路

  • 1.集成第三方测试SDK,
    做的比较好的有 云测TestIn
    腾讯云测等等,这里只要看下文档基本10行以内代码就能搞定了,使用简单,但也有局限性。
  • 2.通过自定义实现,通过UncaughtExceptionHandler处理未捕获异常。(下面着重说说这种方式)
    具体思路:首先为程序指定UncaughtExceptionHandler处理程序,当未捕获异常发生时,会调用处理程序的uncaughtException方法,我们在该方法中获得相应异常信息,然后做出相应处理 弹出友好的提示框,保存异常信息(或上传到服务器)

UncaughtExceptionHandler文档是这么介绍的

java.lang
接口 Thread.UncaughtExceptionHandler
所有已知实现类:
ThreadGroup
正在封闭类:Thread
public static interface Thread.UncaughtExceptionHandler
当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。
当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler() 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,将线程和异常作为参数传递。如果某一线程没有明确设置其 UncaughtExceptionHandler,则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。

  • 通过指定一个Thread.setDefaultUncaughtExceptionHandler()方法指定未知异常处理程序
//设置该线程由于未捕获到异常而突然终止时调用的处理程序Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {    @Override    public void uncaughtException(Thread thread, Throwable throwable) {        //处理异常,我们还可以把异常信息写入文件,以供后来分析。        String stackTraceString = getThrowableStraceStr(thread, throwable);        Log.e(TAG,stackTraceString);        startErroActivity(stackTraceString);        killCurrentProcess();    }});
  • 下面 说一下具体的几个方法
    获取异常及异常的追踪信息,异常信息和追踪信息如下图
    异常信息和追踪信息的区别
/** * 获取此 throwable 及其追踪信息 * @param thread * @param throwable * @return */private static String getThrowableStraceStr(Thread thread, Throwable throwable) {    Log.e("CrashException", thread.getName() + "--Exception:" + throwable + "\n\n");    StringWriter sw = new StringWriter();    PrintWriter pw = new PrintWriter(sw);    throwable.printStackTrace(pw);//    return sw.toString();}
  • 当捕获到异常追踪信息后,我们的处理方式时打开一个专门用于的错误信息Activity用于提示用户,在该并可以用户选择是否重新启动应用程序。
    这里有三个地方需要注意的是:
    • 1.将错误信息通过Intent.putExtra(erroInfo)传递给错误提示页面需要保证文本长度不超过128K
    • 2.打开新的ErroActivity是在一个单独的进程中,所以调用杀死当前进程不会杀掉ErroActivity所在进程
    • 3.设置ErroActivity的意图,清空回退栈中所有activity intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)
private static void startErroActivity(String straceMsg) {    //Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.    //The limit is 1MB on Android but some devices seem to have it lower.    //See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html    //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171    if (straceMsg.length() > MAX_STACK_TRACE_SIZE) {        String disclaimer = " [stack trace too large]";        straceMsg = straceMsg.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;    }    //启动错误页面    Intent intent=new Intent(application,ErroActivity.class);    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);    intent.putExtra(EXTRA_STRACE_MESSAGE,straceMsg);    application.startActivity(intent);}
  • 在开启错误信息页面后,结束当前进程
/** * 结束进程 * INTERNAL method that kills the current process. It is used after * restarting or killing the app. */private static void killCurrentProcess() {    //杀死当前进程    android.os.Process.killProcess(android.os.Process.myPid());    System.exit(10);}

捕获异常完整代码

package com.crash.k.crashuncaughtexception.utils;import android.app.Activity;import android.app.Application;import android.content.Context;import android.content.DialogInterface;import android.content.Intent;import android.os.Bundle;import android.os.Looper;import android.support.v7.app.AlertDialog;import android.util.Log;import android.widget.Toast;import com.crash.k.crashuncaughtexception.ErroActivity;import java.io.PrintWriter;import java.io.StringWriter;import java.lang.Thread.UncaughtExceptionHandler;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Locale;/** * Created by K on 2015/12/21. */public class CrashUncaugtExceptionUtils {    public final static String EXTRA_STRACE_MESSAGE="extra_strace_message";    private static final String TAG="CrashException";    /**设置传递错误堆栈信息最大长度 保证不超过128 KB*/    private static final int MAX_STACK_TRACE_SIZE = 131071; //128 KB - 1    private static Context mContext;    private static Application application;     /**     * 初始化     */    public static void install(Context context){//设置该线程由于未捕获到异常而突然终止时调用的处理程序Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {    @Override    public void uncaughtException(Thread thread, Throwable throwable) {        //处理异常,我们还可以把异常信息写入文件,以供后来分析。        String stackTraceString = getThrowableStraceStr(thread, throwable);        Log.e(TAG,stackTraceString);        startErroActivity(stackTraceString);        killCurrentProcess();    }});        mContext=context;        application=(Application)context.getApplicationContext();        application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {            @Override            public void onActivityCreated(Activity activity, Bundle bundle) {                Log.i(TAG, "onActivityCreated");            }            @Override            public void onActivityStarted(Activity activity) {            }            @Override            public void onActivityResumed(Activity activity) {                Thread.UncaughtExceptionHandler unCaughtExceptionHandler= Thread.getDefaultUncaughtExceptionHandler();                Log.i("LifecycleCallbacks",activity.getClass().getName()+"-->\n"+ unCaughtExceptionHandler.getClass().getName() + "");            }            @Override            public void onActivityPaused(Activity activity) {            }            @Override            public void onActivityStopped(Activity activity) {            }            @Override            public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {            }            @Override            public void onActivityDestroyed(Activity activity) {            }        });    }    private static void startErroActivity(String straceMsg) {        //Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.        //The limit is 1MB on Android but some devices seem to have it lower.        //See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html        //And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171        if (straceMsg.length() > MAX_STACK_TRACE_SIZE) {            String disclaimer = " [stack trace too large]";            straceMsg = straceMsg.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer;        }        //启动错误页面        Intent intent=new Intent(application,ErroActivity.class);        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);        intent.putExtra(EXTRA_STRACE_MESSAGE,straceMsg);        application.startActivity(intent);    }    /**     * 获取此 throwable 及其追踪信息     * @param thread     * @param throwable     * @return     */    private static String getThrowableStraceStr(Thread thread, Throwable throwable) {        Log.e("CrashException", thread.getName() + "--Exception:" + throwable + "\n\n");        StringWriter sw = new StringWriter();        PrintWriter pw = new PrintWriter(sw);        throwable.printStackTrace(pw);//        return sw.toString();    }    /**     * 获取应用的启动页Activity     * INTERNAL method used to get the default launcher activity for the app.     * If there is no launchable activity, this returns null.     *     * @param context A valid context. Must not be null.     * @return A valid activity class, or null if no suitable one is found     */    @SuppressWarnings("unchecked")    public static Class<? extends Activity> getLauncherActivity(Context context) {        Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());        if (intent != null) {            try {                return (Class<? extends Activity>) Class.forName(intent.getComponent().getClassName());            } catch (ClassNotFoundException e) {                //Should not happen, print it to the log!                Log.e(TAG, "Failed when resolving the restart activity class via getLaunchIntentForPackage, stack trace follows!", e);            }        }        return null;    }    /**     * 结束进程     * INTERNAL method that kills the current process. It is used after     * restarting or killing the app.     */    private static void killCurrentProcess() {        //杀死当前进程        android.os.Process.killProcess(android.os.Process.myPid());        System.exit(10);    }}

在Application中完成初始化

封装好了异常处理,在Application中一行代码即可完成初始化了

public class CrashApp extends Application{    @Override    public void onCreate() {        super.onCreate();        //初始化        CrashUncaugtExceptionUtils.install(this);    }}

ErroActivity中的异常处理

  • 1.重新启动APP,首先找到需要LauncherActivity
public static Class<? extends Activity> getLauncherActivity(Context context) {    Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());    if (intent != null) {        try {            return (Class<? extends Activity>) Class.forName(intent.getComponent().getClassName());        } catch (ClassNotFoundException e) {            //Should not happen, print it to the log!            Log.e(TAG, "Failed when resolving the restart activity class via getLaunchIntentForPackage, stack trace follows!", e);        }    }    return null;}
  • 2 .显示异常信息,具体处理可以保存到本地文件统一上传,也可以每次发生异常则上传,具体操作酌情处理
    以下错误信息的代码
public class ErroActivity extends AppCompatActivity {    private Context mContext;    private String mErroDetailsStr;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_erro);        initData();        initViews();    }    private void initData(){        mContext=this;        String erroInfo= getIntent().getStringExtra(CrashUncaugtExceptionUtils.EXTRA_STRACE_MESSAGE);        mErroDetailsStr=getErroDetailsInfo()+"\n strace \n"+erroInfo;    }    private void initViews()    {        Button btnRestart=(Button)findViewById(R.id.btn_restart);        btnRestart.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                Class launcherActivity= CrashUncaugtExceptionUtils.getLauncherActivity(mContext);                if(launcherActivity!=null)                {                    Intent intent=new Intent(mContext,launcherActivity);                    startActivity(intent);                    finish();                }else{                    Toast.makeText(mContext,"重启失败了",Toast.LENGTH_SHORT).show();                }            }        });        TextView tvInfo=(TextView)findViewById(R.id.tv_erro_info);        tvInfo.setText(mErroDetailsStr);    }    /**获取错误信息     *     * @param thread     * @param throwable     * @return     */    private String getErroDetailsInfo()    {        String erroDetailInfo="";        //设备名称        erroDetailInfo+="DeviceName:"+ AppInfoUtils.getDeviceModelName()+ " \n";        //版本号        erroDetailInfo+="VersionName:"+AppInfoUtils.getVersionName(mContext)+ " \n";        DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);        //记录当前出错时间        erroDetailInfo+="currentTime"+dateFormat.format(new Date())+ " \n";        //安装时间        erroDetailInfo+="BuildTime:"+AppInfoUtils.getBuildDateAsString(mContext,dateFormat)+ " \n";        return  erroDetailInfo;    }}

获取设备号,版本号,安装时间的几个方法

package com.crash.k.crashuncaughtexception.utils;import android.content.Context;import android.content.pm.ApplicationInfo;import android.content.pm.PackageInfo;import android.os.Build;import java.text.DateFormat;import java.util.Date;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;/** * Created by K on 2015/12/27. */public class AppInfoUtils {    /**     * 获取应用版本名称.     *     */    public static String getVersionName(Context context) {        try {            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);            return packageInfo.versionName;        } catch (Exception e) {            return "Unknown";        }    }    /**     * 获取设备名称     */    public static String getDeviceModelName() {        String manufacturer = Build.MANUFACTURER;        String model = Build.MODEL;        if (model.startsWith(manufacturer)) {            return capitalize(model);        } else {            return capitalize(manufacturer) + " " + model;        }    }    /**     * 获取应用安装时间     * @param context 当前上下文,必填     * @param dateFormat 时间格式化     * @return     */    public static String getBuildDateAsString(Context context, DateFormat dateFormat) {        String buildDate;        try {            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);            ZipFile zf = new ZipFile(ai.sourceDir);            ZipEntry ze = zf.getEntry("classes.dex");            long time = ze.getTime();            buildDate = dateFormat.format(new Date(time));            zf.close();        } catch (Exception e) {            buildDate = "Unknown";        }        return buildDate;    }    /***     * 如果字符串为null则默认转为 “”     * @param s     * @return     */    private static String capitalize(String s) {        if (s == null || s.length() == 0) {            return "";        }        return  s;    }}

源码下载地址

1 0
原创粉丝点击