Android的Handler消息机制详解

2017-08-28  本文已影响162人  蓝灰_q

我们知道,Android里面的线程做了这样的分工:主线程不做耗时操作,子线程不更新UI。
这是因为Android系统是个单线程模型,系统给App分配的进程里,只有一个主线程,主线程是App进程的核心,所有的组件都在主线程里实现、调度和管理,组件的绘制、互动操作和生命周期回调都是主线程处理的,这个线程如果出现阻塞、或多线程死锁、或CPU饥饿,导致事件处理时间过长,甚至没有机会得到处理,系统就会处理为ANR,强制关闭App。
所以主线程不能做耗时操作,耗时操作需要我们在子线程里处理。同时,Android不允许子线程刷新UI,因为Android无法保证多个线程同步操作,这样子线程和主线程之间就需要做协调,比如子线程从网络服务器拿来数据要展示到UI,就需要告知主线程去向UI里刷新数据,这就需要Handler消息机制了。

主线程、Looper和消息队列

Handler消息机制是围绕一个消息队列展开的,系统启动App时,App进程会启动ActivityThread主线程,主线程启动时,会创建一个Looper来循环处理消息,ActivityThread的main函数主要就是让这个Looper做死循环,如果消息循环停止了,App也就停止了:

//ActivityThread源码
Looper.loop();
//主线程会一直loop,如果出了looper,就直接抛异常,关闭app
throw new RuntimeException("Main thread loop unexpectedly exited");

Looper自己会去创建和管理一个消息队列,然后就开始不断地处理队列里的消息,子线程向消息队列里发消息,就可以和主线程通信了。
消息队列为空时,主线程会休眠,释放CPU资源。(主线程大部分时间都在休眠)


主线程和子线程通信

但是,Android为了确保主线程安全,Looper的消息队列是ThreadLocal的,禁止其他线程访问,所以子线程不能直接向消息队列里发消息。


线程安全禁止直接操作
这样,就需要在主线程里放一个Handler,Handler在创建时,会引用所在线程的Looper,进而获得Looper中的消息队列,这样,我们就可以用Handler来帮忙处理消息了。
通过Handler处理消息
这样一来,处理消息的主力就是Handler了。

Message

为了配合Handler,Message里面有这样几个主要属性:

  1. what:int值,存放消息ID(Send方式)
  2. callback:Runnable对象(Post方式,所以如果消息的callback非空,就直接处理runnable)
  3. target:Handler对象,指向Handler(消息还是丢给Handler处理)
  4. data: Bundle对象,存放业务数据
  5. next:Message对象,存放队列中的下一个Message(链式)

这几个主要的属性在Handler消息机制中非常重要,Handler要使用这几个属性才能正常运转。

Handler

Handler有两个作用:

  1. 发消息,子线程通过Handler,向消息队列发消息
    发消息有两种方式,send一个消息ID(msg.what),或者post一个Runnable(msg.callback),这两种方式最终都是包装为一个Message,通过一个sendMessageAtTime函数,把消息推进队列里。
  2. 处理消息,Looper通过Handler,逐个处理队列中的消息
    Looper在逐个处理消息时,拿到每个msg后,会去执行msg.target.dispatchMessage(msg),这里面的target其实就是Handler,这里会做一个判断,如果msg.callback不为空,说明消息里有Runnable(post进来的),就调用msg.callback.run,整个过程其实就是在Handler里调用Runnable的run函数;
 mHandler.post(new Runnable() {
       @Override
       public void run() {
       }
 });

如果msg.callback为空,说明没有Runnable(send进来的),就调用handlemessage,也就是执行Handler的handlemessage函数;

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

这样,Handler消息机制就能正常运转了。
Handler消息机制多见于主线程UI刷新,不过这并不是它的主业,它其实是用来实现Android线程间通信的,所以只要是跨线程的通信,都需要用到Handler,我们可以自己在子线程里写Looper。
如果我们要在子线程里使用Looper,可以使用HandlerThread,它会帮我们维护一个Looper;如果我们直接用Thread,就需要自己在run函数里做Looper的prepare和loop。

HanderThread

HanderThread就是handler+thread+looper,它有这么几个特点:
1.本质上是一个Thread
2.已经准备好了Looper,可以做Looper循环
3.本身是工作线程,其looper不在UI主线程,可以在handleMessage方法中执行异步任务
4.虽然不能并发处理多任务(因为是一个线程,不是线程池),但是性能损耗小
5.因为已经有Looper,所以可以在线程里创建和使用Handler

其他机制

Android的其他异步处理机制,其实都与Handler机制有关:

if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }

Handler的内存泄露

我们使用Handler时,如果用内部类或匿名内部类的方式去使用,就会遇到内部类持有外部对象导致的内存泄露(非静态内部类,在编译后的构造函数中会引用所在的外部类),解决方式有两种:

  1. 使用静态内部Handler类(+弱引用)。
  2. 在组件的onDestroy中,调用handler.removecallback()方法。

同样的,由于AsyncTask封装了handler,也会有同样的内存泄露问题,解决方法也是使用静态内部类+弱引用,或在onDestroy时及时调用asyncTask.cancel()方法,否则就会内存泄露,甚至导致崩溃(AsyncTask找不到要处理的View对象)。

引用

从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)

Android中Thread、Handler、Looper、MessageQueue的原理分析

源码解析Android中AsyncTask的工作原理

上一篇下一篇

猜你喜欢

热点阅读