App为什么会crash?一篇文章带你探究根本原因 ,事情没有你想得那么简单!(2)

initialized = true;

}

此处会给 Thread 设置一个 KillApplicationHandler 对象,我们可以看到这个 KillApplicationHandler 是实现了 Thread.UncaughtExceptionHandler 这个接口的,所以自然会重写 uncaughtException 方法。

public void uncaughtException(Thread t, Throwable e) {

try {

ensureLogging(t, e);

// Don’t re-enter – avoid infinite loops if crash-reporting crashes.

if (mCrashing) return;

mCrashing = true;

// Try to end profiling. If a profiler is running at this point, and we kill the

// process (below), the in-memory buffer will be lost. So try to stop, which will

// flush the buffer. (This makes method trace profiling useful to debug crashes.)

if (ActivityThread.currentActivityThread() != null) {

ActivityThread.currentActivityThread().stopProfiling();

}

// Bring up crash dialog, wait for it to be dismissed

ActivityManager.getService().handleApplicationCrash(

mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));

} catch (Throwable t2) {

if (t2 instanceof DeadObjectException) {

// System process is dead; ignore

} else {

try {

Clog_e(TAG, “Error reporting crash”, t2);

} catch (Throwable t3) {

// Even Clog_e() fails! Oh well.

}

}

} finally {

// Try everything to make sure this process goes away.

Process.killProcess(Process.myPid());

System.exit(10);

}

}

在代码的最后执行了 System.exit(10) ;这个方法就会直接干掉当前进程,也就是所谓的 App crash 了。

所以我们一旦抛出异常,并且没有捕捉的话,程序就会被强制干掉。

第二个问题:能否让 App 不要 crash

答案自然是肯定的,我们刚才在看代码的时候也看到下面这段代码:

public UncaughtExceptionHandler getUncaughtExceptionHandler() {

//可以看到当uncaughtExceptionHandler没有赋值的时候,会返回ThreadGroup对象

return uncaughtExceptionHandler != null ?

uncaughtExceptionHandler : group;

}

只有在我们没有设置 UncaughtExceptionHandler 的时候,才会调用 defaultUncaughtExceptionHandler 对象,所以自然而然的就想到了实现这个类,然后在这里面做相应的处理。

说干就干试试吧:

我们先试一下主动抛出异常的效果吧,先是在 MainActivity 里面放置一个 Button,让它点击可以主动抛出异常:

package com.netease.demo;

import android.os.Bundle;

import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

public void click(View view) throws Exception{

throw new Exception(“主动抛出异常”);

}

}

来看一下执行效果:

不出意料程序崩溃了。。。

那我们接下来写一个 CrashHandler 的类实现了 Thread.UncaughtExceptionHandler 接口:

package com.netease.demo;

import android.util.Log;

// Created by chendanfeng on 2020-08-19.

public class CrashHandler implements Thread.UncaughtExceptionHandler {

private static final String TAG = “CrashHandler”;

@Override

public void uncaughtException(Thread t, Throwable e) {

Toast.makeText(MyApplication.sApp,"uncaughtException : " + e.getMessage(),Toast.LENGTH_SHORT).show();

}

}

然后在 MyApplication 里面对这个 Handler 进行设置:

package com.netease.demo;

import android.app.Application;

// Created by chendanfeng on 2020-08-19.

public class MyApplication extends Application {

public static MyApplication sApp;

@Override

public void onCreate() {

super.onCreate();

sApp = this;

Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());

}

}

然后再看下效果:

我们发现确实 App 已经不会 crash 了,但是又出现了另外一个问题,那就是 App 卡死在了这个界面,点击无效了。

那么这到底是怎么一回事呢?其实这也不难理解,我们的页面启动的入口是在 ActivityThread 的 main 方法:

public static void main(String[] args) {

…代码省略…

Looper.prepareMainLooper();

…代码省略…

Looper.loop();

throw new RuntimeException(“Main thread loop unexpectedly exited”);

}

在这里面进行初始化主线程的 Loop ,然后执行 loop 循环,我们知道 Looper 是用来循环遍历消息队列的,一旦消息队列中存在消息,那么就会执行里面的操作。整个 Android 系统就是基于事件驱动的,而事件主要就是基于 Looper 来获取的。所以如果这里一旦出现 crash,那么就直接会跳出整个 main 方法,自然 loop 循环也就跳出了,那么自然而然事件也就接收不到,更没法处理,所以整个 App 就会卡死在这里。

既然如此,那有没有其他办法可以保证 App 在抛出异常不 crash 的情况下,又能保证不会卡死呢?

既然 looper 是查询事件的核心类,那么我们是否可以不让跳出 loop 循环呢,乍一想好像没办法做到,我们没法给 loop 方法 try-catch 。但是我们可以给消息队列发送一个 loop 循环,然后给这个 loop 做一个 try-catch ,一旦外层的 loop 检测到这个事件,就会执行我们自己创建的 loop 循环,这样以后 App 内的所有事件都会在我们自己的 loop 循环中处理。一旦抛出异常,跳出 loop 循环以后,我们也可以在 loop 外层套一层 while 循环,让自己的 loop 再次工作。

还是一句老话**“Talk is cheap,show me the code”**,没有什么比代码验证来的更直接的:

package com.netease.demo;

import android.app.Application;

import android.os.Handler;

import android.os.Looper;

import android.widget.Toast;

