Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android消息處理機制原理解析

android消息處理機制原理解析

編輯:關於Android編程

在android開發過程中相信屌絲程序員們都用過Handler來處理一些邏輯任務,比如發送延遲消息處理業務等邏輯,我們常用的圖片加載緩存庫ImageLoader和Picasso等內部也是通過Handler來最終有後台加載線程切換到主線程(UI線程)來更新頁面的,今天就趁著離職有點兒時間就抽空的分析了下它的一點源碼,在此總結出來。閒言少敘,書歸正傳!

先來談談Looper:

Looper從源碼上來看就是一個普通的Java類,它在消息機制中,顧名思義,就是一個消息循環的角色。有時候讀源碼,我習慣性的會從它的構造器開始讀,當然有時候會從一個方法切入,根據情況不同采取不同的方式。下面讓我們看看Looper的構造器都做了什麼:

//(每個Looper對象的)消息隊列,也就是說每個Looper對象都持有自己的消息隊列
 final MessageQueue mQueue;
//(每個Looper線程關聯的)當前線程
final Thread mThread;
private Looper(boolean quitAllowed) {
        //初始化當前Looper對象的消息隊列
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();//獲取當前線程
    }   

從上面的代碼中我們可以得出如下簡單的結論:
a. 每個Looper對象都有自己的消息隊列MessageQueue!
b. 每個Looper對象都和當前線程或者說創建Looper的線程相關聯。
那麼問題來了,當前線程是如何跟Looper對象想關聯的呢?如果你讀過Looper源碼,從代碼注釋中你可以看到下面一個代碼:

  class LooperThread extends Thread {
        public Handler mHandler;
        public void run() {
            //注意是在run方法中調用了prepare
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                }
            };
            Looper.loop();
        }
    }

可以發現裡面調用了prepare()這個靜態方法,所以直接看看prepare()這個方法做了什麼了不起的事兒!

//注意為靜態final變量
static final ThreadLocal sThreadLocal = new ThreadLocal();
 public static void prepare() {
        prepare(true);
    }
    private static void prepare(boolean quitAllowed) {
        //一個Thread只能關聯一個Looper對象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

從代碼上不難看出prepare做了兩個工作:
a.在當前線程中創建一個Looper對象,放入ThreadLocal中;ThreadLocal作用簡單來說就是在每個線程中存儲數據,每個線程只能獲取到自己存儲在ThreadLocal的數據,其他的線程是獲取不到自己線程存儲在ThreadLocal的數據的。當然既然用ThreadLocal保存一個Looper那麼我們肯定可以通過ThreadLocal得到這個Looper對象,方法如下:

//獲取當前線程關聯的Looper對象
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

b.如果當前的線程已經有一個Looper對象相關聯,就會拋出異常,也就是說一個Thread只能關聯一個Looper對象

總之一句話prepare()方法就是讓線程關聯Looper對象用的!

寫到此處不難發現Looper已經完成了如下工作:
這裡寫圖片描述
這樣我們線程也綁定Looper了,消息隊列也由Looper對象創建好了,所以該是消息隊列工作的時候了!!!那怎麼才能讓一個LoZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcGVyttTP87PW09C1xM/7z6K208HQuaTX98TYo7/G5Mq1wf3Ns7XEy7XSu77kzt63x77Nyseyu82jtcS008/7z6K208HQ1tCy6b+0yse38dPQ0MK1xM/7z6KjrNPQ1PK0psDto6zO3tTy1+jI+6Oh1eK49rSmwO3P+8+itcS3vbeoxuTKtdTayc/D5rXEwP3X09bQ0rLP1sntuf2jrMTHvs3Kx2xvb3AoKdXiuPa+ssyst723qKOhPC9wPg0KPHByZSBjbGFzcz0="brush:java;"> /**該方法必須在prepare方法之後調用**/ public static void loop() { //獲取當前線程鎖關聯的Looper對象 final Looper me = myLooper(); if (me == null) {//在調用loop()之前必須調用looper.prepare()方法 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //獲取當前對象的消息隊列 final MessageQueue queue = me.mQueue; ..........省略兩行代碼...... for (;;) {//是一個無限循環,來遍歷消息隊列 //獲取一條消息Message對象,可能會造成阻塞, Message msg = queue.next(); // might block if (msg == null) { return; } //該消息的target是一個Handler,來分發消息,具體怎麼分發稍後討論 msg.target.dispatchMessage(msg); .....省略部分代碼.. msg.recycleUnchecked(); } }

可以發現loop()方法其實執行了如下兩步工作:
1)獲取當前線程關聯的Looper對象
2)獲取Looper對象的消息隊列然後開啟無限循環獲取消息和處理消息
重點就是第二步了,在無限循環中有且只有一個跳出的入口:那就是消息隊列的next方法返回了null!另外需要注意的是next方法是一個阻塞方法,這也意味著當MessQueue沒有消息的時候,next方法會阻塞進而使得loop方法也一直阻塞。當然next方法有新的消息的時候就調用 msg.target.dispatchMessage(msg);發送並處理消息!需要注意的消息隊列中的Mssage都有自己的target對象來處理,target對象不唯一!。簡單的概況下可以用如下流程圖簡單表示:
這裡寫圖片描述
通過上面的流程圖也可以清晰的知道在獲取到一個Messge對象之後,通過Message.target.dispatchMessage進行消息的分發和處理。那麼這個target到底值什麼鬼呢?還記得上文的例子代碼LooperThread麼?裡面有個handler是什麼意思呢會不會就這這個target呢?其實回答這個問題之前或許我們應該考慮:“我們是什麼時候向Looper中的消息隊列添加一條條消息來供loop循環讀取呢?“
這就不得不說本篇博文的另一個主角Handler了,順便說一句,Message的target你應該能想到其實也是一個Handler對象,不信?後面又說明!

