重学 Android 之 Handler 机制

一、前言

      在 Android 开发中,handler 机制的使用几乎随处可见,作为面试中的常客,我们真的了解 handler 吗?想必很多同学会想,当然了,handler 机制不就是 Looper 、handler

MessageQueue 吗?

下面抛砖引玉,我们先来看看这些问题

为什么子线程中不可以直接 new Handler() 而主线程中可以?

真的只能在主(UI)线程中更新 UI 吗?

为什么建议使用 Message.obtain() 来创建 Message 实例?

分发给 Handler 的消息是怎么处理的?

IdleHandler 是什么,使用场景是什么?

Looper 在主线程中死循环,为啥不会 ANR ?

Handler 中的 同步屏障机制 和 异步消息 是什么?

如何让主线程 java 层永不 crash ?

ThreadLocal 是什么?

Handler 中的 阻塞唤醒 是什么? 如何实现的?

      我相信,看了这些问题,心中多少有些疑惑,再看此文,相信更有收获,同时由于作者能力有限,如有不足或者错误地方,欢迎指出。

      下面,我们先来梳理一下 Handler 机制的整个流程,如果想更快知道答案,可按需阅读此文。以下,是本文的整个思维导图,相信通过这个阅读会更加的清晰



二、Handler 说明

1、Handler 机制 是什么?

答:Handler 机制 即为 一套 Android 消息传递机制

2、Handler有什么用?       

答:在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

           

3、Android UI 更新为什么被设计成单线程?

答:如果多个线程对同一个 ui 控制,进行更新,容易发生不可控的错误。如果要解决这种线程安全,最简单的方式是加锁,而且是对每一层加锁,这样会导致 ui 更新的效率变低。所以 android 没有采用线程锁,而是采用了单消息队列机制,需要注意的一点,我们通常说的只能在主线程更新 ui,其实不是特别准确。严格来说,只能在创建 view 的这个线程,才能更新 ui,如果这个 view 在子线程创建的,那也可以在该子线程更新

我们可以看一下 ViewRootImpl 逻辑

void checkThread() {

  if (mThread != Thread.currentThread()) {

    throw new CalledFromWrongThreadException(

      "Only the original thread that created a view hierarchy can touch its views.");                 

  }

}     

      在更新ui前,都会先调用该方法,检测创建 view 和 更新 view 是否是同一个线程,如果不是,则抛出异常。

      需要注意的是:ViewRootImp 在 onCreate() 时还没创建,在onResume() 时,即ActivityThread 的 handleResumeActivity() 执行后才创建, 调用requestLayout(),会触发 checkThread()  ,所以如果你在 onCreate 方法里面,马上开启一个子线程去更新这个 view,可能也不会报错。

三、核心类

  Handler 机制 中有3个重要的类: 

          处理器 类(Handler)

          消息队列 类(MessageQueue)

          循环器 类(Looper)

   

三者的关系如下: 


下面,我们从源码角度来分析整个核心流程

四、核心流程

我们知道一般会通过如下方式使用 Handler

    //步骤1: 创建 Handler

    //此处以 匿名内部类 的使用方式为例

    private Handler mhandler = new  Handler(){

          // 通过复写handlerMessage()从而确定更新UI的操作

          @Override

          public void handleMessage(Message msg) {

            // 需执行的UI操作

          }

    };       

   

    //步骤2:调用 postDelayed 发送消息

    handler.postDelayed(new Runnable() {

          @Override

          public void run() {

            //do something

          }

    }, 500);

      //或者调用 sendMessage 发送消息

      Message msg = Message.obtain();

      msg.what = 1;  // 消息标识

      msg.obj = "AA"; // 消息内容存放

      mHandler.sendMessage(msg);                   

           

实际上,这里涉及到四个核心流程:

创建 Looper 和 MessageQueue ,并将 Looper 保存在 ThreadLocal 中

开启消息循环, 不断从 MessageQueue 获取 Message,并分发给Message 对应的 Handler,如果没有消息,则挂起。

创建 Handler ,并通过 ThreadLocal 获取当前线程的Looper 和 MessageQueue。

通过 Handler 发送消息到 MessageQueue ,以及处理消息

 

1、创建 Looper 和 MessageQueue

  public Handler(Callback callback, boolean async) {

      ...

      mLooper = Looper.myLooper();

      if (mLooper == null) {

          throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");

      }

      mQueue = mLooper.mQueue;

      mCallback = callback;

      mAsynchronous = async;

  }

  public static @Nullable Looper myLooper() {

      return sThreadLocal.get();

  }

      当我们在主线程中,实例化 Handler 对象时,会从 sThreadLocal 获取 Looper ,实际上当在主线程调用时,获取的是主线程的 Looper , 那主线程的 Looper 是什么时候创建和存入的呢?

                 

我们先来回顾一下 app 的启动流程



从上图可知,整个流程涉及的主要角色有:

Instrumentation: 监控应用与系统相关的交互行为。

AMS:四大组件管理调度中心

ActivityStarter:Activity 启动的控制器,处理 Intent 与 Flag 对Activity 启动的影响,具体说来有:1 寻找符合启动条件的 Activity,如果有多个,让用户选择;2 校验启动参数的合法性; 3 返回int参数,代表 Activity 是否启动成功。

ActivityStackSupervisior:这个类的作用你从它的名字就可以看出来,它用来管理任务栈。

ActivityStack:用来管理任务栈里的 Activity。

ActivityThread:最终干活的人 Activity、Service、BroadcastReceiver 的启动、切换、调度等各种操作都在这个类里完成。                       

其中整个流程,主要涉及四个进程:

调用者进程:如果是在桌面启动应用就是 Launcher 应用进程。

System Server 进程:ActivityManagerService 等所在的进程,该进程主要运行着系统服务组件。

Zygote 进程:该进程主要用来fork新进程。

新启动的应用进程:该进程就是用来承载应用运行的进程了,它也是应用的主线程, 处理组件生命周期、界面绘制等相关事情。

                 

所以,整个流程可以概括为:                 

点击桌面应用图标,Launcher 进程将启动 Activity 的请求以 Binder 的方式发送给了 AMS。

AMS 接收到启动请求后,交付 ActivityStarter 处理 Intent 和 Flag 等信息,然后再交给 ActivityStackSupervisior/ActivityStack处理 Activity 进栈相关流程。 同时以 Socket 方式请求 Zygote 进程 fork 新进程。

