Android Handler機制的簡單理解和使用
- Handler機制
- 1、Handler使用的引出
- 2、背景和定義
- 3、作用和意義
- 4、主要引數
- 5、作業原理及流程
- 5.1、對應關系
- 6、深入分析 Handler機制原始碼
- 6.1、Handler機制的核心類
- 6.2、核心方法
- 6.3、方式1:使用 Handler.sendMessage()
- 6.3.1、 創建Handler類物件
- 6.3.1.1、隱式操作1:創建回圈器物件& 訊息佇列物件
- 6.3.1.2、隱式操作2:訊息回圈
- 6.3.2、創建訊息物件
- 6.3.3、在作業執行緒中 發送訊息到訊息佇列中
- 6.3.4、總結
- 6.4、方式2:使用Handler.post()
- 6.4.1、在主執行緒中創建Handler實體
- 6.4.2、創建訊息物件
- 6.4.3、在作業執行緒中 發送訊息到訊息佇列中
- 6.4.4、總結
- 6.4.5、Handler.post和Handler.sendMessage的區別
- 6.5、MessageQueue分析
- 6.5.1、訊息物件Message原始碼分析
- 6.5.2、enqueueMessage是如何處理Message
- 6.6、Looper原始碼分析
- 6.6.1、Looper#建構式原理
- 6.6.2、Looper.myLooper()原理
- 6.6.3、Looper.prepare()原理
- 6.6.4、Looper.loop()原理及總結
- 6.7、Handler機制的原始碼總結
- 7、具體使用案例
- 7.1、使用 Handler.sendMessage()
- 7.1.1、方式1:新建Handler子類(內部類)
- 7.1.2、方式2:匿名內部類
- 7.2、使用 Handler.post()
- 參考
Handler機制
1、Handler使用的引出
有這樣一個需求,當打開Activity界面時,開始倒計時,倒計時結束后跳轉新的界面(思維活躍的朋友可能立馬想到如果打開后自動倒計時,就類似于各個APP的歡迎閃屏頁面),如下圖:

可能覺得直接開啟一個包含倒序回圈的子執行緒就ok了,具體實作如下:
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.mly.panhouye.handlerdemo.Main2Activity">
<TextView
android:gravity="center"
android:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="NO DATA"
android:id="@+id/tv"/>
</LinearLayout>
MainActivity.java
public class Main2Activity extends AppCompatActivity {
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
for (int i=5;i>0;i--){
tv.setText(String.valueOf(i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//計時結束后跳轉到其他界面
startActivity(new Intent(MainActivity.this,MainActivity1.class));
//添加finish方法在任務堆疊中銷毀倒計時界面,使新開界面在回退時直接退出而不是再次回傳該界面
finish();
}
}).start();
}
但當點進入界面時,會發現程式奔潰了,logcat中錯誤日志如下(只有UI執行緒可以更改UI界面):

由此我們發現在安卓開發中,例如上面的示例,我們常常通過一個執行緒來完成某些操作,然后同步顯示對應的視圖控制元件UI上,通過上面的例子我們也知道了安卓中無法直接通過子執行緒來進行UI更新操作,對于這種情況,Android提供了一套異步訊息處理機制Handler,
2、背景和定義

Handler一套 Android 訊息傳遞機制,主要是子執行緒UI更細訊息傳遞給主執行緒,從而主執行緒更新UI,
- Android 主執行緒的UI,只能主執行緒更新, 如果多個執行緒都能更新,勢必要「
加鎖」,還不如采用「單執行緒訊息佇列機制」 - 主執行緒內部維護一個回圈,沒有訊息時候,這個回圈是阻塞的,新來訊息(或者阻塞timeout)時會喚醒,接著處理新到來訊息,
3、作用和意義
- 在多執行緒的應用場景中,將作業執行緒中需更新UI的操作資訊 傳遞到 UI主執行緒,從而實作 作業執行緒對UI的更新處理,最終實作異步訊息的處理
- 多個執行緒并發更新UI的同時 保證執行緒安全

4、主要引數

