編輯:關於Android編程
在android開發過程中相信屌絲程序員們都用過Handler來處理一些邏輯任務,比如發送延遲消息處理業務等邏輯,我們常用的圖片加載緩存庫ImageLoader和Picasso等內部也是通過Handler來最終有後台加載線程切換到主線程(UI線程)來更新頁面的,今天就趁著離職有點兒時間就抽空的分析了下它的一點源碼,在此總結出來。閒言少敘,書歸正傳!
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 ThreadLocalsThreadLocal = 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的一個構造器,為了說明問題撿了其中的一個構造函數來說明:
//當前線程關聯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線程)的工作機制又是怎麼樣的呢?
在任何關於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博客的話,理解或者體會或許就不那麼深了,這也算是堅持寫博客對自己的回報吧!
bugreport是什麼,怎麼用?Android系統想要成為一個功能完備,生態繁榮的操作系統,那就必須提供完整的應用開發環境。而在應用開發中,app程序的調試分析是日常生
前言:前面幾篇文章介紹了補間動畫、逐幀動畫、屬性動畫,大部分都是針對View來實現的動畫,那麼該如何為了一個ViewGroup添加動畫呢?今天結合自定義ViewGroup
擴展自定義相機應用程序 在我看來,Android 上的內置相機應用程序缺少幾個基本特征。其中之一是,延遲一小段時間,10或者30秒,之後進行拍攝。此種功能對於那些可以安裝
“我沒有很聰明,也不是那麼努力,我只是有點不服輸”0x01.簡介A notification is a message you can displ