Handler簡析

先看看Handler的一個構造器,為了說明問題撿了其中的一個構造函數來說明:

//當前線程關聯Looper對象的消息隊列
 final MessageQueue mQueue;
 //當前線程關聯的Looper對象
  final Looper mLooper;
    public Handler(Callback callback, boolean async) {
        ...省略部分代碼..
        //獲取當前線程中關聯的的Looper對象
        mLooper = Looper.myLooper();
        if (mLooper == null) {/不能在Thread裡面創建Hanlder對象,如果沒有調用prepare的話)
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //這個就是初始化關聯對象的地方
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

從上面的代碼中可以看出一個Handler對象有如下信息:
1)Handler對象持有一個Looper對象的引用mLooper 。
2)Handler對象持有一個消息隊列對象的引用mQueue ,並且該引用在構造器中得到了初始化,初始化也很簡單就是把looper對象創建的消息隊列MessageQueue賦值給mQueue對象。
也就是說Handler關聯了Looper對象及Looper對象創建的消息隊列!
3)在一個子線程裡面是如果沒有調用Looper.prepare,不能創建Handler對象!
萬事俱備,是時候回答“什麼時候向消息隊列添加消息”這個問題了!
在使用Handler的時候我們是通過sendMessage方法發送消息的,看看這個方法都做了什麼,查看其源碼,它的調用脈絡是:
sendMessage(Message)–>sendMessageDelayed(Message, long)–>sendMessageAtTime(Message,long);所以直接看sendMessageAtTime這個方法即可:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ****省略了部分代碼**
        return enqueueMessage(queue, msg, uptimeMillis);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //初始化了target從這裡可以看出來target就是一個Handler
        msg.target = this;
       //調用messageQueue的enqueueMessage插入消息
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最終sendMessageAtTime回調用enqueueMessage這個方法,這個方法可以得到如下結論:
a,上圖中loop()循環取的message對象後調用message.target,這個target就是一個Handler!
b.Handler調用sendMessage方法發送消息的過程其實就是向MessageQueue這個消息隊列插入一條消息的過程,另外我們到這裡可以做出以下斷言:在UI線程中(主線程)中創建的Handler對象通過sendMessage發送的Message實際上是添加到了UI線程的消息隊列中!Looper的loop()方法通過循環消息隊列,通過其next方法獲取消息,然後處理之。所以下面就該討論處理流程了。

