在寫這篇文章以前,先祝你們中秋佳節快樂,在這三天小假裏,祝你們玩的開心!好了接下來狗哥給你們分析一下Handler的機制原理,這也算是我閒閒的三天假的一個補償吧。其實Handler機制原理已經被許多大佬寫透的東西了,這裏咱們爲何還要說呢?由於對於像我這種的菜狗來講,也只是會使用Handler而已,對於其機制,也只是大概知道,今天咱們來結合源碼深刻剖析其原理。html
咱們都知道,Android 中經常使用的網絡請求都是在子線程中進行的,而更新UI的操做都是在主線程中進行的,說到這,猿猿們確定會吐槽了,那這跟你寫的Handler有啥關係啊。狗哥在這給大家解釋一下: 從 Android 4.0 開始,Android 中就強制不容許在主線程中進行網絡請求,也不容許在子線程中更新UI,因此Handler機制應運而生,做爲一套線程間通訊的機制,只不過常常被咱們用於更新UI。安全
首先介紹其原理以前,先給你們分析一下handler的四大類
Handler 負責發送消息及其處理消息(發送者和處理者)
Message 系統傳遞的消息
MessageQueue 消息隊列,負責存儲消息
Looper 消息泵,不斷從消息隊列中取出消息而且發送給持有本條消息的Handler
介紹完handler的四大類,咱們簡單理解一下他的機制原理 :
Handler發送一個 Message 到 MessageQueue ,這時 handler 是充當發送者對象而後經過消息泵 Looper ,不斷從消息隊列
中取出消息,而後再經過 handler 處理消息,這時的 handelr 是充當的一個處理者的形象。
複製代碼
來咱們首先看一下 handler 類的初始化操做bash
public class Handler {
final MessageQueue mQueue; // 關聯的MessageQueue
final Looper mLooper; // 關聯的looper
final Callback mCallback;
public Handler() {
// 默認關聯當前線程的looper
mLooper = Looper.myLooper();
// looper不能爲空,即默認的構造方法只能在looper線程中使用
if (mLooper == null) {
throw new RuntimeException(
//沒法在未調用loop .prepare()的線程內建立處理程序。
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 直接把關聯looper的MessageQueue做爲本身的MessageQueue,
// 所以它的消息將發送到關聯looper的MessageQueue上
mQueue = mLooper.mQueue;
mCallback = null;
}
}
複製代碼
咱們經過源碼發現,初始化中handler 關聯了 messageQueue 及其 looper 對象。網絡
咱們一般會這樣建立handler,在handler建立時會關聯一個looper,默認的構造方法將關聯當前線程的looper。這也就是咱們常常建立 handler 對象的兩種方式。app
Handler handler = new Handler(Looper.getMainLooper());
Handler handler = new Handler();
複製代碼
handler關聯了 looper 對象和 messagequeue 對象,才能完成調度工做,而在整個調度工做中,looper 對象纔是真正的核心,也就至關於整個團隊的leader 了,下面咱們仔細、認真的分析一下 looper 的源碼。ide
Looper.getMainLooper(),咱們發現handler關聯當前線程 looper 的入口實際上是在 ActivityThread 的 main() 方法中進行的。而在 main() 方法中它還作了另一件重要的事情。函數
public static void main(String[] args) {
//主線程會建立一個Looper對象。
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//執行消息循環
Looper.loop();
}
複製代碼
沒錯就是執行消息循環 Looper.loop(); 。 總結 main()中作的兩件事 :oop
一、Looper.prepareMainLooper(); //主線程會建立一個Looper對象。
二、Looper.loop(); //執行消息循環
複製代碼
接下來咱們逐個分析:源碼分析
一、Looper.prepareMainLooper();post
public static void prepareMainLooper() {
//在主線程中,其默認初始化一個Looper對象,所以咱們在主線程的操做中是不須要本身去調prepare()。
prepare(false);
synchronized (Looper.class) {
//這裏先進行判斷,在主線程是否已經存在Looper了,
// 避免咱們手動去調用prepareMainLooper(),由於這個是給程序入口初始化的時候系統會自動調用的
if (sMainLooper != null) {
//看拋出的異常,初始化looper已經被準備好了
throw new IllegalStateException("The main Looper has already been prepared.");
}
//設置全局變量,主線程的looper
sMainLooper = myLooper();
}
}
複製代碼
注意這個函數的註釋,大概意思是:在主線程建立一個looper,是這個主線程的主looper,當這個app在初始化的時候就會自行建立,所以這個函數不是給大家調用的,是給系統自身在程序建立的時候調用的。
繼續往下看,有個prepare(boolean)函數,咱們去看看這個究竟是用來幹什麼的。
private static void prepare(boolean quitAllowed) {
//先判斷當前線程是否已經存在Looper了,若是存在,不容許設置新的Looper對象,一個線程只容許存在一個Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//在當前線程中,建立新的Looper對象,並綁定當前線程
sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
咱們看到了sThreadLocal,咱們先看看這個sThreadLocal在Looper是幹什麼用的。
//sThreadLocal在Looper中做爲全局變量,用於保存每一個線程中的數據,能夠看作是容器
public static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
複製代碼
Looper中,sThreadLocal做爲一個全局變量,sThreadLocal實際上是保存Looper的一個容器,咱們繼續往ThreadLocal的get、set進行分析。
public T get() {
//獲取當前線程保存的對象--經過get函數來獲取Looper對象
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
return setInitialValue();
}
public void set(T value) {
//把當前的looper保存到當前線程中
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
複製代碼
關鍵的代碼:
Thread t=Thread.currentThread();
複製代碼
也就是說,咱們的Looper對象分別保存在相對應的線程中。咱們再回來看剛纔前面 的prepare(boolean)函數:
private static void prepare(boolean quitAllowed) {
//先判斷當前線程是否已經存在Looper了,若是存在,不容許設置新的Looper對象,一個線程只容許存在一個Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//在當前線程中,建立新的Looper對象,並綁定當前線程
sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
Looper.prepare()函數總結:
Looper.prepare(boolean)的做用就是建立一個Looper對象,並與當前線程綁定在一塊兒。
在代碼中,首先判斷當前線程是否已經存在looper,若是不存在則建立新的looper而且綁定到當前的線程上。
複製代碼
而後咱們再會看以前 prepareMainLooper() 的代碼:
public static void prepareMainLooper() {
//在主線程中,其默認初始化一個Looper對象,所以咱們在主線程的操做中是不須要本身去調prepare()。
prepare(false);
synchronized (Looper.class) {
//這裏先進行判斷,在主線程是否已經存在Looper了,
// 避免咱們手動去調用prepareMainLooper(),由於這個是給程序入口初始化的時候系統會自動調用的
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//設置全局變量,主線程的looper
sMainLooper = myLooper();
}
}
複製代碼
那麼咱們看到sMainLooper是什麼,myLooper()又是什麼呢?
//保存一個主線程的looper
private static Looper sMainLooper; // guarded by Looper.class
public static Looper myLooper() {
//使用當前線程的looper
return sThreadLocal.get();
}
複製代碼
總結:
sMainLooper在Looper作爲一個全局變量,保存主線程綁定的looper,myLooper()則是獲取當前線程綁定的Looper。
在prepareMainLooper()中,在主線程中建立一個新的Looper,而且綁定主線程中,同時把這個主線程的looper賦值給
sMainLooer這個全局變量。
複製代碼
剛纔講到 handler 再 AcitvityThread 中的 main() 方法中主要作了兩件事,剛纔咱們分析了第一件事,也就是 Looper.prepareMainLooper();
接下來咱們講他作的第二件事 :Looper.loop();
補腦一會兒,咱們都把前期工做給作好了,怎樣才能讓讓 Handler 從 MessageQueue 中獲取到 Message 進行處理呢 ?沒錯就是經過 Looper.loop(); 讓整個消息隊列動起來。來咱們分析一下 loop() 方法中的源碼:
/**
* 調用此函數用於啓動消息隊列循環起來,做用至關於一個團隊的leader下達的命令,
* 只有leader說今天加班,你今天就必須加班😂
* Run the message queue in this thread. Be sure to call
*/
public static void loop() {
//先進行判斷當前線程是否有綁定looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//獲取這個looper的消息隊列
final MessageQueue queue = me.mQueue;
//確保這個線程的標識是本地進程的標識,
//並跟蹤標識符其實是什麼。
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//循環經過消息隊列來獲取消息
for (; ; ) {
Message msg = queue.next(); // might block
if (msg == null) {
//沒有消息退出消息隊列。
return;
}
// 這必須在本地變量中,以防UI事件設置日誌記錄器
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也就是hanlder.看回代碼hanlder.enqueueMessage()
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//最後回收這個message
msg.recycleUnchecked();
}
}
複製代碼
這一段代碼比較長,咱們逐個註釋,發現它是先判斷當前的線程是否存在looper,若是存在獲取保存在Looper的消息隊列messagequeue,而後無限循環這個消息隊列來獲取message,下面這句代碼是比較重要的:
//關鍵點,這裏的msg.target也就是hanlder.看回代碼hanlder.enqueueMessage()
msg.target.dispatchMessage(msg);
複製代碼
分析:
msg.target其實就是咱們的handler,不管是handler經過post或者sendEmptyMessage,最終都會調用到調到這個
enqueueMessage(),在這裏會將handler賦值到msg.target中。
複製代碼
好,那咱們再繼續分析這個 enqueueMessage() 函數
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//在message中放一個標記
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//在這裏把消息放到隊列裏面去
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
既然 Looper 中的 loop() 調用了 msg.target.dispatchMessage,咱們就看看 Handler的dispatchMessage 是如何進行處理這個msg的。
注意: dispatchMessage() 分發函數是 Handler 做爲處理者的身份進行的,天然這個函數也就在 Handler 類中了。
來咱們上源碼繼續逐個分析:
public void dispatchMessage(Message msg) {
//這裏先判斷callback是否爲空
// callback就是咱們使用handler.post(Runnable r)的入參runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//若是hanlder的入參callback不爲空,優先處理
if (mCallback != null) {
//若是回調返回true.則攔截了handler.handleMessage的方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//這就是爲何咱們使用hanlder的時候,須要重寫handleMessage的方法
handleMessage(msg);
}
}
複製代碼
分析:
在dispatchMessage函數中,意思就是分發這個消息,在代碼中先判斷msg.callback是否爲空,msg.callback是什麼?其實
就是handler.post中的runnable對象,通俗的來講就是handler若是有post操做的,就處理post的操做。
複製代碼
咱們在看看 handlerCallback這個函數。
private static void handleCallback(Message message) {
message.callback.run();
}
複製代碼
很簡單,就一行代碼,咱們看到了熟悉的run方法,這個不就是咱們使用post的時候傳進去的Runnbale對象的run方法嗎? 來,咱們模擬一段代碼:
/**
* 模擬開始
*/
private void doSometh() {
//開啓個線程,處理複雜的業務業務
new Thread(new Runnable() {
@Override
public void run() {
//模擬很複雜的業務,須要1000ms進行操做的業務
......
handler.post(new Runnable() {
@Override
public void run() {
//在這裏能夠更新ui
mTv.setText("在這個點我更新了:" + System.currentTimeMillis());
}
});
}
}).start();
}
複製代碼
寫到這裏或許大佬們會有個疑問,Handler 的 post 方法建立的線程和UI線程有什麼關係?來咱們仍是繼續分析源碼解釋:
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中,獲得了一個 Message 對象,而後將咱們建立的Runable對象做爲 callback 屬性,賦值給了當前 message。咱們繼續回到 handler.dispatchMessage(Message) 中,若是不是經過 post 那麼 callback 就爲空,咱們看到了一個 mCallback變量,咱們看看這個Callback的定義:
public interface Callback {
public boolean handleMessage(Message msg);
}
public Handler(Callback callback) {
this(callback, false);
}
複製代碼
咱們能夠經過實現這個接口,並做爲一個參數傳進去 Handler 來達處處理這個消息的效果。 咱們再回到以前的 dispatchMessage() 函數中理解一下剛纔的思路:
public void dispatchMessage(Message msg) {
//這裏先判斷callback是否爲空
// callback就是咱們使用handler.post(Runnable r)的入參runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//若是hanlder的入參callback不爲空,優先處理
if (mCallback != null) {
//若是回調返回true.則攔截了handler.handleMessage的方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//這就是爲何咱們使用hanlder的時候,須要重寫handleMessage的方法
handleMessage(msg);
}
}
複製代碼
分析:最後一行代碼中,咱們看到了熟悉的handleMessage,這不就是咱們常常 handler.handlerMessage 的方法嗎?
但注意以前咱們所看到的,若是咱們mCallback.handlerMessage(msg)返回爲true的話,這樣就不交給handler.handleMessage處理了。這裏咱們再 看看 handleMessage() 函數 :
public void handleMessage(Message msg) {
}
複製代碼
在這裏咱們發現了這個方法是空的,爲什們呢 ?這裏補充一句。由於消息的最終回調是由咱們控制的,咱們在建立handler的時候都是複寫handleMessage方法,而後根據msg.what進行消息處理。產生一個Message對象,能夠new ,也可使用Message.obtain()方法;二者均可以,可是更建議使用obtain方法,由於Message內部維護了一個Message池用於Message的複用,避免使用new 從新分配內存。
咱們繼續看回來咱們的 Looper.loop() :
public static void loop() {
.....
.....
//循環經過消息隊列來獲取消息
for (; ; ) {
......
//最後回收這個message
msg.recycleUnchecked();
}
}
複製代碼
在無限循環每一個消息的時候,除了調用 handler.dispatchMessage,最後還會調用 msg.recycleUnchecked() 進行回收這個消息。
1.爲何在主線程中建立Handler不須要咱們調用 Looper.prepare().由於在程序的入口中系統會調用Looper.prepareMain
Looper()來建立,而且讓其主線程的Looper啓動起來。若是咱們在子線程建立 handler,須要手動建立 looper 而且啓動。
2.每個線程只能存在一個 Looper, Looper有一個全局變量 sThreadLocal 用來保存每個線程的looper,經過 get、set 進行
存取 looper。
3.Handler 能夠經過經過 post 或者sendMessage進行發送消息,由於其最終會調用 sendMessageDelayed,咱們能夠經過
runnable 方式或者重寫handleMessage進行消息的處理,固然若是經過 handler.sendMessage(msg) 的方式的話,咱們
能夠實現Callback接口達到消息的處理。
4.爲何不能在子線程更新UI?其實更準確的來講應該是UI只能在建立UI的線程中進行更新,也就是主線程,若是子線程建立UI,其能夠在子線程進行更新。
複製代碼
轉自 :www.cnblogs.com/devinzhang/…
方法一:Thread
new Thread( new Runnable() {
public void run() {
myView.invalidate();
}
}).start();
複製代碼
分析: 能夠實現功能,刷新UI界面。可是這樣是不合理的,由於它違背了單線程模型:Android UI操做並非線程安全的,而且這些操做必須在UI線程中執行。
方法二 :Thread + Handler
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
myView.invalidate();
break;
}
super.handleMessage(msg);
}
};
複製代碼
class MyThread implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
Message message = new Message();
message.what = 0;
myHandler.sendMessage(message);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
複製代碼
Handler來根據接收的消息,處理UI更新。Thread線程發出Handler消息,通知更新UI。
方法三:TimerTask + Handler 實現 Timer 功能
public class TestTimer extends Activity {
Timer timer = new Timer();
Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
setTitle("狗哥最帥");
break;
}
super.handleMessage(msg);
}
};
TimerTask task = new TimerTask(){
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
timer.schedule(task, 10000);
}
}
複製代碼
方法四:Runnable + Handler.postDelayed(runnable,time)
private Handler handler = new Handler();
private Runnable myRunnable= new Runnable() {
public void run() {
if (run) {
handler.postDelayed(this, 1000);
count++;
}
tvCounter.setText("Count: " + count);
}
};
複製代碼
這裏面的內容有我去年初學的時候在 CDSD上寫的一些,在這裏我不得不吐槽一下,因爲CSDN社區真的是各類廣告太多了,看個文章還要付錢,受不了,因此轉戰掘金社區,個人csdn原文地址:blog.csdn.net/MrZhao_Perf… ,這篇也算是一個總結與補充吧。 OK了,Handler 機制原理經過源碼的方式給你們說了個大概,若是你們有什麼疑問或者能夠留言給我。