2

android线程间通信之handler

 3 years ago
source link: http://www.androidchina.net/7192.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文来一起讨论下Android的handler机制。

相信写过android的童鞋,一定对handler很熟悉。因为使用频率实在太高了。尤其是在非ui线程,想要刷新ui控件的时候。因为ui控件的刷新只能在主线程做,但是我们可能有在非ui线程却需要更新ui的需求,比如在一个后台线程下载了图片需要更新到ui上,这时候就需要主线程handler来发送更新的message。

handler的使用如此频繁,我们有必要知道其内部是如何工作的。

一句话概括

handler, looper, message的组合,能够做什么工作?简单地说,就一句话:在一个线程里,指定在另一个线程里,执行一个任务。

handler thread

什么是handler thread。当一个线程,创建了looper,looper里面拥有message queue,创建了handler,那么,这个线程就是一个handler thread。

handlerthread

handler thread的作用就是,让其他的线程指定handler thread去执行一个任务。比如ui线程就是一个handler thread。我们可以在普通线程中,指定让ui线程去更新ui。

handler

handler有两个工作,一是发送任务或者消息;二是处理消息或者执行任务。

handler可以发送什么
handler和message queue密切联系,直觉上handler会发送消息到message queue。其实不仅如此,handler既能发送message,也能发送runnbale。换句话说,message queue不只是装message的queue(其实是一个单链表),而且还能装runnable。

触发的线程

handler发送消息或者任务,一般是在其他线程发送的,即发送消息时所在的线程,并不是创建handler的线程(当然,也可以在创建handler的线程发消息,等于自己发给自己)。
而handler处理消息或执行任务,则是在创建自己的线程中执行的。

创建handler

handler和looper并不是ui线程独有的。任何一个普通的线程,都可以创建自己的looper,创建自己的handler。
但是有一点需要注意,创建handler前,必须先创建looper。

如果不创建looper,直接new一个handler,比如

new Thread(new Runnable(){
    public void run() {
        Handler handler = new Handler();
    }
}).start();

运行时,会直接报错:

Can’t create handler inside thread that has not called Looper.prepare()

来看看handler的构造函数

public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();  // Looper.myLooper用于获取当前线程的looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        ...
    }

handler发送消息到message queue,所以,构造一个handler的时候必须知道message queue,才能确定把消息发送到哪里。

而message queue是由looper来管理的,因此顺序上,必须先创建了looper,才能创建handler。

创建线程的Looper,

Looper.prepare();

所以,创建一个handler的正确写法是:

new Thread(new Runnable(){
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();
    }
}).start();

可能有同学会觉得奇怪,平时用new Handler() 的时候没有先调用Looper.prepare()也一样可以用呀?那是因为,handler是在主线程创建的。

// ActivityThread.java
public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ...
}

主线程在启动的时候,就会调用Looper.prepareMainLooper() 创建looper,所以,我们可以在主线程直接创建handler,不需要手动先创建looper。

runnable的封装

可能大家会觉得奇怪,message queue应该装的是message,那么handler.post(runnable),runnable跑哪里去了呢?

runnable其实也是发送给了message queue,只不过在发送前,先对runnable进行了封装。

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

getPostMessage 把runnable包装成一个message,message的callback就是runnable。因此,分辨一个message是不是runnable,其实只要看message的callback是否为空,如果为空,就是普通的message,否则,就是一个runnbale。

如何处理消息

看下dispathMessage

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
         handleCallback(msg);    // handler.post(runnable)时走这里
     } else {
         if (mCallback != null) { // handler = new Handler(callback)时走这里
             if (mCallback.handleMessage(msg)) {
                 return;
             }
         }
         handleMessage(msg); // handler = new Handler()时走这里
     }
 }

根据handler发送消息的类型,分成2种情况:

  • handler发送了一个message到message queue
  • handler发送了一个runnbale到message queue

根据前面提到的,message.callback其实就是对runnable的封装,所以,如果handler是发送了一个runnable到message queue,那么就会执行这个runnable。

如果handler是发送了一个message到message queue,那么又细分为2种情况

  • handler创建时设置了callback, 即handler = new Handler(callback);
  • handler创建时未设置callback,即handler = new Handler();

如果设置了callback,那么message会先被callback处理。

如果callback返回true,说明处理完成,不会再传给handler.handleMessage了。

如果callback返回false,说明处理未完成,会再把message传给handler.handleMessage继续处理。

如果未设置callback,message会直接传给handler.handleMessage处理。

message

如何产生消息

消息如何产生
message可以由构造函数生成。更多的时候,是从可回收的消息对象池里面直接获取的,提供性能。从消息对象池获取一个消息的方式是Message.obtain(),也可以用Handler.obtainMessage()。
另外,不产生消息,也可以发送消息。好绕口,啥意思?handler.sendEmptyMessage(),可以发送一个空消息到message queue,不需要构造一个message对象。

消息什么时候发送

消息的处理时机还是由handler来决定(感觉handler管的好宽==)。

handler.sendMessage,把message放到message queue的尾部排队,looper从前往后一个一个取消息。handler.sendMessageAtFrontOfQueue,把message放到message queue的头部,消息可以马上被处理。

handler.sendMessageAtTime,不马上发送消息到message queue,而是在指定的时间点发送。

handler.sendMessageDelayed,不马上发送消息到message queue,而是在指定的时延后再发送。

Looper

创建looper

前面提到,looper就像一个发送机一样,会从message queue中取出消息,然后派发给handler处理。因此,要知道message应该发送到哪个handler,必须先创建looper。

创建looper的方法

Loop.prepare();

通过looper.loop(),looper会不断从message queue取消息,并派发出去。

looper怎么知道message应该派发给哪一个handler呢?

一起看看loop方法

public static void loop() {
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
    }
}

每个msg都有一个target属性,这个target就是发送消息的handler,派发message,就是派发给msg.target对象。

loop方法并不是无限循环的,一旦message queue为空,就会结束,以免长期占用cpu资源。

下图中,线程A是一个handler thread。现在线程B想让线程A处理一个消息message 5。于是,线程B拿到线程A的handler引用,然后调用handler的sendMessage。

send message

message 5被发送到线程A的message queue。

loop

线程A怎么去处理这个消息呢?使用looper.loop方法,每次会从message queue取出一条消息,当取到message 5,说明message 5即将被处理。

handle message

真正的消息处理逻辑,是在handler的handleMessage里面自定义的(或者runnable,callback,这里以handleMessage为例)。
looper取到message 5,通过message 5的target属性,知道目标handler,然后把消息发送给handler进行处理。

handler不是独立存在的,一个handler,一定有一个专属的线程,一个消息队列,和一个looper与之关联。
这几个角色是如何协同工作的呢?简单概括为下面四个步骤:

  1. handler发送消息到message queue,这个消息可能是一个message,可能是一个runnable
  2. looper负责从message queue取消息
  3. looper把消息dispatch给handler
  4. handler处理消息(handleMessage或者执行runnable)

handler和looper的关系有点类似于生产者和消费者的关系,handler是生产者,生产消息然后添加到message queue;looper是消费者,从message queue取消息。

转载请注明:Android开发中文站 » android线程间通信之handler


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK