Android源码学习--Android Handler 消息机制

Handler 在开发中使用的非常平凡,它基于一种队列消息机制,循环处理消息或者开发者的代码任务,实现不同线程之间的通信,下面结合源码学习其如何实现。

  • 主线程如何建立Handler消息机制

当我们点击launch的app图标时,系统会做什么,系统肯定会首先创建一个进程,为该app独有,接着,肯定是主线程了,也就是平常经常说的UI线程,不能执行耗时操作...,根据java的特性,肯定会执行一个类的main方法,这里,系统执行了ActivityThread的main方法。
点开ActivityThread类,main如下(代码太长,省略部分):

ActivityThread:
    public static void main(String[] args) {
    ...

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
      ...
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
   }

首先调用了Looper的prepareMainLooper(),所以开发者就不需要在主线程调用Looper.prepare()直接使用handle,该静态方法内容如下:

    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(),默认传true,下面会分析
    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));
}

和咱调用Looper一样,主线程的Looper也是得先prepare,但传入了false值,深入prepare()源码,可以发现,该false值最终传入了Looper对象所维护的MessageQueue中的mQuitAllowed变量中,而在MessageQueue中作用是什么呢,ctrl+f,查找mQuitAllowed使用过的地方...发现仅在下面这些地方出现过:

MessageQueue:
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();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

噢了,用来防止开发者终止主线程的消息队列的,停止Looper循环的。继续Looper的prepareMainLooper(),在Looper prepare完后,就是创建Looper对象了,并且存在了Looper静态变量中。这样主线程的Looper就算准备完毕,Looper的消息队列MessageQueue也创建处理,作为传送带的角色随时待命开机传送...

再回到ActivityThread的main方法,在 Looper.prepareMainLooper()执行完毕后,就轮到Handler处理者角色创建啦,作为一套成型的消息处理机制,Handler作为消息的消费者,地位肯定不可或缺。

再分析下main方法,在执行 sMainThreadHandler = thread.getHandler();之前,主线程创建了ActivityThread对象并调用了attach函数,这里干嘛呐,这里就涉及到应用的初始化啦,比如Application创建,Activity创建,执行应用的生命周期啊等等,挺复杂,还没转过弯...反正里面的操作也是向主线程消息队列发送消息的。

但是这里可以想下,既然这里send了Message,但Looper还没有开始启动MessageQueue循环处理啊,有用吗?这里再一步步深入Handler源码的sendMessage方法,关键代码如下:

Handle:
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
      if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  }
MessageQueue:
  boolean enqueueMessage(Message msg, long when) {
        ...
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        } 
        ...  
    return true;
}

可以看出,Handle的sendMessage是将Message send进了MessageQueue,enqueueMessage()方法中就是执行队列的操作,将Message加入一个队列中了,就相当于一个个的放上传送带了并且通过next引用互相联系着,通过for(;;)循环,找到队列的最后个Message,然后将最后一个的Message的next指针指向新来的msg,链表结构哈。

这样就清楚上面的疑惑了,传送带还没开动,但先把物品给放上去排好,待会跑起来了,再一个个给处理,一点不耽误。

啥时候跑起来呢,继续撸咱的main方法,好了,说曹操曹操就到,接下来就是 Looper.loop();传送带开关开了,老司机开车了,Looper.loop()源码:

Looper:
    /**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
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) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }

为什么要循环起来呢,因为线程的生命周期限制于它的有效代码行数,循环起来,有效代码相对增多了,生命周期就延长了,这也保证了app能一直运行了,这也是主线程的Looper调用quit()会抛异常的原因,主线程quit了app还怎么玩...

开始 loop之前,要获取当前线程持有的Looper对象,有吗,没有就果断抛异常,有?拿出Looper的MessageQueue传送带来,接下来for循环开启传送带开关,运转起来了。

这里为啥用for(;;)而不是while呢,有说习惯问题,有说防止被修改字节码,也有人说比while少个判断,具体也是不清楚,还请高人指点...

接下来循环体就直截了当了,MessageQueue的next是阻塞方法,在这里等待Handle send的Message,来一个message,就放行一次,然后从message中获取Handler引用,让handle dispatchMessage,dispatchMessage也不复杂:

Handle:
  public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

先判断有没有Runnable类型消息,有就执行run回调呗,没有就走实例化Handler时重写的handleMessage,里面就根据Message类型及参数来执行不同逻辑。

Handle消息处理流程图

到这里就建立好了主线程的消息处理机制,应用层开发者直接通过实例化Handle sendMessage或者postRunnable就可以向主线程发送消息了。

  • 主线程不能有多余5秒的耗时操作

都知道主线程中不能有超过5秒的无响应操作,否者就ANR(Application Not Responding)了,结合上面loop()源码的分析,既然主线程一直阻塞在等待send的message,那么,计时系统应该在msg.target.dispatchMessage(msg); 处理message方法的前后加的,在执行前,开始计时,当达到5秒时,跳出ANR异常,所有,初步猜想可能和Trace这个类有关,还请指点!

  • 子线程创建Handler消息机制

子线程创建Handler消息机制,步骤和主线程的一样,先Looper.prepare(),来为当前线程绑定新的Looper对象,同时,Looper对象会实例化持有的MessageQueue,然后就可以通过实例化Handler发送Message了,但这时发了是处理不了的,Looper需要通过loop循环当前的MessageQueue,一个个取Message。
所有,子线程有上面操作需要注意啦,loop()之后,子线程生命周期就延长了,除非调用Looper.quit(),就结束循环了。

  • 为何子线程中直接使用Toast报错

翻看Toast源码,可以看到Toast底层也是用了Handler机制发送消息,这就不难理解了,Handler消息机制需要依附于当前线程的Looper对象来运作,子线程没有自己的Looper,Handler也就使用报错,Toast也就不能用啦。
具体参看:http://blog.csdn.net/saroll57/article/details/23276275

  • 为何UI操作要在主线程中

UI操作不是线程安全的,比如View的invalidate()就是非线程安全的,View的各种操作最终都会导致invalidate()函数执行,多个线程执行同一UI操作就会导致同时有多次刷新,就会出现异常情况。

子线程操作UI,可以通过获取主线程handler的引用发送message来实现,或者通过:

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

推荐阅读更多精彩内容