很簡單就從上圖中的dispatchMessage方法說起:

 public void dispatchMessage(Message msg) {
        //如果創建的msg,設置了callback這個Runnable
        if (msg.callback != null) {
            //執行callback方法,其實是執行run方法
            handleCallback(msg);
        } else {//如果創建Handler的時候創建了Callback對象
            if (mCallback != null) {
                //執行callback的handleMessage方法
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //讓handler自己來處理msg
            handleMessage(msg);
        }
    }

 private static void handleCallback(Message message) {
        //只是簡單的調用了Runnable的run方法
        message.callback.run();
    }
 //簡單的接口,提供了一個handleMessage方法來處理消息
 public interface Callback {
        //返回一個boolean值
        public boolean handleMessage(Message msg);
    }

所以Handler處理消息的過程其實也很簡單:
a.檢測Message的callback!=null,注意這個callback其實是一個Runnable,調用handleCallback方法其實就是執行這個Runnable的run方法而已。
b.如果創建Handler的時候,初始化了mCallack(這個callback並不是一個Ruannable,其實一個接口,該接口也很簡單,就提供了一個handleMessage方法,由客戶端決定怎麼處理這條Message。
c.最後一步就是調用Handler的handleMessage方法來處理消息了。
通常我自己在使用Handler的時候就是用的定義一個Handler的子類,重寫handleMessage方法來處理消息,倒是沒有為Handler創建callback!
同時如果你設置了Handler的Callback,並且Callback的handleMessage方法如果返回true,那麼Handler的handleMessage方法將不會執行;否則Handler的handleMessage方法也會得到執行!
所以通過上面的講解,綜合起來能得到下面的流程圖:
這裡寫圖片描述

到此位置android的消息機制可以說是解說完畢,不過還有些問題值得思考:我們說了這麼多,那麼android主線程(UI線程)的工作機制又是怎麼樣的呢?

UI線程的消息處理

在任何關於activity啟動流程解析的資料中我們都會進入ActivityThread的main方法,這裡當然不會再分析Activity的啟動流程,拿來主義有時候還是不錯的(需要注意一點ActivityThread不是一個Thread,只是一個普通的java對象:

 public static void main(String[] args) {
        //創建主線程的Looper對象,prepareMainLooper內部實際上調用了
        //Looper.prepare(false)方法,並把UI線程交給Looper的靜態變量             //Looper sMainLooper;持有
        Looper.prepareMainLooper();
        //創建ActivityThread對象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        //創建一個Handler,這個Handler就是關聯了UI消息隊列的Handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //開啟了消息循環  
        Looper.loop();
    }
}

結合前面關於Looper的分析,其實ActivityThread的main方法也很簡單,其流程如下:
1)調用Looper.prepareMainLooper()使得UI線程也就是主線程與一個Looper對象想關聯。prepareMainLooper方法如下:

 //提供一個靜態變量來持有UI線程的Looper對象
 private static Looper sMainLooper;  
  public static void prepareMainLooper() {
        //為UI線程創建一個Looper對象
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //靜態變量來引用這個關聯了UI線程的Looper對象
            sMainLooper = myLooper();
        }
    }

既然Looper用一個靜態變量來保存關聯了UI線程的Looper對象,那麼我們可以調用Looper的如下兩個方法檢測是否是主線程:

//Picasso提供的檢測是否是UI線程的方法
 static boolean isMain() {
    return Looper.getMainLooper().getThread() == Thread.currentThread();
  }

而ImageLoader檢測是否是UI線程的方法則是如下:

 Looper.myLooper() == Looper.getMainLooper()

2)創建一個Handler對象,根據前面的講解這個Handler對象同樣關聯了UI線程的looper對象以及該looper對象的消息隊列
3)調用Looper.loop()循環獲取和處理消息。
需要注意的是:根據Looper源碼的注釋,android官方並不希望我們自己主動調用prepareMainLooper()方法。

最後的總結

這樣UI線程的處理流程也簡單的梳理完畢,那麼還有一個最後一個問題:我們知道UI組件的更新是是在UI線程中進行的,也即是如果你在非線程中處理了某個任務後需要更新UI組件,那麼在非UI線程線程工作完成後都需要交給UI線程來處理,怎麼通知UI線程呢?你應該會知道答案:通過Handler,在UI線程中創建Handler,在非UI線程工作完畢後調用UI線程創建的Handler發送消息到UI消息隊列,然後按照上面的流程圖處理即可。
前面我既然分析了ImageLoader的源碼(詳見ImageLoader博客),那麼就根據ImageLoader的工作原理把非UI線程和UI線程的工作流程也做個總結吧,如下圖所示:

這裡寫圖片描述

到此為止android消息處理機制就簡單的講解完畢,如有不當的地方歡迎批評指正,共同學習!
其實多寫博客還是有所幫助的,比如我在寫這篇博客的時候,邊分析Looper和Handler的源碼,有的時候還需要ImageLoader的工作原理來配合加深理解和體會。如果我之前沒有寫過ImageLoader博客的話,理解或者體會或許就不那麼深了,這也算是堅持寫博客對自己的回報吧!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved