Handler 消息傳遞機制
在 Android 系統中,App 的邏輯代碼默認都是跑在主線程(即UI線程)當中的,而且所有的 UI 刷新以及輸入處理必須在主線程中執行。這樣一旦任務多了就會阻塞 UI 線程導致畫面卡頓,從而嚴重影響性能,所以正確的做法是將耗時的操作單獨放在子線程中與 UI 線程隔離,等到耗時操作完成之后再把結果傳到 UI 線程進行展示,這就要用到本節學到的消息傳遞工具——Handler。
1. Handler 基本原理
Handler 是連接不同線程的管道,它讓你能夠在不同線程之間自由的傳遞數據,當然我們用的比較多的場景是在子線程中 與主線程通信。因為 Android 系統要求只能在主線程操作 UI,所以常規的做法是將子線程耗時操作的結果傳遞到 UI 線程進行刷新。
Handler 的基本原理如下圖所示:
每一個 Handler 實例與一個線程關聯,每一個線程又會維護一個自己的 MessageQueue,當我們創建一個 Handler 的時候我們需要制定一個 Looper 對象(Looper對象對應一個線程),這樣就將一個 Handler 對象和一個線程綁定到了一起,隨后就可以編寫我們的耗時操作,然后通過 Handler 將消息塞入線程的 MessageQueue中,當對應線程從 MessageQueue 取出該條消息的時候,就會回調 Handler 的 handleMessage
方法并拿到消息,這樣就完成了跨線程通信。
2. Handler 相關方法介紹
- void handleMessage(Message msg):
在該方法中處理其他線程傳遞過來的消息*(用的非常多,一定要掌握!)* - sendEmptyMessage(int what):
發送一條空消息,what 可以理解為消息 ID - sendEmptyMessageDelayed(int what,long delayMillis):
延時發送空消息,what 為自定義 ID - sendMessage(Message msg):
發送消息,msg 是消息內容 - sendMessageDelayed(Message msg):
延時發送,msg是消息內容 - hasMessage(int what):
檢查 MessageQueue 中是否包含一條 ID 為 what 的消息。what 是用戶自定義的整形數,可作為消息 ID
3. Handler 使用示例
講了這么多理論知識,我們來通過一個示例來演示一下如何通過 Handler 完成線程通信。一個很常見的場景就是當 App 需要執行一個耗時任務的時候,會把任務放在子線程中執行,但是在主線程通過一個進度條(ProgressBar)來實時更新進度,這樣讓用戶能夠隨時看到任務執行的進展。
3.1 布局文件
布局比較簡單,主要由三個部分:
- 啟動任務
- 進度文本
- 進度條
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<Button
android:id="@+id/start_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar"
android:layout_alignParentStart="true"
android:layout_marginStart="24dp"
android:layout_marginTop="62dp"
android:text="開始任務" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/start_progress"
android:layout_alignBottom="@+id/start_progress"
android:layout_alignParentEnd="true"
android:layout_marginEnd="85dp"
android:gravity="center"
android:text="當前進度:0%"
android:textSize="16sp" />
</RelativeLayout>
3.2 MainActivity
主要的邏輯全在 MainActivity 中實現,要完成以下 3 個任務:
- 線程切換: 在子線程中執行耗時操作
- 更新進度: 在主線程更新
ProgressBar
,并同步更新TextView
- 消息傳遞: 通過
Handler
將進度從子線程傳遞到主線程
基本完成了上面 3 個任務,整個線程通信就搞定了。代碼如下:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final int MAX = 100;
private static final int START_PROGRESS = 100;
private static final int UPDATE_COUNT = 200;
private ProgressBar progressBar;
private Button startProgress;
private TextView textView;
private boolean mHasStart;
// 任務2:在主線程刷新進度條
Handler mHandlerThread = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == START_PROGRESS) {
if (!mHasStart) {
thread.start();
mHasStart = true;
}
} else if (msg.what == UPDATE_COUNT) {
textView.setText("當前進度:" + msg.arg1 + "%");
progressBar.setProgress(msg.arg1);
}
}
};
// 任務1:在子線程執行耗時操作,通過sleep模擬耗時任務
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
try {
// 一秒鐘的耗時操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = new Message();
message.what = UPDATE_COUNT;
message.arg1 = i;
mHandlerThread.sendMessage(message);
}
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
startProgress = findViewById(R.id.start_progress);
textView = findViewById(R.id.textView);
progressBar.setMax(MAX);
startProgress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 任務3:通過Handler傳遞進度消息
Message message = new Message();
message.what = START_PROGRESS;
mHandlerThread.sendEmptyMessage(START_PROGRESS);
}
});
}
}
4. 小結
本節學習了 Android 消息傳遞機制,詳細介紹了 Handler 的基本原理,以及 Looper、線程、MessageQueue、Message 等概念。Handler 最常見的用途就是在子線程執行耗時操作的時候與主線程通信,通知主線程進行 UI 的刷新。一個完成的線程通信主要有 3 大任務,并用一個完成的示例演示了如何完成這 3 個任務,這一節的內容非常重要,請大家務必掌握!