Zygote 接收到新进程创建请求后 fork 出新进程。

在新进程里创建 ActivityThread 对象,新创建的进程就是应用的、主线程,在主线程 main 方法里开启 Looper消息循环,开始处理创建 Activity。

ActivityThread 利用 ClassLoader 去加载 Activity、创建 Activity实例,并回调 Activity 的 onCreate() 方法。这样便完成了 Activity 的启动。

由此可知,主线程的 Looper 创建在 ActivityThread 的 main 方法中,

我们看一下源码

public static void main(String[] args) {

  ...

  // 1. 为主线程创建1个 Looper 对象,同时生成1个消息队列对象(MessageQueue) 方法逻辑类似 Looper.prepare()

  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();

  // 2.自动开启 消息循环 ->>下面将详细分析

  Looper.loop(); 

}

 

    可以看出,这里调用了 Looper 的 prepareMainLooper 方法,我们看一下

public static void prepareMainLooper() {

    prepare(false);

    synchronized (Looper.class) {

      if (sMainLooper != null) {

          throw new IllegalStateException("The main Looper has already been prepared.");

      }

      sMainLooper = myLooper();

  }

}

 

      可以看出,最终调用了 prepare 方法,这里将 Looper 存储在 sThreadLocal 中,

记录了主线程的 Looper 对象 

public static final void prepare() {

  if (sThreadLocal.get() != null) {

      throw new RuntimeException("Only one Looper may be created per thread");

  }

  // 1. 判断sThreadLocal是否为null,否则抛出异常

  // 即 Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例

  // 注:sThreadLocal = 1个 ThreadLocal 对象,用于存储线程的变量

  sThreadLocal.set(new Looper(true));

}

private Looper(boolean quitAllowed) {

  mQueue = new MessageQueue(quitAllowed);

  // 1. 创建1个消息队列对象(MessageQueue)

  // 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue)

  mRun = true;

  mThread = Thread.currentThread();

}

到这儿可知,调用 prepare 后,会创建一个 Looper 和  MessageQueue, 并把 Looper 保存在 ThreadLocal 中

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,后续详细介绍

2、开启消息循环,获取和分发消息

      接着上面,在 ActivityThread 的 mian 方法中,执行Looper.prepareMainLooper()  创建 Looper 和 MessageQueue 后,最后会执行 Looper.loop() 开启消息循环

ActivityThread.java:             

  public static void main(String[] args) {

    ...

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();

    thread.attach(false);

    ...

    //调用 loop 方法,开启消息循环

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");

  }

Looper.java:

  public static void loop() {

        // 获取当前的 Looper

        // myLooper()作用:返回 sThreadLocal 存储的 Looper实例;若 me 为null 则抛出异常

        // 即 loop()执行前必须执行prepare(),从而创建1个 Looper 实例

        final Looper me = myLooper();

        if (me == null) {

            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

        }

        //获取当前的消息队列

        final MessageQueue queue = me.mQueue;

        .....

        for (;;) {

          //不断的取消息,这里可能会阻塞,比如取到一个消息,但要5分钟后执行,则这里会阻塞5分钟

            Message msg = queue.next();

            if (msg == null) {

                return;

            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long end;

            try {

                //派发消息到对应的 Handler,最后会执行 Hanlder 的 handleMessage 方法 或者传入的 callback

                msg.target.dispatchMessage(msg);

                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();

            } finally {

                if (traceTag != 0) {

                    Trace.traceEnd(traceTag);

                }

            }

            //消息回收, 放入到消息池中,后续调用 obtain 方法,从这儿取消息

            msg.recycleUnchecked();

      }

  }

 

MessageQueue.java:

    Message next() {

        ...

        // 该参数用于确定消息队列中是否还有消息

        // 从而决定消息队列应处于出队消息状态 or 等待状态

        int nextPollTimeoutMillis = 0;

        for (;;) {

            if (nextPollTimeoutMillis != 0) {

                Binder.flushPendingCommands();

            }

            // nativePollOnce 方法在 native 层,若 nextPollTimeoutMillis 为-1,此时消息队列处于等待状态 

            // 获取 ptr,long 类型,它其实是 Native 层的 MessageQueue 对象指针, ptr 的初始化是在 MessageQueue 的构造方法中初始化的

            nativePollOnce(ptr, nextPollTimeoutMillis);

           

            //从消息队列中拿消息,注意这里加锁了

            synchronized (this) {

                final long now = SystemClock.uptimeMillis();

                Message prevMsg = null;

                Message msg = mMessages;

                // 如果 msg.target==null,说明这是一个消息屏障,如果是消息屏障,会遍历整个消息队列,

                // 去搜索是否有异步消息,如果有异步消息,会将第一个异步消息取出

                if (msg != null && msg.target == null) {

                    do {

                        prevMsg = msg;

                        msg = msg.next;

                    } while (msg != null && !msg.isAsynchronous());

                }

                // 出队消息,即 从消息队列中取出消息:按创建 Message 对象的时间顺序取出

                if (msg != null) {

                    if (now < msg.when) {

                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                    } else {

                        // 取出了消息

                        mBlocked = false;

                        if (prevMsg != null) {

                            prevMsg.next = msg.next;

                        } else {

                            mMessages = msg.next;

                        }

                        msg.next = null;

                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);

                        msg.markInUse();

                        //取到消息了,就不往下执行

                        return msg;

                    }

                } else {

                    // 若消息队列中已无消息,则将 nextPollTimeoutMillis 参数设为-1,

                    // 下次循环时,消息队列则处于休眠状态,而且休眠时间是永久,直到被唤醒, 这块后续会介绍

                    nextPollTimeoutMillis = -1;

                }

                ...

                //如果走到了这里,说明没有消息,则判断 mIdleHandlers 是否执行了,如果是第一次没有执行,则执行 mIdleHandlers

                //执行完成后,下一次循环,如果消息还为空,则会将 loop 过程阻塞休眠,等到 enqueueMessage() 方法,插入消息时进行唤醒。

                if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {

                    pendingIdleHandlerCount = mIdleHandlers.size();

                }

                if (pendingIdleHandlerCount <= 0) {

                    mBlocked = true;

                    continue;

                }

                //pendingIdleHandlerCount 在业务调用 addIdleHandler 方法添加

                if (mPendingIdleHandlers == null) {

                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

                }

                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

                ......

            }

            // 执行idle Handler

            // 如果消息队列已经为空了,会执行 idle handlers,

            for (int i = 0; i < pendingIdleHandlerCount; i++) {

                final IdleHandler idler = mPendingIdleHandlers[i];

                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;

                try {

                    //执行 idle handlers

                    keep = idler.queueIdle();

                } catch (Throwable t) {

                    Log.wtf(TAG, "IdleHandler threw exception", t);

                }

                //如果 queueIdle 返回值为 false, 则会移除该回调

                if (!keep) {

                    synchronized (this) {

                        mIdleHandlers.remove(idler);

                    }

                }

            }

            .....

        }

    } 

   

    //可以看出,mPtr 实例化在创建 消息队列时创建

    MessageQueue(boolean quitAllowed) {

        mQuitAllowed = quitAllowed;

        mPtr = nativeInit();

    }

