[Android骚操作] RuntimeException与全局异常捕获的巧妙结合

20170701100822333.jpg

前言

由RuntimeException的使用延申和满脑子骚操作的Android开发人员阴差阳错的被全局异常捕获拘留直到Application认领才知道大水冲了龙王庙的Android开发人员跟全局异常捕获原来是远方亲戚

这方法我已经用上瘾了

android_exception_use.gif

代码POU析

实际上从上图看,表面上是看不出来什么的,但是其内容与处理方式已经是天翻地覆的变化了,至于怎么天翻地覆,我们来看看代码就知道了
当然我只贴出部分核心代码,上图的进入主界面的按钮事件监听在这里
事先声明一下,第一个界面是 MainActivity ,第二个界面是 SignInActivity 第三个界面则是登陆后的 HomeActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /* 设置点击事件 */
        findViewById(R.id.start_to_home).setOnClickListener(new View.OnClickListener() {

            /**
             * 你会看到很神奇的一幕,当 {@link com.var.intercept_android.data.Storage} 中的
             * {@link com.var.intercept_android.data.Storage#isSign} 为false的时候.跳转到
             * {@link HomeActivity} 的代码并没有执行
             */
            @Override
            public void onClick(View v) {

                /* 首先检查用户是否登陆 */
                Sign.isSign();

                /* 然后跳转到HomeActivity */
                startActivity(new Intent(MainActivity.this, HomeActivity.class));
            }
        });
    }
}

然而你看到的是,我只是在上边调用了 Sign 类的 isSign() 方法,至于 isSign() 怎么实现的,我们等会在看
其实看到这里,你已经发现有些不对劲了,是吧?正常情况下 isSign() 执行完是要执行跳转到主界面的,然而并没有执行跳转到主界面的那句代码,这是为什么呢?我们再来看看 isSing() 方法里边用了什么 骚操作

public class Sign {

    /**
     * 检查是否登陆
     */
    public static void isSign() {
        /* 如果未登录 */
        if (!Storage.isSign) {
            /* 抛出未登录异常 */
            throw new NotSignException();
        }
    }
}

对的,你没看错 isSign() 方法抛了个名为 NotSignException 的自定义异常,那我们再来看看 NotSignException 是怎么写的

public class NotSignException extends RuntimeException {

    /**
     * 构造方法,一个就够了
     */
    public NotSignException() {
        super("用户未登录");
    }
}

居然是继承的 RuntimeException ,想不到吧,这样的程序居然没崩溃?

原理POU析

灵感来源

事实上,这个骚操作是这样来的,最开始,我准备做一个检查用户是否登陆的功能,但是呢,用返回值为布尔类型的方法来做验证感觉太繁琐,并且吧,if和else写多了也并不怎么美观,所以寻思着怎么让代码执行到某一句的时候不继续执行了

实现原理

众所周知,Java中的异常机制,只要发生异常,那从发生异那句代码开始到整个方法结束的代码都不会执行了,所以我就想到了使用异常的方式来简化代码的骚操作,不过,这个异常也是要有讲究的,他不能在编译的时候就直接被编译器检查出来了,所以要隐藏起来,那就得使用Error或者RuntimeException,不过Error是不可捕获的异常,那么就只能使用RuntimeException及其子类

后遗症处理

既然抛出了异常,那在Java中不处理的就会抛给虚拟机,那虚拟机就会停掉,所以需要写一个异常捕获

延申问题

由于使用环境是在Android上,因为Android平台的特殊机制,所以通常的异常捕获是行不通的,那怎么处理呢?
Android的应用程序里边有一个叫 Looper 的好东西,我们可以通过直接接管 Looper 然后捕获 Looper.loop(); 的异常来捕获主线程的异常信息,然后其他的异常信息则是通过以往的全局异常捕获来实现,我们来看看全局异常捕获是怎么实现的

public class GlobalCrashCapture implements Thread.UncaughtExceptionHandler {

    /* 静态实例 */
    private static GlobalCrashCapture instance;
    /* 应用程序的上下文参数 */
    private Application context;
    /* looper状态 */
    private boolean running = true;

    /**
     * 构造方法
     */
    private GlobalCrashCapture() {
    }