// Created by chendanfeng on 2020-08-19.

public class MyApplication extends Application {

@Override

public void onCreate() {

super.onCreate();

Handler handler = new Handler(getMainLooper());

handler.post(new Runnable() {

@Override

public void run() {

while(true){

try {

Looper.loop();

} catch (Exception e) {

Toast.makeText(MyApplication.this,“抛出了异常”,Toast.LENGTH_SHORT).show();

}

}

}

});

}

}

执行以下效果:

这样就解决了抛出异常导致 App crash 的问题了~

不过事情当然没有那么快就结束,这里给主线程的Looper 发送 loop 循环都是主线程操作的,那么子线程如果抛出异常怎么办呢,这么处理应该也是会 crash 吧,那就再做个实验吧:

package com.netease.demo;

import android.os.Bundle;

import android.view.View;

import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

public void click(View view) throws Exception{

new Thread(new Runnable() {

@Override

public void run() {

TextView tv = null;

tv.setText(“hello,word”);

}

}).start();

}

}

这段代码TextView没有初始化,然后看下效果:

没错,确实是直接crash的,那这个时候该怎么办呢?刚才说的**Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());**似乎也不行,这是设置当前 Thread 的方法,总不能给每个 Thread 都设置一个吧,这肯定不可取。不过 Thead 里面貌似还有个全局静态的 UncaughtExceptionHandler 对象被遗忘了

// null unless explicitly set

private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

ThreadGroup 里面最终会调用到他的方法,一开始在 RunTimeInit 里面初始化的。既然这样,那我们直接覆盖这个对象应该就可以了吧?那就试试吧:

package com.netease.demo;

import android.app.Application;

import android.os.Handler;

import android.os.Looper;

import android.widget.Toast;

// Created by chendanfeng on 2020-08-19.

public class MyApplication extends Application {

@Override

public void onCreate() {

super.onCreate();

Handler handler = new Handler(getMainLooper());

handler.post(new Runnable() {

@Override

public void run() {

while(true){

try {

Looper.loop();

} catch (Exception e) {

Toast.makeText(MyApplication.this,“抛出了异常”,Toast.LENGTH_SHORT).show();

}

}

}

});

//代码其实也很简单,只需要这里加上这么一句话

Thread.setDefaultUncaughtExceptionHandler(new CrashHandler());

}

}

另外,这里还稍微改造了一下 CrashHandler :
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

《设计思想解读开源框架》

第一章、 热修复设计

  • 第一节、 AOT/JIT & dexopt 与 dex2oat

  • 第二节、 热修复设计之 CLASS_ISPREVERIFIED 问题

  • 第三节、热修复设计之热修复原理

  • 第四节、Tinker 的集成与使用(自动补丁包生成)

    第二章、 插件化框架设计

  • 第一节、 Class 文件与 Dex 文件的结构解读

  • 第二节、 Android 资源加载机制详解

  • 第三节、 四大组件调用原理

  • 第四节、 so 文件加载机制

  • 第五节、 Android 系统服务实现原理

    第三章、 组件化框架设计

  • 第一节、阿里巴巴开源路由框——ARouter 原理分析

  • 第二节、APT 编译时期自动生成代码&动态类加载

  • 第三节、 Java SPI 机制

  • 第四节、 AOP&IOC

  • 第五节、 手写组件化架构

    第四章、图片加载框架

  • 第一节、图片加载框架选型

  • 第二节、Glide 原理分析

  • 第三节、手写图片加载框架实战

    第五章、网络访问框架设计

  • 第一节、网络通信必备基础

  • 第二节、OkHttp 源码解读

  • 第三节、Retrofit 源码解析

    第六章、 RXJava 响应式编程框架设计

  • 第一节、链式调用

  • 第二节、 扩展的观察者模式

  • 第三节、事件变换设计

  • 第四节、Scheduler 线程控制

    第七章、 IOC 架构设计

  • 第一节、 依赖注入与控制反转

  • 第二节、ButterKnife 原理上篇、中篇、下篇

  • 第三节、Dagger 架构设计核心解密

    第八章、 Android 架构组件 Jetpack

  • 第一节、 LiveData 原理

  • 第二节、 Navigation 如何解决 tabLayout 问题

  • 第三节、 ViewModel 如何感知 View 生命周期及内核原理

  • 第四节、 Room 架构方式方法

  • 第五节、 dataBinding 为什么能够支持 MVVM

  • 第六节、 WorkManager 内核揭秘

  • 第七节、 Lifecycles 生命周期


    本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

第一节、 依赖注入与控制反转**

  • 第二节、ButterKnife 原理上篇、中篇、下篇

  • 第三节、Dagger 架构设计核心解密

    [外链图片转存中…(img-ELZbTXYv-1712744394088)]

    第八章、 Android 架构组件 Jetpack

  • 第一节、 LiveData 原理

  • 第二节、 Navigation 如何解决 tabLayout 问题

  • 第三节、 ViewModel 如何感知 View 生命周期及内核原理

  • 第四节、 Room 架构方式方法

  • 第五节、 dataBinding 为什么能够支持 MVVM

  • 第六节、 WorkManager 内核揭秘

  • 第七节、 Lifecycles 生命周期

    [外链图片转存中…(img-zp6pCTpT-1712744394088)]
    本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
    [外链图片转存中…(img-5DUSXA1l-1712744394088)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值