在开启消息循环之前,会先获取当前线程的 looper 对象,并获取消息队列

在取消息的时候,如果第一次消息为空,则会执行 IdleHandler 回调,后面消息再为空,则会直接挂起,等待有新消息加入后,才会唤醒

在取消息的时候,会跟当前时间比较,如果大于当前时间,则会休眠这个时间差

ptr 的初始化是在 MessageQueue 的构造方法中初始化的,它其实是 Native 层的 MessageQueue 对象指针, 后面介绍

nativePollOnce 的挂起实际上最终通过 linux 层的 epoll_wait 方法实现,后面介绍

注意,这里有一些细节,比如当 msg.target==null 为空时,会直接遍历整个消息列表,查找合适的异步消息, 这其实就是消息屏障机制,后面会详细介绍,另外阻塞与唤醒 也会后面介绍,我们先继续往后看

3、创建 Handler ,获取 Looper 和 MessageQueue。

  上面创建 Looper 和 MessageQueue 后,业务层使用时,会创建一个 Handler

   

    //步骤1

    //此处以 匿名内部类 的使用方式为例

    private Handler mhandler = new  Handler(){

        // 通过复写handlerMessage()从而确定更新UI的操作

        @Override

        public void handleMessage(Message msg) {

          // 需执行的UI操作

        }

    };

    public Handler() {

        this(null, false);

    }

    public Handler(Callback callback, boolean async) {                  ...

        mLooper = Looper.myLooper();

        if (mLooper == null) {

            throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");

        }

        mQueue = mLooper.mQueue;

        mCallback = callback;

        mAsynchronous = async;

    }

可以看出,创建 Handler 时,会调用 Looper.myLooper() 获取 当前线程的 Looper 对象,同时获取 MessageQueue, 后续发送消息时,会发送到该消息队列,其中参数  async 代表是否要发送异步消息,设置为 true 后,后续通过这个 Handler 发送的都是异步消息 

4、Handler 发送消息 和 处理消息

Handler 中的 Message 可以分为两类:同步消息、异步消息。一般情况下这两种消息的处理方式没什么区别,只有在设置了同步屏障时才会出现差异。

同步屏障可以通过 MessageQueue # postSyncBarrier 函数来设置, 通过 MessageQueue # removeSyncBarrier 来移除

如何发送异步消息: 通常我们使用 Handler 发消息时, 这些消息都是同步消息,如果我们想发送异步消息,那么在创建 Handler 时使用以下构造函数中的其中一种, async 传true

public Handler(boolean async);

public Handler(Callback callback, boolean async);

public Handler(Looper looper, Callback callback, boolean async);

 

      该过程分为以下三步:

          1、创建消息对象

          2、发送消息

          3、处理消息

(1)创建消息对象

    //构建消息

    Message msg = Message.obtain(); // 实例化消息对象

    msg.what = 1;      // 消息标识

    msg.obj = "AA";  // 消息内容存放

    //获取消息

    public static Message obtain() {

        // Message 内部维护了1个 Message 池,用于 Message 消息对象的复用

        // 使用obtain()则是直接从池内获取

        synchronized (sPoolSync) {

            if (sPool != null) {

                Message m = sPool;

                sPool = m.next;

                m.next = null;

                m.flags = 0; // clear in-use flag

                sPoolSize--;

                return m;

            }

            // 建议:使用obtain()”创建“消息对象,避免每次都使用new重新分配内存

        }

        // 若池内无消息对象可复用,则还是用关键字new创建

        return new Message();

    }

(2)发送消息

  发送消息有两种方式, 如下:

    //方式1

    mHandler.sendMessage(msg);

    //方式2

    mHandler.post(new Runnable() {

        @Override

        public void run() {

            // 需执行的UI操作

        }

    });

    public final boolean sendMessage(Message msg){

      return sendMessageDelayed(msg, 0);

    }

    public final boolean post(Runnable r){

      return  sendMessageDelayed(getPostMessage(r), 0);

    }

    //说明:可以看出,最终调用的都是 sendMessageDelayed 方法,

    //      区别只是 post 方法里面调用了 getPostMessage 方法,

    //    我们看一下 getPostMessage 方法

    private static Message getPostMessage(Runnable r) {

        Message m = Message.obtain();

        //给 Message 的 callback 赋值为传入的 Runnable

        m.callback = r;

        return m;

    }

    //说明:可以看出 post 方法与 sendMessage 区别在于, post 方法 给 Message 的 callback 赋值为传入的 Runnable

    //后续再分发消息,处理消息时,会跟据 callback 做处理,如果 callback 不为空,则直接回调到 callback 即为传入的 Runnable

    //我们继续看下看

    //分析1:sendMessageDelayed(msg, 0)

    public final boolean sendMessageDelayed(Message msg, long delayMillis) {

        if (delayMillis < 0) {

            delayMillis = 0;

        }

        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

    }

    //分析2:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

        // 1. 获取对应的消息队列对象(MessageQueue)

        MessageQueue queue = mQueue;

        ...

        // 2. 调用了enqueueMessage方法 ->>分析3

        return enqueueMessage(queue, msg, uptimeMillis);

    }

    //分析3:enqueueMessage(queue, msg, uptimeMillis)

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

        // 1. 将 msg.target 赋值为 this, 即:把当前的 Handler 实例对象作为 msg 的 target 属性

        msg.target = this;

        // 请回忆起上面说的 Looper 的 loop() 中消息循环时,会从消息队列中取出每个消息 msg,然后执行 msg.target.dispatchMessage(msg) 去处理消息

        // 实际上则是将该消息派发给对应的 Handler 实例       

        if (mAsynchronous) {

            msg.setAsynchronous(true);

        }

        // 2. 调用消息队列的 enqueueMessage()

        // 即:Handler 发送的消息,最终是保存到消息队列->>分析4

        return queue.enqueueMessage(msg, uptimeMillis);

    }

    /**

    * 消息队列消息根据时间 从小到大 排列

    * 分析4:queue.enqueueMessage(msg, uptimeMillis)

    * 定义:属于消息队列类(MessageQueue)的方法

    * 作用:入队,即 将消息 根据时间 放入到消息队列中(Message ->> MessageQueue)

    * 采用单链表实现:提高插入消息、删除消息的效率

    */

    boolean enqueueMessage(Message msg, long when) {

        ...

        synchronized (this) {

            //设置消息 inUse 标识

            msg.markInUse();

            //消息何时被处理

            msg.when = when;

            //mMessages 是消息队列头,注意数据结构是单链表

            Message p = mMessages;

            boolean needWake;

            // 如果队列是空,或者 when 的时间为0,或者当前插入的时间小于表头消息时间

            if (p == null || when == 0 || when < p.when) {

                // 将消息插入表头,并检查当前是否被阻塞,如果被阻塞,设置 needWake 标识

                msg.next = p;

                mMessages = msg;

                needWake = mBlocked;

            } else {

                //判断当前是否被挂起?

                needWake = mBlocked && p.target == null && msg.isAsynchronous();

                Message prev;

                // b. 判断消息队列里有消息,则根据 消息(Message)创建的时间 插入到队列中

                //    将新的msg插入到一个合适的位置,按照 when 字段从小到大排序

                for (;;) {

                    prev = p;

                    p = p.next;

                    if (p == null || when < p.when) {

                        break;

                    }

                    if (needWake && p.isAsynchronous()) {

                        needWake = false;

                    }

                }

                msg.next = p;

                prev.next = msg;

            }

            //如果需要唤醒,唤醒Looper

            if (needWake) {

                nativeWake(mPtr);

            }

        }

        return true;

    }