使用Handler方式進行異步訊息處理主要由Message,Handler,MessageQueue,Looper四部分組成:
(1)Message,執行緒之間傳遞的訊息,用于不同執行緒之間的資料互動,Message中的what欄位用來標記區分多個訊息,arg1、arg2 欄位用來傳遞int型別的資料,obj可以傳遞任意型別的欄位,
(2)Handler,用于發送和處理訊息,其中的sendMessage()用來發送訊息,handleMessage()用于訊息處理,進行相應的UI操作,
(3)MessageQueue,訊息佇列(先進先出),用于存放Handler發送的訊息,一個執行緒只有一個訊息佇列,
(4)Looper,可以理解為訊息佇列的管理者,當發現MessageQueue中存在訊息,Looper就會將訊息傳遞到handleMessage()方法中,同樣,一個執行緒只有一個Looper,

5、作業原理及流程
Handler機制的作業流程主要包括4個步驟:
- 1、異步通信準備
- 2、訊息發送
- 3、訊息回圈
- 4、訊息處理

作業流程圖:

5.1、對應關系

執行緒(Thread)、回圈器(Looper)、處理者(Handler)之間的對應關系如下:
1個執行緒(Thread)只能系結1個回圈器(Looper),但可以有多個處理者(Handler)1個回圈器(Looper)可系結多個處理者(Handler)1個處理者(Handler)只能系結1個回圈器(Looper)

6、深入分析 Handler機制原始碼

6.1、Handler機制的核心類
Handler機制 中有3個重要的類:
- 處理器 類(Handler)
- 訊息佇列 類(MessageQueue)
- 回圈器 類(Looper)

6.2、核心方法

下面的原始碼分析將根據 Handler的使用步驟進行
- Handler使用方式因發送訊息到訊息佇列的方式不同而不同,共分為2種:使用
Handler.sendMessage()、使用Handler.post()
6.3、方式1:使用 Handler.sendMessage()
/**
* 此處以 匿名內部類 的使用方式為例
*/
// 步驟1:在主執行緒中 通過匿名內部類 創建Handler類物件
private Handler mhandler = new Handler(){
// 通過復寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需執行的UI操作
}
};
// 步驟2:創建訊息物件
Message msg = Message.obtain(); // 實體化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
// 步驟3:在作業執行緒中 通過Handler發送訊息到訊息佇列中
// 多執行緒可采用AsyncTask、繼承Thread類、實作Runnable
mHandler.sendMessage(msg);
// 步驟4:開啟作業執行緒(同時啟動了Handler)
// 多執行緒可采用AsyncTask、繼承Thread類、實作Runnable
6.3.1、 創建Handler類物件
步驟1:在主執行緒中 通過匿名內部類 創建Handler類物件
/**
* 具體使用
*/
private Handler mhandler = new Handler(){
// 通過復寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需執行的UI操作
}
};
/**
* 原始碼分析:Handler的構造方法
* 作用:初始化Handler物件 & 系結執行緒
* 注:
* a. Handler需系結 執行緒才能使用;系結后,Handler的訊息處理會在系結的執行緒中執行
* b. 系結方式 = 先指定Looper物件,從而系結了 Looper物件所系結的執行緒(因為Looper物件本已系結了對應執行緒)
* c. 即:指定了Handler物件的 Looper物件 = 系結到了Looper物件所在的執行緒
*/
public Handler() {
this(null, false);
// ->>分析1
}
/**
* 分析1:this(null, false) = Handler(null,false)
*/
public Handler(Callback callback, boolean async) {
...// 僅貼出關鍵代碼
// 1. 指定Looper物件
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// Looper.myLooper()作用:獲取當前執行緒的Looper物件;若執行緒無Looper物件則拋出例外
// 即 :若執行緒中無創建Looper物件,則也無法創建Handler物件
// 故 若需在子執行緒中創建Handler物件,則需先創建Looper物件
// 注:可通過Loop.getMainLooper()可以獲得當前行程的主執行緒的Looper物件
// 2. 系結訊息佇列物件(MessageQueue)
mQueue = mLooper.mQueue;
// 獲取該Looper物件中保存的訊息佇列物件(MessageQueue)
// 至此,保證了handler物件 關聯上 Looper物件中MessageQueue
}
從上面可看出:
- 當創建
Handler物件時,則通過構造方法自動關聯當前執行緒的Looper物件& 對應的訊息佇列物件(MessageQueue),從而自動系結了 實作創建Handler物件操作的執行緒
那么,當前執行緒的Looper物件 & 對應的訊息佇列物件(MessageQueue) 是什么時候創建的呢?
- 在上述使用步驟中,并無 創建Looper物件 & 對應的訊息佇列物件(MessageQueue)這1步
,
6.3.1.1、隱式操作1:創建回圈器物件& 訊息佇列物件
創建Looper物件主要通過方法:Looper.prepareMainLooper()、Looper.prepare();創建訊息佇列物件(MessageQueue)方法:創建Looper物件時則會自動創建,即:創建回圈器物件(Looper)的同時,會自動創建訊息佇列物件(MessageQueue),