    /**
     * 初始化
     *
     * @param context 上下文参数
     */
    public void init(Application context) {
        this.context = context;
        this.looperException();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 获取当前对象的静态实例
     *
     * @return 返回 {@link GlobalCrashCapture} 对象
     */
    public static GlobalCrashCapture instance() {
        if (GlobalCrashCapture.instance == null) {
            GlobalCrashCapture.instance = new GlobalCrashCapture();
        }
        return GlobalCrashCapture.instance;
    }

    /**
     * 重点在这里,正常情况下主线程的异常就被捕获也会导致虚拟机停止,为了不让虚拟机停止(简单来说是不让APP崩溃)
     * 最好的方法就是接管Android的Looper,然后捕获异常
     * <p>
     * 接管Android的Looper并捕获异常
     */
    private void looperException() {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                while (running) {
                    try {
                        Looper.loop();
                    } catch (Throwable e) {
                        /* 如果是自己定义的异常 */
                        if (e instanceof NotSignException) {
                            /* 那就自己去处理吧 */
                            handleNotSignException();
                        } else {
                            /* 如果不是自己抛出的异常,那就用全局异常捕获去处理 */
                            handleException(e);
                        }
                    }
                }
            }
        });
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        this.handleException(e);
    }

    /**
     * 默认异常信息处理
     *
     * @param ex 异常信息
     */
    private void handleException(final Throwable ex) {
        ex.printStackTrace();
        new Thread() {
            @Override
            public void run() {

                /* 弹个Toast告诉你程序出问题了 */
                Looper.prepare();
                if (BuildConfig.DEBUG) {
                    Toast.makeText(context, "哦豁,程序问题咯: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG).show();
                }
                Looper.loop();

                /* 等三秒 */
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                /* 然后结束应用程序 */
                android.os.Process.killProcess(android.os.Process.myPid());
                System.exit(0);
            }
        }.start();
    }

    /**
     * 未登录异常处理
     */
    private void handleNotSignException() {
        /* 其实你可以定义一个接口,然后使用回调的方式处理,此处为了简单明了的说明问题,不做详细解释 */
        context.startActivity(new Intent(context, SignInActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }
}

首先是保证全局异常捕获的单例,然后是一贯的实现 Thread.UncaughtExceptionHandler 接口来捕获异常,重点在 looperException() 方法, looperException() 方法通过接管 Looper 来实现异常捕获,并将捕获到的异常进行处理,然后将捕获到的 NotSignException 异常信息交由 handleNotSignException() 方法处理,从而达到异常不抛给虚拟机又实现了异常信息收集和代码执行截断的功能,是不是很爽?

最重要的一点是,千万要记得在你的Application的onCeate()方法中初始化全局异常捕获,否则你的应用程序点哪那崩,加上这句就是点哪都不会崩

public class MainApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        /* 初始化全局异常捕获,这个很重要,不加这句你的应用就是点哪哪崩 */
        GlobalCrashCapture.instance().init(this);
    }
}

14988115776887403.png

上边的代码,只要 Storage 中的 isSignfalse , 你在任何地方调用 Sign.isSign() 方法,其后边的代码都不会执行,用在 登陆验证,输入检查,接口请求,Bean对象非空验证等 地方简直就是爽歪歪,当然适用场景不止这些,更多的还有待小伙伴们去探索

干货分享

上边的源码我已经上传Github,有需要的小伙伴可以自己拉下来研究研究,注释到都还是有的,只不过这只是写个Demo而已,有些地方代码没写很详细,也就简单的表述一下我要实现的功能而已
源码地址: https://github.com/scvax/Intercept_Android

如果有疑问,可以在本文下方留言,在我的能力范围内我还是很乐意解答的,我上简书很勤的哦~~

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,458评论 2 59
  • 【Android Handler 消息机制】 前言 在Android开发中,我们都知道不能在主线程中执行耗时的任务...
    Rtia阅读 4,699评论 1 28
  • 懒人活该错过爱情吗,我懒得说,以为你都懂,你也懒得说,以为我会懂,然而,错过就是那么容易…… ...
    茯苓浓汤阅读 710评论 4 4
  • 思维导图是孩子们爱上学习,喜欢上学习的法宝。孩子们如果学习了思维导图,并能掌握,在实际学习生活中灵活运用,家长和老...
    徐露桐阅读 184评论 1 0