可以看出,插入消息时,跟据消息的时间插入,时间小的在表头在插入消息过程中, 会判断当前消息队列是否被挂起,如果挂起的话,则会进行唤醒

(3) 处理消息

通过 MessageQueue 的 queue.next() 拣出消息后,调用

msg.target.dispatchMessage(msg)  把消息分发给对应的 Handler,

跟到 Handler -> dispatchMessage 

  public void dispatchMessage(Message msg) {

      //callback 不为空,说明是 post 方法发送的 runnable , 直接 run

    if (msg.callback != null) {

          handleCallback(msg);

      } else {

            //Handler 初始化传入的 Callback

          if (mCallback != null) {

              if (mCallback.handleMessage(msg)) {

                  return;

              }

          }

          //处理事件

          handleMessage(msg);

      }

  }   

      可以看出,分发消息的时候,如果 msg 的 callback 不为空,说明是 post 方法发送的 runnable , 直接执行 run  方法,如果 handler 的 callback 不为空,则执行 callback,否则执行 handleMessage

    到这儿 Handler 的核心流程介绍完了,下面我们来看一些细节

            (文章有点长,大家坚持住 )

五、细节

1、同步屏障/消息屏障 和 异步消息

  (1)说明:     

同步屏障/消息屏障:它也是一个消息,只不过它的 target 是 null,我们可以通过 MessageQueue 的 postSyncBarrier 方法来发送一个消息屏障插入到消息队列,排在它后面的普通消息会一直等待,直到该消息屏障被移除才会有机会被处理,当然需要清除就调用 removeSyncBarrier。                     

异步消息:异步消息通过 Message 的 setAsynchronous 方法来设置,异步消息不受消息屏障的影响,所以如果希望消息能立刻被执行,可以发送异步消息,并将其插入到消息队列的头部。异步消息只有在设置了消息屏障的情况下才生效,否则跟普通消息没有什么区别 

(2)源码

Message next() {

      ...

      for (;;) {

          ...

          nativePollOnce(ptr, nextPollTimeoutMillis);

          synchronized (this) {

            final long now = SystemClock.uptimeMillis();

            Message prevMsg = null;

            Message msg = mMessages;

            // 当该消息为消息屏障时,就直接遍历消息队列,

            // 找到第一个异步消息

            if (msg != null && msg.target == null) {

                do {

                  prevMsg = msg;

                  msg = msg.next;

                } while (msg != null && !msg.isAsynchronous());

              }

            if (msg != null) {

                if (now < msg.when) {

                  nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                } else {

                  mBlocked = false;

                  if (prevMsg != null) {

                      prevMsg.next = msg.next;

                  } else {

                      mMessages = msg.next;

                  }

                    msg.next = null;

                    msg.markInUse();

                    return msg;

                }

              } else {

                nextPollTimeoutMillis = -1;

              }

              ...

            }

            ...

      }

  }

(3)应用

  Android 应用框架中为了更快的响应 UI 刷新事件在 ViewRootImpl.scheduleTraversals

中使用了同步屏障和异步消息

  void scheduleTraversals() {

        if (!mTraversalScheduled) {

            mTraversalScheduled = true;

            //设置同步障碍,确保 mTraversalRunnable 优先被执行

            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

            //内部通过 Handler 发送了一个异步消息

            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

            if (!mUnbufferedInputDispatch) {

                scheduleConsumeBatchedInput();

            }

            notifyRendererOfFramePending();

            pokeDrawLockIfNeeded();

        }

    }

   

    //说明:mTraversalRunnable 调用了 performTraversals 执行 measure、layout、draw

    //为了让 mTraversalRunnable 尽快被执行,

    //在发消息之前调用 MessageQueue.postSyncBarrier 设置了同步屏障

2、阻塞唤醒

(1)说明:

      我们知道不断地进行循环是非常消耗资源的,有时我们 MessageQueue 中的消息都不是当下就需要执行的,而是要过一段时间,此时如果 Looper 仍然不断进行循环肯定是一种对于资源的浪费。

        所以这里使用了阻塞唤醒机制,他是 Linux 的 I/O 多路复用机制 epoll 实现的,epoll 它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作

(2)源码

MessageQueue.java:

    Message next() {       

        ...

        int nextPollTimeoutMillis = 0;

        for (;;) {

            ...

            // nativePollOnce 方法在 native 层,若 nextPollTimeoutMillis 为-1,此时消息队列处于等待状态 

            // 获取mPtr,long 类型,看名字它应该是一个指针,它其实是 Native 层的 MessageQueue 对象指针

            // mPtr 的初始化是在 MessageQueue 的构造方法中初始化的     

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {

                ...

                if (msg != null) {

                    if (now < msg.when) {

                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                    } else {

                        // 取出了消息

                        mBlocked = false;

                        if (prevMsg != null) {

                            prevMsg.next = msg.next;

                        } else {

                            mMessages = msg.next;

                        }

                        msg.next = null;

                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);

                        msg.markInUse();

                        //取到消息了,就不往下执行

                        return msg;

                    }

                } else {

                    // 若 消息队列中已无消息,则将 nextPollTimeoutMillis 参数设为-1,

                    // 下次循环时,消息队列则处于休眠状态,而且休眠时间是永久,直到被唤醒

                    nextPollTimeoutMillis = -1;

                }

                ......

            }

            .....

        }

    }

    MessageQueue(boolean quitAllowed) {

        mQuitAllowed = quitAllowed;

        mPtr = nativeInit();

    }

我们知道, 当在不断从消息队列中取消息时,如果没有消息,并且 mIdleHandlers size 为0 ,就会把消息队列挂起减少 cpu 的消耗,核心方法是 nativePollOnce 传入的 ptr 代表是

消息队列 native 层的指针,那底层到的是怎么实现的呢?

我们看一下,先看一下 ptr 是如何创建的, 看一下 nativeInit

MessageQueue.cpp:

  static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {

        NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();

        if (!nativeMessageQueue) {

            jniThrowRuntimeException(env, "Unable to allocate native queue");

            return 0;

        }

        nativeMessageQueue->incStrong(env);

        return reinterpret_cast<jlong>(nativeMessageQueue);

    }

    说明:可以看出,这里实例化了一个 native 层的消息队列,并把这个指针返回到了 java 层

    static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {

        NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);

        nativeMessageQueue->pollOnce(env, obj, timeoutMillis);

    }

    void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {

        mPollEnv = env;

        mPollObj = pollObj;

        mLooper->pollOnce(timeoutMillis);

        mPollObj = NULL;

        mPollEnv = NULL;

        if (mExceptionObj) {

            env->Throw(mExceptionObj);

            env->DeleteLocalRef(mExceptionObj);

            mExceptionObj = NULL;

        }

    }

    int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {

        int result = 0;

        ...

        result = pollInner(timeoutMillis);

    }

    int Looper::pollInner(int timeoutMillis) {

        ...

        struct epoll_event eventItems[EPOLL_MAX_EVENTS];

        int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        ...

    } 

    可以看出 nativePollOnce 的挂起实际上最终通过 linux 层的 epoll_wait 方法实现具体原理,这里就不再展开了

3、IdleHandler通过 MessageQueue 的 queue.next() 拣出消息后,调用

msg.target.dispatchMessage(msg)  把消息分发给对应的 Handler,

跟到 Handler -> dispatchMessage 

  public void dispatchMessage(Message msg) {

      //callback 不为空,说明是 post 方法发送的 runnable , 直接 run

    if (msg.callback != null) {

          handleCallback(msg);

      } else {

            //Handler 初始化传入的 Callback

          if (mCallback != null) {

              if (mCallback.handleMessage(msg)) {

                  return;

              }

          }

          //处理事件

          handleMessage(msg);

      }

  }   

      可以看出,分发消息的时候,如果 msg 的 callback 不为空,说明是 post 方法发送的 runnable , 直接执行 run  方法,如果 handler 的 callback 不为空,则执行 callback,否则执行 handleMessage

    到这儿 Handler 的核心流程介绍完了,下面我们来看一些细节

            (文章有点长,大家坚持住 )

五、细节

1、同步屏障/消息屏障 和 异步消息

  (1)说明:     

同步屏障/消息屏障:它也是一个消息,只不过它的 target 是 null,我们可以通过 MessageQueue 的 postSyncBarrier 方法来发送一个消息屏障插入到消息队列,排在它后面的普通消息会一直等待,直到该消息屏障被移除才会有机会被处理,当然需要清除就调用 removeSyncBarrier。                     

异步消息:异步消息通过 Message 的 setAsynchronous 方法来设置,异步消息不受消息屏障的影响,所以如果希望消息能立刻被执行,可以发送异步消息,并将其插入到消息队列的头部。异步消息只有在设置了消息屏障的情况下才生效,否则跟普通消息没有什么区别 

(2)源码

Message next() {

      ...

      for (;;) {

          ...

          nativePollOnce(ptr, nextPollTimeoutMillis);

          synchronized (this) {

            final long now = SystemClock.uptimeMillis();

            Message prevMsg = null;

            Message msg = mMessages;

            // 当该消息为消息屏障时,就直接遍历消息队列,

            // 找到第一个异步消息

            if (msg != null && msg.target == null) {

                do {

                  prevMsg = msg;

                  msg = msg.next;

                } while (msg != null && !msg.isAsynchronous());

              }

            if (msg != null) {

                if (now < msg.when) {

                  nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                } else {

                  mBlocked = false;

                  if (prevMsg != null) {

                      prevMsg.next = msg.next;

                  } else {

                      mMessages = msg.next;

                  }

                    msg.next = null;

                    msg.markInUse();

                    return msg;

                }

              } else {

                nextPollTimeoutMillis = -1;

              }

              ...

            }

            ...

      }

  }

(3)应用

  Android 应用框架中为了更快的响应 UI 刷新事件在 ViewRootImpl.scheduleTraversals

中使用了同步屏障和异步消息

  void scheduleTraversals() {

        if (!mTraversalScheduled) {

            mTraversalScheduled = true;

            //设置同步障碍,确保 mTraversalRunnable 优先被执行

            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

            //内部通过 Handler 发送了一个异步消息

            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

            if (!mUnbufferedInputDispatch) {

                scheduleConsumeBatchedInput();

            }

            notifyRendererOfFramePending();

            pokeDrawLockIfNeeded();

        }

    }

   

    //说明:mTraversalRunnable 调用了 performTraversals 执行 measure、layout、draw

    //为了让 mTraversalRunnable 尽快被执行,

    //在发消息之前调用 MessageQueue.postSyncBarrier 设置了同步屏障

2、阻塞唤醒