/**
* 原始碼分析1:Looper.prepare()
* 作用:為當前執行緒(子執行緒) 創建1個回圈器物件(Looper),同時也生成了1個訊息佇列物件(MessageQueue)
* 注:需在子執行緒中手動呼叫該方法
*/
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 1. 判斷sThreadLocal是否為null,否則拋出例外
//即 Looper.prepare()方法不能被呼叫兩次 = 1個執行緒中只能對應1個Looper實體
// 注:sThreadLocal = 1個ThreadLocal物件,用于存盤執行緒的變數
sThreadLocal.set(new Looper(true));
// 2. 若為初次Looper.prepare(),則創建Looper物件 & 存放在ThreadLocal變數中
// 注:Looper物件是存放在Thread執行緒里的
// 原始碼分析Looper的構造方法->>分析a
}
/**
* 分析a:Looper的構造方法
**/
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
// 1. 創建1個訊息佇列物件(MessageQueue)
// 即 當創建1個Looper實體時,會自動創建一個與之配對的訊息佇列物件(MessageQueue)
mRun = true;
mThread = Thread.currentThread();
}
/**
* 原始碼分析2:Looper.prepareMainLooper()
* 作用:為 主執行緒(UI執行緒) 創建1個回圈器物件(Looper),同時也生成了1個訊息佇列物件(MessageQueue)
* 注:該方法在主執行緒(UI執行緒)創建時自動呼叫,即 主執行緒的Looper物件自動生成,不需手動生成
*/
// 在Android應用行程啟動時,會默認創建1個主執行緒(ActivityThread,也叫UI執行緒)
// 創建時,會自動呼叫ActivityThread的1個靜態的main()方法 = 應用程式的入口
// main()內則會呼叫Looper.prepareMainLooper()為主執行緒生成1個Looper物件
/**
* 原始碼分析:main()
**/
public static void main(String[] args) {
... // 僅貼出關鍵代碼
Looper.prepareMainLooper();
// 1. 為主執行緒創建1個Looper物件,同時生成1個訊息佇列物件(MessageQueue)
// 方法邏輯類似Looper.prepare()
// 注:prepare():為子執行緒中創建1個Looper物件
ActivityThread thread = new ActivityThread();
// 2. 創建主執行緒
Looper.loop();
// 3. 自動開啟 訊息回圈 ->>下面將詳細分析
}
總結:
1、創建主執行緒時,會自動呼叫ActivityThread的1個靜態的main();而main()內則會呼叫Looper.prepareMainLooper()為主執行緒生成1個Looper物件,同時也會生成其對應的MessageQueue物件
- 即 主執行緒的Looper物件自動生成,不需手動生成;而子執行緒的Looper物件則需手動通過Looper.prepare()創建
- 在子執行緒若不手動創建Looper物件 則無法生成Handler物件
2、根據Handler的作用(在主執行緒更新UI),故Handler實體的創建場景 主要在主執行緒
3、生成Looper & MessageQueue物件后,則會自動進入訊息回圈:Looper.loop(),即又是另外一個隱式操作,
6.3.1.2、隱式操作2:訊息回圈
此處主要分析的是Looper類中的loop()方法
/**
* 原始碼分析: Looper.loop()
* 作用:訊息回圈,即從訊息佇列中獲取訊息、分發訊息到Handler
* 特別注意:
* a. 主執行緒的訊息回圈不允許退出,即無限回圈
* b. 子執行緒的訊息回圈允許退出:呼叫訊息佇列MessageQueue的quit()
*/
public static void loop() {
...// 僅貼出關鍵代碼
// 1. 獲取當前Looper的訊息佇列
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// myLooper()作用:回傳sThreadLocal存盤的Looper實體;若me為null 則拋出例外
// 即loop()執行前必須執行prepare(),從而創建1個Looper實體
final MessageQueue queue = me.mQueue;
// 獲取Looper實體中的訊息佇列物件(MessageQueue)
// 2. 訊息回圈(通過for回圈)
for (;;) {
// 2.1 從訊息佇列中取出訊息
Message msg = queue.next();
if (msg == null) {
return;
}
// next():取出訊息佇列里的訊息
// 若取出的訊息為空,則執行緒阻塞
// ->> 分析1
// 2.2 派發訊息到對應的Handler
msg.target.dispatchMessage(msg);
// 把訊息Message派發給訊息物件msg的target屬性
// target屬性實際是1個handler物件
// ->>分析2
// 3. 釋放訊息占據的資源
msg.recycle();
}
}
/**
* 分析1:queue.next()
* 定義:屬于訊息佇列類(MessageQueue)中的方法
* 作用:出隊訊息,即從 訊息佇列中 移出該訊息
*/
Message next() {
...// 僅貼出關鍵代碼
// 該引數用于確定訊息佇列中是否還有訊息
// 從而決定訊息佇列應處于出隊訊息狀態 or 等待狀態
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// nativePollOnce方法在native層,若是nextPollTimeoutMillis為-1,此時訊息佇列處于等待狀態
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 出隊訊息,即 從訊息佇列中取出訊息:按創建Message物件的時間順序
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取出了訊息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 若 訊息佇列中已無訊息,則將nextPollTimeoutMillis引數設為-1
// 下次回圈時,訊息佇列則處于等待狀態
nextPollTimeoutMillis = -1;
}
......
}
.....
}
}// 回到分析原處
/**
* 分析2:dispatchMessage(msg)
* 定義:屬于處理者類(Handler)中的方法
* 作用:派發訊息到對應的Handler實體 & 根據傳入的msg作出對應的操作
*/
public void dispatchMessage(Message msg) {
// 1. 若msg.callback屬性不為空,則代表使用了post(Runnable r)發送訊息
// 則執行handleCallback(msg),即回呼Runnable物件里復寫的run()
// 上述結論會在講解使用“post(Runnable r)”方式時講解
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 2. 若msg.callback屬性為空,則代表使用了sendMessage(Message msg)發送訊息(即此處需討論的)
// 則執行handleMessage(msg),即回呼復寫的handleMessage(msg) ->> 分析3
handleMessage(msg);
}
}
/**
* 分析3:handleMessage(msg)
* 注:該方法 = 空方法,在創建Handler實體時復寫 = 自定義訊息處理方式
**/
public void handleMessage(Message msg) {
... // 創建Handler實體時復寫
}
總結:
- 訊息回圈的操作 =
訊息出隊 + 分發給對應的Handler實體 - 分發給對應的Handler的程序:根據出隊訊息的歸屬者通過
dispatchMessage(msg)進行分發,最侄訓呼復寫的handleMessage(Message msg),從而實作 訊息處理 的操作 - 特別注意:在進行訊息分發時(
dispatchMessage(msg)),會進行1次發送方式的判斷:- 1、若
msg.callback屬性不為空,則代表使用了post(Runnable r)發送訊息,則直接回呼Runnable物件里復寫的run() - 2、若
msg.callback屬性為空,則代表使用了sendMessage(Message msg)發送訊息,則回呼復寫的handleMessage(msg)
- 1、若
圖表總結如下:

6.3.2、創建訊息物件
/**
* 具體使用
*/
Message msg = Message.obtain(); // 實體化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
/**
* 原始碼分析:Message.obtain()
* 作用:創建訊息物件
* 注:創建Message物件可用關鍵字new 或 Message.obtain()
*/
public static Message obtain() {
// Message內部維護了1個Message池,用于Message訊息物件的復用
// 使用obtain()則是直接從池內獲取
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
// 建議:使用obtain()”創建“訊息物件,避免每次都使用new重新分配記憶體
}
// 若池內無訊息物件可復用,則還是用關鍵字new創建
return new Message();
}
6.3.3、在作業執行緒中 發送訊息到訊息佇列中
多執行緒的實作方式:AsyncTask、繼承Thread類、實作Runnable
/**
* 具體使用
*/
mHandler.sendMessage(msg);
/**
* 原始碼分析:mHandler.sendMessage(msg)
* 定義:屬于處理器類(Handler)的方法
* 作用:將訊息 發送 到訊息佇列中(Message ->> MessageQueue)
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
// ->>分析1
}
/**
* 分析1:sendMessageDelayed(msg, 0)
**/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// ->> 分析2
}
/**
* 分析2:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
**/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1. 獲取對應的訊息佇列物件(MessageQueue)
MessageQueue queue = mQueue;
// 2. 呼叫了enqueueMessage方法 ->>分析3
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
* 分析3:enqueueMessage(queue, msg, uptimeMillis)
**/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1. 將msg.target賦值為this
// 即 :把 當前的Handler實體物件作為msg的target屬性
msg.target = this;
// 請回憶起上面說的Looper的loop()中訊息回圈時,會從訊息佇列中取出每個訊息msg,然后執行msg.target.dispatchMessage(msg)去處理訊息
// 實際上則是將該訊息派發給對應的Handler實體
// 2. 呼叫訊息佇列的enqueueMessage()
// 即:Handler發送的訊息,最終是保存到訊息佇列->>分析4
return queue.enqueueMessage(msg, uptimeMillis);
}
/**
* 分析4:queue.enqueueMessage(msg, uptimeMillis)
* 定義:屬于訊息佇列類(MessageQueue)的方法
* 作用:入隊,即 將訊息 根據時間 放入到訊息佇列中(Message ->> MessageQueue)
* 采用單鏈表實作:提高插入訊息、洗掉訊息的效率
*/
boolean enqueueMessage(Message msg, long when) {
...// 僅貼出關鍵代碼
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 判斷訊息佇列里有無訊息
// a. 若無,則將當前插入的訊息 作為隊頭 & 若此時訊息佇列處于等待狀態,則喚醒
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// b. 判斷訊息佇列里有訊息,則根據 訊息(Message)創建的時間 插入到佇列中
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
// 之后,隨著Looper物件的無限訊息回圈
// 不斷從訊息佇列中取出Handler發送的訊息 & 分發到對應Handler
// 最侄訓呼Handler.handleMessage()處理訊息
總結
Handler發送訊息的本質 = 為該訊息定義target屬性(即本身實體物件) & 將訊息入隊到系結執行緒的訊息佇列中,具體如下:

6.3.4、總結


6.4、方式2:使用Handler.post()
// 步驟1:在主執行緒中創建Handler實體
private Handler mhandler = new mHandler();
// 步驟2:在作業執行緒中 發送訊息到訊息佇列中 & 指定操作UI內容
// 需傳入1個Runnable物件
mHandler.post(new Runnable() {
@Override
public void run() {
... // 需執行的UI操作
}
});
// 步驟3:開啟作業執行緒(同時啟動了Handler)
// 多執行緒可采用AsyncTask、繼承Thread類、實作Runnable
實際上,該方式與方式1中的Handler.sendMessage()作業原理相同、原始碼分析類似,下面將主要講解不同之處
6.4.1、在主執行緒中創建Handler實體
/**
* 具體使用
*/
private Handler mhandler = new Handler();
// 與方式1的使用不同:此處無復寫Handler.handleMessage()
/**
* 原始碼分析:Handler的構造方法
* 作用:
* a. 在此之前,主執行緒創建時隱式創建Looper物件、MessageQueue物件
* b. 初始化Handler物件、系結執行緒 & 進入訊息回圈
* 此處的原始碼分析類似方式1,此處不作過多描述
*/
6.4.2、創建訊息物件
訊息物件的創建 = 內部 根據Runnable物件而封裝
參考下一節(6.4.3)
6.4.3、在作業執行緒中 發送訊息到訊息佇列中
/**
* 具體使用
* 需傳入1個Runnable物件、復寫run()從而指定UI操作
*/
mHandler.post(new Runnable() {
@Override
public void run() {
... // 需執行的UI操作
}
});
/**
* 原始碼分析:Handler.post(Runnable r)
* 定義:屬于處理者類(Handler)中的方法
* 作用:定義UI操作、將Runnable物件封裝成訊息物件 & 發送 到訊息佇列中(Message ->> MessageQueue)
* 注:
* a. 相比sendMessage(),post()最大的不同在于,更新的UI操作可直接在重寫的run()中定義
* b. 實際上,Runnable并無創建新執行緒,而是發送 訊息 到訊息佇列中
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
// getPostMessage(r) 的原始碼分析->>分析1
// sendMessageDelayed()的原始碼分析 ->>分析2
}
/**
* 分析1:getPostMessage(r)
* 作用:將傳入的Runable物件封裝成1個訊息物件
**/
private static Message getPostMessage(Runnable r) {
// 1. 創建1個訊息物件(Message)
Message m = Message.obtain();
// 注:創建Message物件可用關鍵字new 或 Message.obtain()
// 建議:使用Message.obtain()創建,
// 原因:因為Message內部維護了1個Message池,用于Message的復用,使用obtain()直接從池內獲取,從而避免使用new重新分配記憶體
// 2. 將 Runable物件 賦值給訊息物件(message)的callback屬性
m.callback = r;
// 3. 回傳該訊息物件
return m;
} // 回到呼叫原處
/**
* 分析2:sendMessageDelayed(msg, 0)
* 作用:實際上,從此處開始,則類似方式1 = 將訊息入隊到訊息佇列,
* 即 最終是呼叫MessageQueue.enqueueMessage()
**/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// 請看分析3
}
/**
* 分析3:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
**/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1. 獲取對應的訊息佇列物件(MessageQueue)
MessageQueue queue = mQueue;
// 2. 呼叫了enqueueMessage方法 ->>分析3
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
* 分析4:enqueueMessage(queue, msg, uptimeMillis)
**/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1. 將msg.target賦值為this
// 即 :把 當前的Handler實體物件作為msg的target屬性
msg.target = this;
// 請回憶起上面說的Looper的loop()中訊息回圈時,會從訊息佇列中取出每個訊息msg,然后執行msg.target.dispatchMessage(msg)去處理訊息
// 實際上則是將該訊息派發給對應的Handler實體
// 2. 呼叫訊息佇列的enqueueMessage()
// 即:Handler發送的訊息,最終是保存到訊息佇列
return queue.enqueueMessage(msg, uptimeMillis);
}
// 注:實際上從分析2開始,原始碼 與 sendMessage(Message msg)發送方式相同
從上面的分析可看出:
- 訊息物件的創建 = 內部 根據
Runnable物件而封裝; - 發送到訊息佇列的邏輯 = 方式1中
sendMessage(Message msg),
下面,我們重新回到步驟1前的隱式操作2:訊息回圈,即Looper類中的loop()方法
/**
* 原始碼分析: Looper.loop()
* 作用:訊息回圈,即從訊息佇列中獲取訊息、分發訊息到Handler
* 特別注意:
* a. 主執行緒的訊息回圈不允許退出,即無限回圈
* b. 子執行緒的訊息回圈允許退出:呼叫訊息佇列MessageQueue的quit()
*/
public static void loop() {
...// 僅貼出關鍵代碼
// 1. 獲取當前Looper的訊息佇列
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// myLooper()作用:回傳sThreadLocal存盤的Looper實體;若me為null 則拋出例外
// 即loop()執行前必須執行prepare(),從而創建1個Looper實體
final MessageQueue queue = me.mQueue;
// 獲取Looper實體中的訊息佇列物件(MessageQueue)
// 2. 訊息回圈(通過for回圈)
for (;;) {
// 2.1 從訊息佇列中取出訊息
Message msg = queue.next();
if (msg == null) {
return;
}
// next():取出訊息佇列里的訊息
// 若取出的訊息為空,則執行緒阻塞
// 2.2 派發訊息到對應的Handler
msg.target.dispatchMessage(msg);
// 把訊息Message派發給訊息物件msg的target屬性
// target屬性實際是1個handler物件
// ->>分析1
// 3. 釋放訊息占據的資源
msg.recycle();
}
}
/**
* 分析1:dispatchMessage(msg)
* 定義:屬于處理者類(Handler)中的方法
* 作用:派發訊息到對應的Handler實體 & 根據傳入的msg作出對應的操作
*/
public void dispatchMessage(Message msg) {
// 1. 若msg.callback屬性不為空,則代表使用了post(Runnable r)發送訊息(即此處需討論的)
// 則執行handleCallback(msg),即回呼Runnable物件里復寫的run()->> 分析2
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 2. 若msg.callback屬性為空,則代表使用了sendMessage(Message msg)發送訊息(即此處需討論的)
// 則執行handleMessage(msg),即回呼復寫的handleMessage(msg)
handleMessage(msg);
}
}
/**
* 分析2:handleCallback(msg)
**/
private static void handleCallback(Message message) {
message.callback.run();
// Message物件的callback屬性 = 傳入的Runnable物件
// 即回呼Runnable物件里復寫的run()
}
至此,你應該明白使用 Handler.post()的作業流程與Handler.sendMessage()類似,區別在于:
- 不需外部創建訊息物件,而是內部根據傳入的
Runnable物件 封裝訊息物件 - 回呼的訊息處理方法是:復寫
Runnable物件的run()
6.4.4、總結
關于使用 Handler.post()的原始碼決議完畢,總結如下:

