手写CrashHandler实现UncaughtExceptionHandler拦截android异常

手写CrashHandler实现UncaughtExceptionHandler拦截android异常

作者:码字员小D

有点复杂,虽然知道原理,但是并不好从哪开始写了。。。。。。

首先这是个需要在整个app运行状态中都需要存在的对象,所以需要在application里初始化这个类,并且这个类实例~~~慢着!发现这里代码有疑问,application中只在oncreate方法里面初始化

public class CrashApplication extends Application {
@Override
public void onCreate() {
    super.onCreate();
//      CrashHandler crashHandler = CrashHandler.getInstance();
    CrashHandler crashHandler = new CrashHandler();
    crashHandler.init(getApplicationContext());
}
}

在application里面并没有持有这个UncaughtExceptionHandler的实例(CrashHandler实现了UncaughtExceptionHandler接口类);仅仅在这里做初始化的工作,可以让UncaughtExceptionHandler这个类从一开始就拦截app运行时候的任何uncaught异常。

写CrashHandler类可以用单例写,也可以直接new对象。其实个人觉得这里单例也没太必要,直接new一个对象,这个对象反正是要被set到Thread类里去的

/**
 * Sets the default uncaught exception handler. This handler is invoked in
 * case any Thread dies due to an unhandled exception.
 *
 * @param handler
 *            The handler to set or null.
 */
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
    Thread.defaultUncaughtHandler = handler;
}

上面的Thread.defaultUncaughtHandler就是系统的一个静态的实例。现在明白了其实在外面用static维持一个单例对象也没多大必要的原因了吧,系统里为我们维持了一个单例对象。

好了用单例初始化CrashHandler和直接new的两种方法都写出来吧

/** 保证只有一个CrashHandler实例 */
public CrashHandler() {

}

//CrashHandler实例
private static CrashHandler INSTANCE = new CrashHandler();
/** 获取CrashHandler实例 ,单例模式 */
public static CrashHandler getInstance() {
    return INSTANCE;
}

下面我们需要讨论的是如何实现拦截异常。其实在android系统中有异常就会报错,闪退,application not response ANR。我们要做的是要分担下系统为我们默认对异常处理的任务而已。系统默认处理异常不友好,半天不响应,返回键也没用,最后会弹出个窗来要用户自己决定是否结束或者等待。

看android里Thread的源代码可以看到,还有个

public class ThreadGroup implements Thread.UncaughtExceptionHandler{

实现了Thread里面UncaughtExceptionHandler的接口,这个类里有系统线程组。。。好了这系统代码也不太好看

//获取系统默认的UncaughtException处理器
    mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

我们需要获取系统的默认的defaultUncaughtHandler。不过疑惑的是这个defaultUncaughtHandler是谁初始化或者设置过去的呢,在Thread类里没有初始化过程。应该是系统给初始化设置过去的,暂时没找到具体代码实现。。。有知道的教教我呗!

然后呢,我们需要把自己写的UncaughtException类设置到系统变量defaultUncaughtHandler中去。

//设置该CrashHandler为程序的默认处理器
    Thread.setDefaultUncaughtExceptionHandler(this);

Ok,这里其实有点乱了。理理,首先我们需要获取一个系统默认的异常处理对象defaultUncaughtHandler,然后我们要把系统默认的异常处理对象设置成我们自己写的一个实现UncaughtException接口的对象。为什么要这么做呢?

首先系统默认的异常处理对象defaultUncaughtHandler维持了一个系统的异常处理对象,获取到这个系统处理对象可以通过调用uncaughtException(thread, ex);方法实现系统默认的异常处理。

而我们自己要把自己写的一个实现UncaughtException接口的对象设置成系统默认的异常处理对象是为了让系统检测到未catch的异常是会调用我们自己实现的uncaughtException(thread, ex)方法。然后在此方法里面实现处理逻辑。

理理就知道了,我们需要在实现了UncaughtException接口的CrashHandler类的uncaughtException方法里面实现处理逻辑。并且完成了自己的处理逻辑之后要执行系统原理默认对未捕获异常的处理。

PS:为什么一定要自己处理完后让系统默认的异常处理对象再处理一遍呢,因为系统默认有对异常处理有默认的处理方案,比如runtimeexcption运行时异常,nullexception空指针异常怎么处理。其实都是卡死半天,然后弹窗让用户决定怎么处理,等待还是结束。这个弹窗的两个选项,用户选择了等待,说明这个卡死状态是正常了,也许是网络请求慢但是确实是逻辑没错导致的。用户选择了结束,说明了这个确实是异常要立马结束掉。选择结束也就是用户选择了系统默认的异常处理方式,即调用了类似的defaultUncaughtHandler.uncaughtException(thread, ex)方法处理。

/**
 * 当UncaughtException发生时会转入该函数来处理
 */
@Override
public void uncaughtException(Thread thread, Throwable ex) {
    if (!handleException(ex) && mDefaultHandler != null) {
        //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);

//          mDefaultHandler.uncaughtException(null, ex);

//      android.os.Process.killProcess(android.os.Process.myPid());
//      System.exit(0);
    } else {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Log.e(TAG, "error : ", e);
        }
        //退出程序
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(0);
    }
}

这里回调接口方法的handleException(ex)是我们对异常的自定义处理。先不考虑。

其实这里模拟了系统的默认异常处理方式,即当没有默认异常处理的时候,会线程睡眠3秒然后退出应用。等价于卡死三秒然后弹窗提醒用户选择。

自己处理完对未捕获异常处理后交由系统默认异常处理对象处理,默认就退出应用了。

但是呢,这里我发现如果是mDefaultHandler.uncaughtException(thread, ex);处理的话第一次是正常退出应用的,当第二次点击应用图标的时候并没有执行到我们自己处理异常方法里。第三次点击应用图标时候直接弹出我们的异常处理方式。这是为什么呢?我的观点是系统有缓存异常机制,任务一样的错误一样的处理方式,就不再会执行我们自己写的异常处理方式里了,直接退出。这是我的观点。求反驳批判。

所以我让mDefaultHandler.uncaughtException(null, ex);中的一个参数都传null则系统不会缓存记录异常,因为是null的,没异常啊~我这算是在调戏系统么。所以每次都能正常执行我们的处理方法。

当然你也可以在系统处理异常方式里面写上我们自己的异常处理方式。但是如果最后不退出应用的话是会卡死的,,,,,,其实这里个人更推荐自己写android.os.Process.killProcess(android.os.Process.myPid());因为,如果传一个null给系统默认异常处理会报null指针异常的~~

我们再来聊聊自己怎么处理异常

/**
 * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
 *
 * @param ex
 * @return true:如果处理了该异常信息;否则返回false.
 */
private boolean handleException(Throwable ex) {
    if (ex == null) {
        return false;
    }
    //使用Toast来显示异常信息
    new Thread() {
        @Override
        public void run() {
            Looper.prepare();
            Toast.makeText(mContext, "出错了~~~", Toast.LENGTH_LONG).show();
            TestService.getInstance().sendError("error:made by byl");
            Looper.loop();
        }
    }.start();

    //收集设备参数信息
    collectDeviceInfo(mContext);
    //保存日志文件
    saveCrashInfo2File(ex);

    return false;
}

//收集设备参数信息 collectDeviceInfo(mContext);

//保存日志文件 saveCrashInfo2File(ex);

这里有两个方法。保存出错日志。我觉得类似umeng这种错误分析的系统肯定也是这样做处理的,要不然可以知道什么手机出什么错误的统计一清二楚的。

这里关键在TestService这个后台服务。

顺便说下这里的Looper.prepare和Looper.loop方法.网上的说法Toast要想在子线中显示,就必须在当前线程中存在一个消息队列(Looper)。 具体Handler的操作,你看下Toast源码就知道了。好吧~

很讨厌面试的时候很多人问handler啊,Looper对象啊,message消息队列啊,然后我会很笼统的说android里面的Looper是一个轮询的类,handler每次send一个消息的时候会把消息放到消息队列里面去,然后looper去轮询这个消息队列去一个一个的处理消息。其实我能理解的也就是这一层,具体怎么轮询,怎么处理消息的其实代码具体怎么实现我也不知道啊~相信很多面试官也不清楚!

public class TestService extends Service

TestService是一个服务了,在第一个Activity启动的时候就

Intent intent = new Intent(this, TestService.class);
    startService(intent);

这个后台服务默默地等待着系统出现未捕获的异常,然后执行自己的方法

    Intent intent = new Intent(this, SendErrorActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.putExtra("msg", message);
    startActivity(intent);
    stopSelf();

然后这个SendErrorActivity呢可以实现弹窗提示的功能,具体逻辑要看业务需求。

再说下这个SendErrorActivity的具体实现吧

manifest里面配置

<activity
        android:name="com.example.testerror.SendErrorActivity"
        android:screenOrientation="portrait"
        android:theme="@style/bklistDialog" >
    </activity>

style里面配置bklistDialog

<style name="bklistDialog" parent="@android:style/Theme.Dialog">
    <item name="android:windowFrame">@android:color/transparent</item>
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <!--控制灰度的值,当为1时,界面除了我们的dialog内容是高亮显示的,dialog以外的区域是黑色的,完全看不到其他内容,系统的默认值是0.5,而已根据自己的需要调整-->
    <item name="android:backgroundDimAmount">0.3</item>
    <item name="android:windowAnimationStyle">@null</item>
</style>

layout文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp" >

<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="#ffffff"
    android:gravity="center"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="10dp" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:padding="10dp"
            android:text="提示"
            android:textSize="18sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="3dp"
            android:text="是否发送错误信息?"
            android:textColor="#A19D94"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/info_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:textColor="#A19D94"
            android:textSize="16sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/ok"
            android:layout_width="fill_parent"
            android:layout_height="45dp"
            android:layout_weight="1"
            android:gravity="center"
            android:padding="10dp"
            android:text="确 定"
            android:textSize="18sp" />

        <Button
            android:id="@+id/cancel"
            android:layout_width="fill_parent"
            android:layout_height="45dp"
            android:layout_weight="1"
            android:gravity="center"
            android:padding="10dp"
            android:text="取消"
            android:textSize="18sp" />
    </LinearLayout>
</LinearLayout>

</RelativeLayout>

SendErrorActivity里面的oncreate方法具体实现

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_loginoutinfo);
    //全屏显示
    getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    ok=(Button) findViewById(R.id.ok);
    cancel=(Button) findViewById(R.id.cancel);
    error_msg=getIntent().getStringExtra("msg");
    ok.setOnClickListener(this);
    cancel.setOnClickListener(this);
}

OK,花了两个小时写下了这个东西研究了下还是很值得的。纯手敲啊,麻烦高人赐教啊!谢了

时间: 2024-08-26 07:14:11

手写CrashHandler实现UncaughtExceptionHandler拦截android异常的相关文章

Android 手写Binder 教你理解android中的进程间通信

关于Binder,我就不解释的太多了,网上一搜资料一堆,但是估计还是很多人理解的有困难.今天就教你如何从 app层面来理解好Binder. 其实就从我们普通app开发者的角度来看,仅仅对于android应用层的话,Binder就是客户端和服务端进行通信的媒介. AIDL就是我们理解Binder 最好的事例. 我们都知道 我们写好aidl 文件以后,开发工具 会自动帮我们生成好代码.实际上 我们最终apk里面 是只有这些代码的,我们写的aidl文件 是不会被打包进去的,也就是说aidl文件 实际上

提高Android应用手写流畅度(基础篇)

在使用android类的手写应用时,整体上都有这样一个印象:android的手写不流畅.不自然,和苹果应用比起来相差太远.本文结合作者亲身经历,介绍一下有效提高手写流畅度的几种方法: 1.未做任何处理的手写效果: 这是一个自定义的view,通过在onTouchEvent时间中捕获系统回调的触摸点信息,然后再onDraw方法里面刷新,可以明显地感觉到线条很生硬,并且在手写的过程中跟随感很差,反应迟钝,具体代码如下: package com.mingy.paint.view; import andr

Android -- 使用UncaughtExceptionHandler捕获全局异常

在集成了统计SDK(友盟统计,百度统计等)之后,有一个非常有利于测试的功能:错误分析!此功能能够将程序在运行中碰到的崩溃(runtimeException)问题反馈到服务器,帮助开发者改善产品,多适配机器. 然而在公司android开发中不集成这些SDK,那应该怎么实现这样的功能呢?下面让我们来看下如何使用UncaughtExceptionHandler来捕获异常. 首先实现创建一个类,实现UncaughtExceptionHandler接口.代码如下: public class CrashHa

【Android】 -- 使用UncaughtExceptionHandler捕捉全局异常

在综合统计SDK(欧盟统计局的朋友,百度统计)之后.有一个非常有利的功能测试:错误分析.此功能可以在程序的执行中遇到崩溃(runtimeException)反馈给server,帮助开发者提高产品.多功能适配器机. 然而在公司android开发中不集成这些SDK,那应该怎么实现这种功能呢?以下让我们来看下怎样使用UncaughtExceptionHandler来捕获异常. 首先实现创建一个类,实现UncaughtExceptionHandler接口.代码例如以下: public class Cra

Android手写开源项目和资料搜集

引言 Android的手写效率一直是件头疼的事情,比如手写效率.笔锋效果.手掌抑制等等,本文搜集了关于手写的开源项目和一些相关的文章资料. 开源项目 1 android-signaturepad 项目地址:android-signaturepad 项目介绍:这是一款银行手写签名的应用,通过event的getHistory方法获取存储在MotionEvent中的历史点,大大提高了手写的流畅度,通过算法实现了笔锋效果. 2  Markers 项目地址:Markers 项目介绍:这是一款带有笔锋效果的

android项目 之 记事本(6)----- 添加手写

想必大家都用过QQ的白板功能,里面主要有两项,一个是涂鸦功能,其实类似于上节的画板功能,而另一个就是手写,那记事本怎么能没有这个功能呢,今天就来为我们的记事本添加手写功能. 先上图,看看效果: 看了效果图,是不是心动了呢?那就赶紧着手做吧,其实,手写功能并不难实现,大体就是全屏书写,定时发送handle消息,更新activity. 实现手写功能的主要步骤: 1. 自定义两个View,一个是TouchView,用于在上面画图,另一个是EditText,用于将手写的字显示在其中,并且,要将两个自定义

android手写笔思路

工作需要,对这方面做了一些了解 一般的手写对android canvas有点理解的应该都知道,只需要单纯的使用drawPath就可以在view上画画. 而手写笔的关键则是要让path的strokeWidth发生变化 这个令人头大了, 毕竟setPaint只能够设置一个paint,一旦改变paint的参数,整个path都会发生改变. 所以,我们只能够另辟蹊径. 我们可以先开一个arraylist(Point),用来记录我们在surfaceview(推荐在画画功能上使用surfaceview代替vi

android项目 之 记事本(15) ----- 保存手写及绘图

本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020 之前,忘了写如何将手写和绘图保存,现在补上. 首先看如何保存绘图,先看效果图: 因为记事本的绘图功能主要用到了画布,而在构建画布时,指定了Bitmap,也就是说在画布上的所画的东西都被保存在了Bitmap中,因此,我们只要保存该Bitmap,就可以将我们的所绘制的图形以图片的形式保存,主要代码如下: /* * 保存所绘图形 * 返回绘图文件的存储路径 * */ public S

android项目 之 记事本(7)----- 手写功能之删除、恢复和清空

上一节,为记事本添加了手写的功能,但是没有实现底部按钮的各种功能,这节就先实现撤销,恢复和清空的功用. 因为不会录制屏幕成gif图片,所以就以图片形式给出吧,不是很形象,凑合着看: 显然,需要为底部GridView的添加item单击事件: private GridView paint_bottomMenu; paint_bottomMenu = (GridView)findViewById(R.id.paintBottomMenu); paint_bottomMenu.setOnItemClic