浅析Android中的Handler的消息传递机制

来源:互联网 发布:深入浅出node.js系列 编辑:程序博客网 时间:2024/05/16 14:06

###1.作用

由于主线程中做耗时操作会导致ANR异常,所以需要将网络请求等耗时操作放在子线程中来进行,但是由于在子线程中不能操作UI,所以需要将子线程中获取到的数据传递给UI线程更新.这样,Handler的机制就应运而生了.Handler的机制不仅仅能完成子线程与主线程的通讯,任何线程之间的通讯都可以用Handler.以下三张图由浅到深,根据自己喜好,自由选择看图理解:

图一:
handler

图二:
Handler的消息传递过程

图三:
Handler

###2.使用

    1)在主线程中创建Handler,重写handleMessage方法.用该handler对象在子线程发送消息,然后在主线程中的handleMessage方法中处理消息    2)handler.post(Runnable runnable).

###3.组成:handler消息机制有四大组成部分,分别是:

    1)Handler:用于发送消息和处理消息.    2)Message:用于携带数据和通知.    3)MessageQueue:用于存储消息,是单链表.    4)Looper:无限循环的轮训器,用于从MessageQueue中取出来消息,并交给Handler处理.

###4.源码分析:由于源码解析篇幅过大,这里仅从几个问题中说说源码.

  • (1)为什么我们在使用Handler的时候没有看到Looper和MessageQueue呢?

    主线程默认通过Looper.prepareMainLooper方法准备了一个Looper对象,prepareMainLooper方法的具体实现如下:

    public static void prepareMainLooper() {            prepare(false);            synchronized (Looper.class) {            if (sMainLooper != null) {            throw new IllegalStateException("ThemainLooper has already been prepared.");                    }            sMainLooper = myLooper();                }            }

实际上是先调用了prepare方法,然后myLooper的方法获取Looper对象赋值给sMainLooper.prepare方法的具体代码实现如下:

```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));}```

实际上就是new一个Looper对象,然后通过set方法将其存储带Threadlocal中,那我就猜测myLooper方法应该是通过get方法从ThreadLocal中获取Looper对象.我们看看吧.

    public static @Nullable Looper myLooper() {        return sThreadLocal.get();    }

果然,这样的化我们就获取到了Looper对象.但是MessageQueue对象呢?我们看看Looper的构造函数就知道了.

    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }

在构造函数中,new了一个MessageQueue对象,并赋值给Looper类的成员变量mQueue.

  • (2).怎么实现由主线程向子线程传递数据?
    要实现主线程向子线程中传递数据,那么我们必须在子线程中new一个Handle对象,然后用Handler发送消息给子线程.
    但是在子线程new Handler对象的话会报错:没有Looper,不能new Handler.那么我们就必须先调用Looper.prepare方法准备一个对象,然后调用Looper.loop方法开始轮询.
    prepare方法我们在第一个问题中分析了,会new一个Looper对象,然后通过set的方法将其存储到ThreadLocal中.
public static void loop(){    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;    // Make sure the identity of this thread is that of the local process,    // and keep track of what that identity token actually    is.Binder.clearCallingIdentity();    final long ident = Binder.clearCallingIdentity();    for (;;) {    Message msg = queue.next(); // might block    if (msg == null) {//下面省略很多代码....}

该方法我虽然省略了很多的方法,但是已经足够理解原理了.
首先,第一行就通过myLooper()方法(在问题1中分析过),从ThreadLocal中取出Looper对象,并赋值给局部变量me.
然后me.mQueue得到MessageQueue对象,接着立马通过for(;;)开启一个无限循环,通过MessageQueue对象queue.next()取出消息.
所以通过调用Looper.prepare和Looper.loop()方法之后,我们就能通过Handler来实现主线程向子线程传递数据.

  • (3).Looper是死循环,为什么不会引起ANR?
    我们首先要找到调用Looper.prepareMainLooper和Looper.loop的地方,就是ActivityThread.java类的main方法.这也是应用程序的入口.
    public static void main(String[] args) {    ........        //创建Looper 和 MessageQueue        Looper.prepareMainLooper();        .......        //开启无限循环轮询.        Looper.loop();        throw new RuntimeException("Main thread loopunexpectedly exited");    }

那么问题来了,如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出.这就完了.
所以我们可以得出结论,ActivityThread的main方法主要就是要做消息循环,一旦退出消息循环,那么你的应用也就退出了.
既然我们知道了死循环的重要性,那么为什么死循环不会造成主线程阻塞呢?
首先我们要了解造成ANR的原因:
(1)当前时间没机会得到处理.
(2)当前时间正在处理而没有计时完成.
因为Andriod是由时间驱动的,looper.loop不断接收时间\处理时间,每一个点击,触摸和Activity的生命周期都运行在looper.loop的控制下,如果它停止了,应用程序也就停了.如果某一个消息处理时间过长,那么下一次的用户的交互就得不到及时处理,就产生了卡顿,时间长了就会ANR.所以说明一个消息或者对消息的处理阻塞了looper.loop,不能说looper.loop阻塞了主线程.而且,主线程looper从消息队列中读取消息,当所有消息读完,主线程睡眠,当子线程再次往消息队列中发消息时,主线程将被再次唤醒.主线程被唤醒只是为了读取消息,读取完毕再次睡眠.因此,loop循环并不会对cpu有过多的消耗.
总结:只要looper.loop的消息循环和处理没有被阻塞,那么就不会引起ANR.

  • (4).Looper 在取出消息后怎么能准确无误地将消息交给发送该消息的Handler 对象?
    这里得从Handler的obtaineMessage开始分析,该方法实际调用的Message.obtain(this)方法.看看Message.obtain的源码:
    public static Message obtain(Handler h) {        Message m = obtain();        m.target = h;        return m;    }

源码实现很简单,就是获取一个消息对象,再将Handler对象本身赋值给消息对象的target成员变量.也就是说,而Looper是通过MessageQueue的next方法取出消息的,通过取出这个消息的target属性我们可以得到发送该消息的Handler对象,然后looper对象就能将消息交给发送消息的Handler对象处理了.

1 0
原创粉丝点击