6.4.5、Handler.post和Handler.sendMessage的區別

6.5、MessageQueue分析
首先來看看建構式
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
建構式之上定義了很多native方法
private native static long nativeInit();
private native static void nativeDestroy(long ptr);// 阻塞
private native static void nativeWake(long ptr); // 喚醒
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
native之上定義了幾類資料結構,Message、ArrayList、SparseArray、陣列
Message mMessages; // 頭結點
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
6.5.1、訊息物件Message原始碼分析
Message有如下公有屬性,供程式員呼叫:
public int what;//訊息標示,
public int arg1; //簡單int型別資料
public int arg2;//簡單int型別資料
public Object obj;//簡單Object型別資料
public Messenger replyTo;//跨行程信使
public int sendingUid = -1;//Messenger訊息標示
Message有如下私有屬性,用途如下:
/*package*/ static final int FLAG_IN_USE = 1 << 0;//正在使用中
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;//訊息同步標識
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;//
/*package*/ int flags;//訊息執行標識
/*package*/ long when;//執行時間
/*package*/ Bundle data;//裝載的資料
/*package*/ Handler target;//目標載體
/*package*/ Runnable callback;//任務執行緒
/*package*/ Message next;//訊息鏈表,下一個訊息
private static final Object sPoolSync = new Object();//鎖物件
private static Message sPool;//訊息池
private static int sPoolSize = 0;//訊息池大小
private static final int MAX_POOL_SIZE = 50;//訊息池最大訊息數常量
private static boolean gCheckRecycle = true;//回圈檢查
Message的原始碼,我們可以得出如下結論,Message是一種鏈表結構,每個Message持有以下資訊:
- 1、用于傳遞的資料,如what、arg1、arg2、obj
- 2、用于執行當前Message的Handler
- 3、用于執行當前Message的回呼介面CallBack、子執行緒Runnable
- 4、當前Message的屬性,如延時時間、執行標識、Bundle資料,下一個Message參考,這種結構構成了鏈表,
6.5.2、enqueueMessage是如何處理Message

- 圖中 1處會判斷如果 Message 中的 target 沒有被設定,則直接拋出例外;
- 圖中2和 3 處會按照
Message的時間when來有序得插入MessageQueue中,可以看出MessageQueue實際上是一個鏈表維護的有序佇列,只不過是按照 Message 的執行時間來排序,
看到這里,思路似乎終止了,我們跟隨Handler、MessQueue的腳步,只看到了Message被插入到MessageQueue的私有佇列中,那我們產生的Message什么時候會背消費呢?
接下來我們看看Looper吧!
6.6、Looper原始碼分析
在任何執行緒要開啟Loop,都要用Looper.prepare()+Looper.looper()的方式,
- 以APP主行程為例,APP行程啟動入口的
main方法,也是通過這種方式開啟loop的,與子執行緒細微不同的是,主執行緒開啟looper用的是prepareMainLooper,
6.6.1、Looper#建構式原理
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper建構式做了兩件事情,初始化訊息佇列MessageQueue物件,記錄當前執行緒資訊,
6.6.2、Looper.myLooper()原理
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到myLooper是從threadLocal中取出Looper物件,在Looper類中定義了如下變數sThreadLocal、mQueue、sMainLooper、mThread
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
6.6.3、Looper.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));
}
prepare就是 new 出一個 Looper,核心之處在于將 new 出的 Looper 設定到了執行緒本地變數 sThreadLocal 中,也就是說創建的 Looper 與當前執行緒發生了系結,
Looper#prepareMainLooper原理
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
prepareMainLooper只有在APP行程啟動的時候有用,并不推薦開發者呼叫這個函式,
6.6.4、Looper.loop()原理及總結

- 1、 取出
Looper物件 - 2 、校驗當前執行緒是否持有
Looper,是否啟動而來Looper.prepare - 3、從
Looper中取出對應的MessageQueue,主執行緒Looper就取出主執行緒的MessageQueue,子執行緒就取出子執行緒MessageQueue - 4 、從
MessageQueue中取出Message - 5、
Message.target屬性,即handler,呼叫Message系結好的handler.dispatchMessage,處理訊息,
也就是說,Message最終交由與Message系結的Handler處理,Looper只是負責無限回圈+從MessageQueue中讀取,
6.7、Handler機制的原始碼總結



7、具體使用案例
1個簡單 “更新UI操作” 的案例,主布局檔案相同 = 1個用于展示的TextView,具體如下:
布局代碼:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context="com.example.carson_ho.handler_learning.MainActivity">
<TextView
android:id="@+id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</RelativeLayout>
7.1、使用 Handler.sendMessage()
7.1.1、方式1:新建Handler子類(內部類)
public class MainActivity extends AppCompatActivity {
public TextView mTextView;
public Handler mHandler;
// 步驟1:(自定義)新創建Handler子類(繼承Handler類) & 復寫handleMessage()方法
class Mhandler extends Handler {
// 通過復寫handlerMessage() 從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
// 根據不同執行緒發送過來的訊息,執行不同的UI操作
// 根據 Message物件的what屬性 標識不同的訊息
switch (msg.what) {
case 1:
mTextView.setText("執行了執行緒1的UI操作");
break;
case 2:
mTextView.setText("執行了執行緒2的UI操作");
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.show);
// 步驟2:在主執行緒中創建Handler實體
mHandler = new Mhandler();
// 采用繼承Thread類實作多執行緒演示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 步驟3:創建所需的訊息物件
Message msg = Message.obtain();
msg.what = 1; // 訊息標識
msg.obj = "A"; // 訊息記憶體存放
// 步驟4:在作業執行緒中 通過Handler發送訊息到訊息佇列中
mHandler.sendMessage(msg);
}
}.start();
// 步驟5:開啟作業執行緒(同時啟動了Handler)
// 此處用2個作業執行緒展示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通過sendMessage()發送
// a. 定義要發送的訊息
Message msg = Message.obtain();
msg.what = 2; //訊息的標識
msg.obj = "B"; // 訊息的存放
// b. 通過Handler發送訊息到其系結的訊息佇列
mHandler.sendMessage(msg);
}
}.start();
}
}
實驗結果:

7.1.2、方式2:匿名內部類
public class MainActivity extends AppCompatActivity {
public TextView mTextView;
public Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.show);
// 步驟1:在主執行緒中 通過匿名內部類 創建Handler類物件
mHandler = new Handler(){
// 通過復寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
// 根據不同執行緒發送過來的訊息,執行不同的UI操作
switch (msg.what) {
case 1:
mTextView.setText("執行了執行緒1的UI操作");
break;
case 2:
mTextView.setText("執行了執行緒2的UI操作");
break;
}
}
};
// 采用繼承Thread類實作多執行緒演示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 步驟3:創建所需的訊息物件
Message msg = Message.obtain();
msg.what = 1; // 訊息標識
msg.obj = "A"; // 訊息記憶體存放
// 步驟4:在作業執行緒中 通過Handler發送訊息到訊息佇列中
mHandler.sendMessage(msg);
}
}.start();
// 步驟5:開啟作業執行緒(同時啟動了Handler)
// 此處用2個作業執行緒展示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通過sendMessage()發送
// a. 定義要發送的訊息
Message msg = Message.obtain();
msg.what = 2; //訊息的標識
msg.obj = "B"; // 訊息的存放
// b. 通過Handler發送訊息到其系結的訊息佇列
mHandler.sendMessage(msg);
}
}.start();
}
}
實驗結果

7.2、使用 Handler.post()
public class MainActivity extends AppCompatActivity {
public TextView mTextView;
public Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.show);
// 步驟1:在主執行緒中創建Handler實體
mHandler = new Handler();
// 步驟2:在作業執行緒中 發送訊息到訊息佇列中 & 指定操作UI內容
new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通過psot()發送,需傳入1個Runnable物件
mHandler.post(new Runnable() {
@Override
public void run() {
// 指定操作UI內容
mTextView.setText("執行了執行緒1的UI操作");
}
});
}
}.start();
// 步驟3:開啟作業執行緒(同時啟動了Handler)
// 此處用2個作業執行緒展示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText("執行了執行緒2的UI操作");
}
});
}
}.start();
}
}
實驗結果:

參考
1、Android異步通信:這是一份 全面、詳細的Handler機制學習攻略
2、Handler介紹
3、Android百問百答-《那些年被問過的Handler原理》
4、ANDROID中HANDLER使用淺析
5、都 2021 年了,還有人在研究 Handler?
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/386643.html
標籤:其他
上一篇:安卓面試題整理1-6