(1)说明:

      我们知道不断地进行循环是非常消耗资源的,有时我们 MessageQueue 中的消息都不是当下就需要执行的,而是要过一段时间,此时如果 Looper 仍然不断进行循环肯定是一种对于资源的浪费。

        所以这里使用了阻塞唤醒机制,他是 Linux 的 I/O 多路复用机制 epoll 实现的,epoll 它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作

(2)源码

MessageQueue.java:

    Message next() {       

        ...

        int nextPollTimeoutMillis = 0;

        for (;;) {

            ...

            // nativePollOnce 方法在 native 层,若 nextPollTimeoutMillis 为-1,此时消息队列处于等待状态 

            // 获取mPtr,long 类型,看名字它应该是一个指针,它其实是 Native 层的 MessageQueue 对象指针

            // mPtr 的初始化是在 MessageQueue 的构造方法中初始化的     

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {

                ...

                if (msg != null) {

                    if (now < msg.when) {

                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                    } else {

                        // 取出了消息

                        mBlocked = false;

                        if (prevMsg != null) {

                            prevMsg.next = msg.next;

                        } else {

                            mMessages = msg.next;

                        }

                        msg.next = null;

                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);

                        msg.markInUse();

                        //取到消息了,就不往下执行

                        return msg;

                    }

                } else {

                    // 若 消息队列中已无消息,则将 nextPollTimeoutMillis 参数设为-1,

                    // 下次循环时,消息队列则处于休眠状态,而且休眠时间是永久,直到被唤醒

                    nextPollTimeoutMillis = -1;

                }

                ......

            }

            .....

        }

    }

    MessageQueue(boolean quitAllowed) {

        mQuitAllowed = quitAllowed;

        mPtr = nativeInit();

    }

我们知道, 当在不断从消息队列中取消息时,如果没有消息,并且 mIdleHandlers size 为0 ,就会把消息队列挂起减少 cpu 的消耗,核心方法是 nativePollOnce 传入的 ptr 代表是

消息队列 native 层的指针,那底层到的是怎么实现的呢?

我们看一下,先看一下 ptr 是如何创建的, 看一下 nativeInit

MessageQueue.cpp:

  static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {

        NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();

        if (!nativeMessageQueue) {

            jniThrowRuntimeException(env, "Unable to allocate native queue");

            return 0;

        }

        nativeMessageQueue->incStrong(env);

        return reinterpret_cast<jlong>(nativeMessageQueue);

    }

    说明:可以看出,这里实例化了一个 native 层的消息队列,并把这个指针返回到了 java 层

    static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {

        NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);

        nativeMessageQueue->pollOnce(env, obj, timeoutMillis);

    }

    void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {

        mPollEnv = env;

        mPollObj = pollObj;

        mLooper->pollOnce(timeoutMillis);

        mPollObj = NULL;

        mPollEnv = NULL;

        if (mExceptionObj) {

            env->Throw(mExceptionObj);

            env->DeleteLocalRef(mExceptionObj);

            mExceptionObj = NULL;

        }

    }

    int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {

        int result = 0;

        ...

        result = pollInner(timeoutMillis);

    }

    int Looper::pollInner(int timeoutMillis) {

        ...

        struct epoll_event eventItems[EPOLL_MAX_EVENTS];

        int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        ...

    } 

    可以看出 nativePollOnce 的挂起实际上最终通过 linux 层的 epoll_wait 方法实现具体原理,这里就不再展开了

3、IdleHandler

当消息队列中没有消息时,会执行 Idlehandlers,所以它的意义在于,当消息队列中没有任务在执行时,给我们一个回调,让我们有机会去执行一些非紧急任务,而不用担心抢占主线程紧急任务的 CPU 执行时间片,因为此刻消息队列是空闲状态,UI 线程处理完所有 View 事务后,回调一些额外的操作,且不会堵塞主进程 IdleHandler的一个典型应用就是:我们经常在 App 启动的时候会用到 IdleHandler 来执行一些非紧急的初始化任务。

可以通过下面的代码向 MessageQueue 中添加 IdleHandler

mMessageQueue.addIdleHandler(new MessageQueue.IdleHandler() {

      @Override

      public boolean queueIdle() {

          return true;

      }

  }); 

      这里 queueIdle 的返回值是有意义的,如果返回 false,IdleHandler 被回调后就会被移除,也就是说只会回调一次,如果返回true,会多次调用。当然 IdleHandler 也是执行在主线程,如果做过重的任务,仍然有可能会阻塞消息队列,导致后面的消息处理不及时。

4、quit 和 quitSafely

(1)说明:

      Looper 是通过调用 loop 方法驱动着消息循环的进行: 从 MessageQueue 中阻塞式地取出一个消息,然后让Handler处理该消息,周而复始,loop 方法是个死循环方法。

    那如何终止消息循环呢?我们来可看一下 Looper的 quit 方法或 quitSafely 方法,

    二者稍有不同。

(2)源码   

Looper.java

    public void quit() {

        mQueue.quit(false);

    }

    public void quitSafely() {

        mQueue.quit(true);

    }

    void quit(boolean safe) {

        if (!mQuitAllowed) {

            throw new IllegalStateException("Main thread not allowed to quit.");

        }

        synchronized (this) {

            if (mQuitting) {

                return;

            }

            mQuitting = true;

            if (safe) {

                removeAllFutureMessagesLocked();

            } else {

                removeAllMessagesLocked();

            }

            nativeWake(mPtr);

        }

    }

quit: 实际上执行了 MessageQueue 中的 removeAllMessagesLocked 方法,该方法的作用是把 MessageQueue 消息池中所有的消息全部清空,无论是延迟消息, 还是非延迟消息。

quitSalf: 实际上执行了 MessageQueue 中的 removeAllFutureMessagesLocked 方法,通过名字就可以看出,该方法只会清空 MessageQueue 消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely 相比于 quit 方法安全之处在于清空消息之前会派发所有的非延迟消息。

无论是调用了 quit 方法还是 quitSafely 方法只会,Looper 就不再接收新的消息。即在调用了Looper 的 quit 或 quitSafely 方法之后,消息循环就终结了, 这时候再通过 Handler 调用 sendMessage 或 post 等方法发送消息时均返 false,表示消息没有成功放入消息队列 MessageQueue 中,因为消息队列已经退出了。

需要注意的是 Looper 的 quit 方法从 API Level 1 就存在了,但是 Looper 的 quitSafely 方法从 API Level 18 才添加进来

5、ThreadLocal

(1)说明

      在前面,我们经常看到 ThreadLocal, 那它是干什么用的呢?

它其实是 一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有再指定线程中可以获取到存储的数据 ,  对于其他线程来说则无法获取到数据。

(2)源码分析

    class Thread implements Runnable {

        ThreadLocal.ThreadLocalMap threadLocals = null;

        //存储父线程中 ThreadLocalMap 存储的值

        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

        ...

    }

    public class ThreadLocal<T> {

        ...

        private final int threadLocalHashCode = nextHashCode();

        ...

        void createMap(Thread t, T firstValue) {

            t.threadLocals = new ThreadLocalMap(this, firstValue);

        }

        ThreadLocalMap getMap(Thread t) {

            return t.threadLocals;

        }

        static class ThreadLocalMap {

            private Entry[] table;

            // key 为 ThreadLocal, 是一个弱引用

            // value 是保存的值

            static class Entry extends WeakReference<ThreadLocal<?>> {

                Object value;

                Entry(ThreadLocal<?> k, Object v) {

                    super(k);

                    value = v;

                }

            }

            private Entry getEntry(ThreadLocal<?> key) {

                //根据 ThreadLocal key , 映射成一个 hash 值

                int i = key.threadLocalHashCode & (table.length - 1);

                //通过该 hash 值,获取 entry

                Entry e = table[i];

                //然后从 entry 中获取 值

                if (e != null && e.get() == key)

                    return e;

                else

                    return getEntryAfterMiss(key, i, e);

            }

            private void set(ThreadLocal<?> key, Object value) {

                Entry[] tab = table;

                int len = tab.length;

                int i = key.threadLocalHashCode & (len-1);

                for (Entry e = tab[i];

                    e != null;

                    e = tab[i = nextIndex(i, len)]) {

                    ThreadLocal<?> k = e.get();

                    if (k == key) {

                        e.value = value;

                        return;

                    }

                    if (k == null) {

                        replaceStaleEntry(key, value, i);

                        return;

                    }

                }

                tab[i] = new Entry(key, value);

                int sz = ++size;

                if (!cleanSomeSlots(i, sz) && sz >= threshold){

                    rehash();

                }

            }

            ...

        }

        public T get() {

            Thread t = Thread.currentThread();

            //获取当前线程的 ThreadLocalMap

            ThreadLocalMap map = getMap(t);

            if (map != null) {

                //根据 ThreadLocal 获取 entry , 然后从 entry 中获取值

                ThreadLocalMap.Entry e = map.getEntry(this);

                if (e != null) {

                    @SuppressWarnings("unchecked")

                    T result = (T)e.value;

                    return result;

                }

            }

            //如果没有设置值,则返回外部设置的默认值

            return setInitialValue();

        }

        private T setInitialValue() {

            //调用了 initalValue 方法

            T value = initialValue();

            Thread t = Thread.currentThread();

            ThreadLocalMap map = getMap(t);

            if (map != null)

                map.set(this, value);

            else

                createMap(t, value);

            return value;

        }

        public void set(T value) {

            Thread t = Thread.currentThread();

            ThreadLocalMap map = getMap(t);

            if (map != null)

                //否则,保存在该线程的 ThreadLocalMap 中, key 为 ThreadLocal, 值为要保存的内容

                map.set(this, value);

            else

                //如果该线程中 ThreadLocalMap 为空,则创建一个 ThreadLocalMap

                createMap(t, value);

        }

    }

    public final class Looper {

        //内部实例化了一个 ThreadLocal

        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

        private static Looper sMainLooper;  // guarded by Looper.class

        public static void prepare() {

            prepare(true);

        }

        private static void prepare(boolean quitAllowed) {

            if (sThreadLocal.get() != null) {

                throw new RuntimeException("Only one Looper may be created per thread");

            }

            sThreadLocal.set(new Looper(quitAllowed));

        }

        ...

    }

总结:

每个线程都有一个 ThreadLocalMap

ThreadLocalMap 内部是一个 Entry 数组,Entry 为键值对存储,key 为 ThreadLocal 的软引用 value 为要保存的值

当使用 ThreadLocal 保存值时,最终会保存在当前线程的 ThreadLocalMap 中,key 为当前的 ThreadLocal, value 为保存的值

当使用 ThreadLocal 获取值时,最终会从当前线程的 ThreadLocalMap 中,根据 key 为当前的 ThreadLocal 获取 value 如果获取的 value 为空,则返回外部调用 setInitialValue 设置的值

      提到一点,我们知道在 Thread 中的 threadLocals 是最终保存 value 在当前线程的 ThreadLocalMap

      那还有一个 inheritableThreadLocals 属性,也是 ThreadLocalMap 类型,那它是干什么用的呢?

      它其实是用来获取 父线程通过 ThreadLocal 保存的值

      常用的情况,比如在主线程设置了一个值,在子线程需要获取,则可以通过它拿到,

      这里不再详细展开了

6、如何让应用程序 java 层永不 crash

(1)说明

         

在日常开发过程中, 经常会遇到各种 crash , 但是我们真的了解 crash 吗

思考一个问题:

    为什么 Android 程序 发生空指针等异常时,就会导致应用崩溃, 进程结束但是普通的 java 程序发生异常时,只要其他线程还在,虚拟机就不会关闭,进程也不会结束呢 ?

我们先简单回顾一下 app 的启动流程

当我们启动一个进程时,大致流程如下

AMS 调用 startProcessLocked 方法开始,内部经过 Process 、

ZygoteProcess 等层层调用,最终通过 socket 与 zygote 建立起连接

  zygote 进程里的 zygoteServer 收到连接消息后,最终 fork 出一个新的应用进程

新进程创建起来后,会调用  ZygoteInit# zygoteInit 进程进行初始化

        ...

         

    我们看下 ZygoteInit # zygoteInit 做了什么

    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {

        if (RuntimeInit.DEBUG) {

            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");

        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");

        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();

        //1、创建 Binder 线程池

        ZygoteInit.nativeZygoteInit();

        //2、继续调用 RuntimeInit 的 applicationInit 方法

        RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);

    }

    可以看出里面有调用  RuntimeInit.commonInit();  方法,继续看一下

    protected static final void commonInit() {

        //设置异常处理回调

        Thread.setUncaughtExceptionPreHandler(new LoggingHandler());

        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());

        TimezoneGetter.setInstance(new TimezoneGetter() {

            @Override

            public String getId() {

                return SystemProperties.get("persist.sys.timezone");

            }

        });

        ....

        initialized = true;

    }

              可以看出这里设置了

setUncaughtExceptionPreHandler 和

setDefaultUncaughtExceptionHandler

我们先复习一下 Thread 的 Exception 设置:   

Thread.setUncaughtExceptionPreHandler:覆盖所有线程,会在回

调 DefaultUncaughtExceptionHandler 之前调用,只能在 Android Framework

内部调用该方法

Thread.setDefaultUncaughtExceptionHandler:如果在任意线程中调用即可覆盖所有线程的异常,可以在应用层调用,每次调用传入的

Thread.UncaughtExceptionHandler 都会覆盖上一次的,即我们可以手动覆盖系统实现的 KillApplicationHandler       

new Thread().setUncaughtExceptionHandler(),只可以覆盖当前线程的异常,如果

某个 Thread 有定义 UncaughtExceptionHandler, 则忽略全局

DefaultUncaughtExceptionHandler

我们看下 LoggingHandler 和

KillApplicationHandler         

private static class LoggingHandler implements Thread.UncaughtExceptionHandler {

        @Override

        public void uncaughtException(Thread t, Throwable e) {

            // 防止多次运行

            if (mCrashing) return;

            if (mApplicationObject == null) {

                //打印崩溃信息

                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);

            } else {

                StringBuilder message = new StringBuilder();

                //打印崩溃信息

                message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");

                final String processName = ActivityThread.currentProcessName();

                if (processName != null) {

                    message.append("Process: ").append(processName).append(", ");

                }

                message.append("PID: ").append(Process.myPid());

                Clog_e(TAG, message.toString(), e);

            }

        }

    }

    可知 LoggingHandler 的作用是 打印异常日志,我们继续看下 KillApplicationHandler

  private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {

        public void uncaughtException(Thread t, Throwable e) {

            try {

                //防止多次调用

                if (mCrashing) return;

                mCrashing = true;

                //刷新缓冲区,便于调试崩溃

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

                    ActivityThread.currentActivityThread().stopProfiling();

                }

                // 打开 crash 对话框

                ActivityManager.getService().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));

            } catch (Throwable t2) {

                                    ...

            } finally {

                //1、杀死当前进程

                Process.killProcess(Process.myPid());

                System.exit(10);

            }

        }

    } 

    可以看出,这里是 app crash 的原因, 线程发现异常后,直接就杀死进程了

所以,一般我们会在 Application 里面覆写 UncaughtExceptionHandler

(2)黑科技

那么有什么方式,可以不让发生 crash 吗?

           

上面我们说到,可以在应用层,调用 Thread.setDefaultUncaughtExceptionHandler 来实现所有线程的 Uncaught 异常的监听,这适用于 子线程发生异常,如果是主线程发生异常,

如果主线程被销毁了, 则跟 crash 没有什么区别

             

我们知道 Looper 内部是一个死循环,不断的从 MessageQueue 中取出消息,由消息通知来做任务,那么我们可以捕获 Looper.loop 的 异常

//扑捉所有线程的异常

  public class CrashCatch {

        ...

        public void setCrashHandler(CrashHandler crashHandler){

            mCrashHandler = crashHandler;

            //主线程异常拦截

            new Handler(Looper.getMainLooper()).post(new Runnable() {

                @Override

                public void run() {

                    for (;;) {

                        try {

                            Looper.loop();

                        } catch (Throwable e) {

                            if (mCrashHandler != null) {

                                //处理异常

                                mCrashHandler.handlerException(Looper.getMainLooper().getThread(), e);

                            }

                        }

                    }

                }

            });

            //所有线程异常拦截,由于主线程的异常都被我们catch住了,

            //所以下面的代码拦截到的都是子线程的异常

            Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

                @Override

                public void uncaughtException(Thread t, Throwable e) {

                    ....

                }

            });

        }

  }

   

        原理很简单,就是通过 Handler 往主线程的 MessageQueue 中添加一个 Runnable ,当主线程执行到该 Runnable 时,会进入我们的 for 死循环。如果 for 死循环 内部是空的就会导致代码卡在这里,最终导致 ANR,但我们在 for 死循环 中又调用了 Looper.loop(),这就导致主线程又开始不断的读取 queue 中的 Message 并执行,这样就可以保证以后主线程的所有异常都会从我们手动调用的 Looper.loop() 处抛出,一旦抛出就会被 try{}catch 捕获,这样主线程就不会 crash 了。

      如果没有这个 for 死循环 的话那么主线程下次抛出异常时我们就又捕获不到了,这样 App 就又 crash 了,所以我们要通过 for 死循环 让每次 crash 发生后都再次进入消息循环,for 死循环 的作用仅限于每次主线程抛出异常后迫使主线程再次进入消息循环。

      为什么要通过 new Handler.post 方式而不是直接在主线程中任意位置执行 for 死循环

{ try { Looper.loop(); } catch (Throwable e) {} }。这是因为该方法是个死循环,若在主线程中,比如在 Activity 的 onCreate 中执行时会导致 for 死循环 后面的代码得不到执行,Activity 的生命周期也就不能完整执行,

通过 Handler.post 方式可以保证不影响该条消息中后面的逻辑。

六、源码 Handler 机制运用

            Android 中源码 Handler 机制用得很多,比较有代表性的一个是上面讲到的

ViewRootImpl 另一个就是 Android 源码中四大组件的生命周期回调,都是通过 Handler 切换到主线程的, 那为啥需要 Handler 呢?这是因为我们的进程启动后,需要和 AMS 进行通信,而通信的载体当然是通过 Binder , 在我们的应用进程中 ApplicationThread 就是一个 Binder, 并且运行在 Binder 线程池中,所以各个生命周期的回调,需要通过 Handler 来切换到主线程中

由于篇幅有限,本文不再详细介绍。

后续会专门出一系列关于 framework 、binder 的相关介绍,欢迎关注

七、结语

        到这里,整个 Handler 机制基本结束了,希望本文能给大家帮助,同时,由于作者能力

有限,文中有错误或者不对的地方,欢迎大家指出。

        写文,更多的是一个知识交流,思想碰撞的过程,希望能大家共同进步!

        最后想说的是原创不易,还请大家点个赞哈,关注我不迷路

 


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

推荐阅读更多精彩内容