目錄:
1. 前言
本篇文章介紹 Android Handler 的基本使用方法,且 Demo 會以 Java & Kotlin 兩種代碼形式進(jìn)行展示。
在 Android 實際開發(fā)中,我們經(jīng)常會遇到耗時任務(wù),比如:網(wǎng)絡(luò)請求API接口來獲取數(shù)據(jù)、數(shù)據(jù)庫 CRUD 操作等等,我們需要額外創(chuàng)建開啟工作線程來處理這些耗時任務(wù)。由于 Android 規(guī)定只能由主線程才能處理 UI 工作,所以這時候我們就需要通過 Handler 來通知主線程處理 UI 工作。
1.1 定義
Handler:子線程與主線程之間的溝通中介,用于傳遞處理消息。
在 Android 中,為保證處理 UI 工作的線程穩(wěn)定安全,規(guī)定只有主線程才能更新處理 UI 工作。所以當(dāng)子線程想處理 UI 工作時,就需要通過 Handler 來通知主線程作出相對應(yīng)的 UI 處理工作。
如下圖所示:
本篇文章,我將介紹 Handler 的三種使用方法,分別是:
- Handler.sendMessage(Message)
- Handler.post(Runnable)
- Handler.obtainMessage(what).sendToTarget();
2. Handler.sendMessage()方法
Handler.sendMessage(Msg) 方法是最為常見的一種方法。
2.1 使用步驟說明
其使用步驟分四步,如下所示:
- 步驟一:新建 Handler 對象,覆寫 handleMessage(Message) 方法。
- 步驟二:新建 Message 對象,設(shè)置其攜帶的數(shù)據(jù)。
- 步驟三:在子線程中通過 Handler.sendMessage(Message) 方法發(fā)送信息。
- 步驟四:在 Handler 的 handleMessage(Message msg) 方法中處理消息,通知主線程作出相對應(yīng)的 UI 工作。
- 步驟一:新建 Handler 對象,覆寫 handleMessage(Message) 方法
//創(chuàng)建 Handler對象,并關(guān)聯(lián)主線程消息隊列
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
···略···
}
}
};
- 步驟二:新建 Message 對象,設(shè)置其攜帶的數(shù)據(jù)
Bundle bundle = new Bundle();
bundle.putInt(CURRENT_PROCESS_KEY, i);
Message msg = new Message();
msg.setData(bundle);
msg.what = 2;
- 步驟三:在子線程中通過 Handler.sendMessage(Message) 方法發(fā)送信息
mHandler.sendMessage(msg)
- 步驟四:在 Handler 的 handleMessage(Message msg) 方法中處理消息,通知主線程作出相對應(yīng)的 UI 工作
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//根據(jù)信息編碼及數(shù)據(jù)做出相對應(yīng)的處理
switch (msg.what) {
case 1:
//更新 TextView UI
mDisplayTv.setText("CustomChildThread starting!");
break;
case 2:
//獲取 ProgressBar 的進(jìn)度,然后顯示進(jìn)度值
Bundle bundle = msg.getData();
int process = bundle.getInt(CURRENT_PROCESS_KEY);
mProgressBar.setProgress(process);
break;
default:
break;
}
}
};
2.2 具體例子
根據(jù)上述的使用步驟,我們來完成一個完整的例子:
新建一個線程,模擬處理耗時任務(wù),然后將處理進(jìn)度通過 ProgressBar 顯示出來。
效果如下所示:點(diǎn)擊按鈕,開啟線程處理耗時操作。
具體代碼見下:
2.2.1 Java版本Demo
Java版本的具體代碼如下所示:
public class HandlerAddThreadActivity extends AppCompatActivity {
public static final String CURRENT_PROCESS_KEY = "CURRENT_PROCESS";
private TextView mDisplayTv;
private Handler mHandler;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_add_thread);
TextView titleTv = findViewById(R.id.title_tv);
titleTv.setText("Handler + Thread");
mDisplayTv = findViewById(R.id.display_tv);
mProgressBar = findViewById(R.id.test_handler_progress_bar);
//mHandler用于處理主線程消息隊列中的子線程消息
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//更新 TextView UI
mDisplayTv.setText("CustomChildThread starting!");
break;
case 2:
//獲取 ProgressBar 的進(jìn)度,然后顯示進(jìn)度值
Bundle bundle = msg.getData();
int process = bundle.getInt(CURRENT_PROCESS_KEY);
mProgressBar.setProgress(process);
break;
default:
break;
}
}
};
Button mClickBtn = findViewById(R.id.click_btn);
mClickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//開啟子線程,子線程處理UI工作
CustomChildThread customThread = new CustomChildThread();
customThread.start();
}
});
}
/**
* 子線程,用于處理耗時工作
*/
public class CustomChildThread extends Thread {
@Override
public void run() {
//在子線程中創(chuàng)建一個消息對象
Message childThreadMessage = new Message();
childThreadMessage.what = 1;
//將該消息放入主線程的消息隊列中
mHandler.sendMessage(childThreadMessage);
//模擬耗時進(jìn)度,將進(jìn)度值傳給主線程用于更新 ProgressBar 進(jìn)度。
for (int i = 1; i <= 5; i++) {
try {
//讓當(dāng)前執(zhí)行的線程(即 CustomChildThread)睡眠 1s
Thread.sleep(1000);
//Message 傳遞參數(shù)
Bundle bundle = new Bundle();
bundle.putInt(CURRENT_PROCESS_KEY, i);
Message progressBarProcessMsg = new Message();
progressBarProcessMsg.setData(bundle);
progressBarProcessMsg.what = 2;
mHandler.sendMessage(progressBarProcessMsg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
執(zhí)行結(jié)果看上圖 Handler.sendMessage() Demo。
2.2.2 Kotlin版本Demo
Kotlin版本的代碼如下所示:
class TestThreadAddHandlerActivity : AppCompatActivity() {
companion object {
const val PROGRESS_VALUE_KEY = "PROGRESS_VALUE"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread_add_handler)
handlerAddThreadStartBtn.setOnClickListener(View.OnClickListener {
//工作線程開始模擬下載任務(wù)
val workThread: WorkThread = WorkThread(this)
workThread.start()
})
}
class WorkThread(activity: TestThreadAddHandlerActivity) : Thread() {
private var handler: MyHandler = MyHandler(activity)
override fun run() {
super.run()
for (i in 0..6) {
sleep(1000)
//通過 Handler 將進(jìn)度參數(shù)傳遞給 主線程,讓其更新 progressBar 進(jìn)度
val message = Message()
message.what = 1
val bundle = Bundle()
bundle.putInt(PROGRESS_VALUE_KEY, i)
message.data = bundle
handler.sendMessage(message)
}
}
}
/**
* 靜態(tài)內(nèi)部類,防止內(nèi)存泄漏
*/
class MyHandler(activity: TestThreadAddHandlerActivity) : Handler() {
private var weakReference = WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
//處理消息
when (msg.what) {
1 -> {
val activity = weakReference.get()
if (activity != null && !activity.isFinishing) {
//獲取消息中攜帶的任務(wù)處理進(jìn)度參數(shù),然后設(shè)置成 ProgressBar 的進(jìn)度。
val progressValue: Int = msg.data.get(PROGRESS_VALUE_KEY) as Int
activity.handlerAddThreadProgressBar.progress = progressValue
}
}
}
}
}
}
執(zhí)行結(jié)果看上圖 Handler.sendMessage() Demo
3. Handler.post()方法
除了使用 Handler.sendMessage(Message) 來發(fā)送信息,Handler 還支持 post(Runnable) 方法來傳遞消息,通知主線程做出相對應(yīng)的 UI 工作。使用方法如下:
/**
* 將可運(yùn)行的 Runnable 添加到消息隊列。Runnable 將在該 Handler 相關(guān)的線程上運(yùn)行處理。
* The runnable will be run on the thread to which this handler is attached.
*/
new Handler().post(new Runnable() {
@Override
public void run() {
//更新處理 UI 工作
}
});
3.1 具體例子
實現(xiàn)上述同樣的功能 -> 點(diǎn)擊按鈕,子線程開始工作模擬處理耗時任務(wù),通過 Handler 將進(jìn)度告訴主線程,再通過 ProgressBar 將進(jìn)度顯示出來。
效果如下:
具體代碼見下:
3.1.1 Java版本Demo
Java版本的具體代碼如下:
public class HandlerPostFunctionActivity extends AppCompatActivity {
private Handler mMainHandler;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_add_thread);
TextView titleTv = findViewById(R.id.title_tv);
titleTv.setText("Handler post() function");
mProgressBar = findViewById(R.id.test_handler_progress_bar);
//新建靜態(tài)內(nèi)部類 Handler 對象
mMainHandler = new Handler(getMainLooper());
Button mClickBtn = findViewById(R.id.click_btn);
mClickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//開啟子線程,子線程處理UI工作
CustomChildThread customThread = new CustomChildThread();
customThread.start();
}
});
}
/**
* 子線程,用于處理耗時工作
*/
public class CustomChildThread extends Thread {
@Override
public void run() {
//模擬耗時進(jìn)度,將進(jìn)度值傳給主線程用于更新 ProgressBar 進(jìn)度。
for (int i = 1; i <= 5; i++) {
try {
//讓當(dāng)前執(zhí)行的線程(即 CustomChildThread)睡眠 1s
Thread.sleep(1000);
//新創(chuàng)建一個 Runnable 用戶處理 UI 工作
MyRunnable runnable = new MyRunnable(HandlerPostFunctionActivity.this, i);
//調(diào)用Handler post 方法。
mMainHandler.post(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 將 Runnable 寫成靜態(tài)內(nèi)部類,防止內(nèi)存泄露
*/
public static class MyRunnable implements Runnable {
private int progressBarValue;
private WeakReference<HandlerPostFunctionActivity> weakReference;
MyRunnable(HandlerPostFunctionActivity activity, int value) {
this.weakReference = new WeakReference<>(activity);
this.progressBarValue = value;
}
@Override
public void run() {
HandlerPostFunctionActivity activity = weakReference.get();
if (activity != null && !activity.isFinishing()) {
activity.mProgressBar.setProgress(progressBarValue);
}
}
}
}
執(zhí)行結(jié)果看上圖 Handler.post() Demo
3.1.2 Kotlin版本Demo
Kotlin版本的具體代碼如下:
class TestHandlerPostRunnableActivity : AppCompatActivity() {
private var mMainHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread_add_handler)
handlerAddThreadStartBtn.setOnClickListener(View.OnClickListener {
//工作線程開始模擬下載任務(wù)
val workThread: WorkThread = WorkThread(this)
workThread.start()
})
//創(chuàng)建 Handler,關(guān)聯(lián)App的 主Looper 對象
mMainHandler = Handler(Looper.getMainLooper())
}
class WorkThread(private var activity: TestHandlerPostRunnableActivity) : Thread() {
private var handler: Handler? = activity.mMainHandler
override fun run() {
super.run()
for (i in 0..6) {
sleep(1000)
//新建 Runnable 設(shè)置進(jìn)度參數(shù)傳,然后通過 post(Runnable) 方法,讓其更新 progressBar 進(jìn)度
val runnable: MyRunnable = MyRunnable(activity, i)
handler?.post(runnable)
}
}
}
/**
* 處理 UI 工作。
* 靜態(tài)內(nèi)部類,防止內(nèi)存泄露
*/
class MyRunnable(activity: TestHandlerPostRunnableActivity, value: Int) : Runnable {
private var weakReference = WeakReference(activity)
private var progressValue = value
override fun run() {
val activity = weakReference.get()
if (activity != null && !activity.isFinishing) {
//獲取任務(wù)執(zhí)行進(jìn)度參數(shù),更新 progressBar 進(jìn)度
activity.handlerAddThreadProgressBar.progress = progressValue
}
}
}
}
執(zhí)行結(jié)果看上圖 Handler.post() Demo
4. obtainMessage()方法
obtainMessage() 方法與 sendMessage() 方法很相似,通過 mHandler.obtainMessage().sendToTarget() 發(fā)送信息。該方法與 sendMessage() 的區(qū)別就是你不用額外去創(chuàng)建一個 Message 對象。
obtainMessage() 方法有三種,分別是:
//指定 what 用于區(qū)分,通過 Message.what 獲得
public final Message obtainMessage(int what);
//傳遞obj參數(shù),通過 Message.obj 獲得
public final Message obtainMessage(int what, @Nullable Object obj)
//傳遞arg1 arg2參數(shù),通過 Message.arg1 Message.arg2 獲得
public final Message obtainMessage(int what, int arg1, int arg2)
4.1 具體例子
同樣實現(xiàn)上述同樣的功能 -> 點(diǎn)擊按鈕,子線程開始工作,通過 Handler 將進(jìn)度告訴主線程,通過 ProgressBar 將進(jìn)度顯示出來。
效果如下:
具體代碼如下:
4.1.1 Java版本Demo
Java版本的具體代碼如下:
public class HandlerObtainMessageActivity extends AppCompatActivity {
private TextView mDisplayTv;
private Handler mHandler;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_add_thread);
TextView titleTv = findViewById(R.id.title_tv);
titleTv.setText("Handler + Thread");
mDisplayTv = findViewById(R.id.display_tv);
mProgressBar = findViewById(R.id.test_handler_progress_bar);
//mHandler用于處理主線程消息隊列中的子線程消息
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//更新 TextView UI
mDisplayTv.setText("Handler obtainMessage() Test!!");
break;
case 2:
//通過 msg.obj 獲取 ProgressBar 的進(jìn)度,然后顯示進(jìn)度值
int process = (int) msg.obj;
mProgressBar.setProgress(process);
break;
default:
break;
}
}
};
Button mClickBtn = findViewById(R.id.click_btn);
mClickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//開啟子線程,子線程處理UI工作
CustomChildThread customThread = new CustomChildThread();
customThread.start();
}
});
}
/**
* 子線程,用于處理耗時工作
*/
public class CustomChildThread extends Thread {
@Override
public void run() {
//發(fā)送第一條消息,代表開始執(zhí)行異步任務(wù)
mHandler.obtainMessage(1).sendToTarget();
//模擬耗時進(jìn)度,將進(jìn)度值傳給主線程用于更新 ProgressBar 進(jìn)度。
for (int i = 1; i <= 5; i++) {
try {
//讓當(dāng)前執(zhí)行的線程(即 CustomChildThread)睡眠 1s
Thread.sleep(1000);
//將執(zhí)行進(jìn)度參數(shù) i 傳遞給主線程 progressBar
mHandler.obtainMessage(2, i).sendToTarget();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
執(zhí)行結(jié)果看上圖 Handler.obtainMessage() Demo
4.1.2 kotlin版本Demo
Kotlin版本的具體代碼如下:
class TestHandlerObtainMessageActivity : AppCompatActivity() {
companion object {
const val PROGRESS_VALUE_KEY = "PROGRESS_VALUE"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread_add_handler)
handlerAddThreadStartBtn.setOnClickListener(View.OnClickListener {
//工作線程開始模擬下載任務(wù)
val workThread: WorkThread = WorkThread(this)
workThread.start()
})
}
class WorkThread(activity: TestHandlerObtainMessageActivity) : Thread() {
private var handler: MyHandler = MyHandler(activity)
override fun run() {
super.run()
for (i in 0..6) {
sleep(1000)
//通過 Handler 將進(jìn)度參數(shù)傳遞給 主線程,讓其更新 progressBar 進(jìn)度
val bundle = Bundle()
bundle.putInt(PROGRESS_VALUE_KEY, i)
handler.obtainMessage(1, bundle).sendToTarget()
}
}
}
/**
* 靜態(tài)內(nèi)部類,防止內(nèi)存泄漏
*/
class MyHandler(activity: TestHandlerObtainMessageActivity) : Handler() {
private var weakReference = WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
1 -> {
val activity = weakReference.get()
if (activity != null && !activity.isFinishing) {
//獲取任務(wù)執(zhí)行進(jìn)度參數(shù),然后通過 ProgressBar 顯示出來
val bundle: Bundle = msg.obj as Bundle
val progressValue: Int = bundle.get(PROGRESS_VALUE_KEY) as Int
activity.handlerAddThreadProgressBar.progress = progressValue
}
}
}
}
}
}
執(zhí)行結(jié)果看上圖 Handler.obtainMessage() Demo
5. 總結(jié):
在實際開發(fā)中,三種方法的使用都可行,具體用哪種方法,需結(jié)合你的實際情況及個人喜好。另外,在實際使用中往往將 Handler 寫成靜態(tài)內(nèi)部類,這時需要注意防止內(nèi)存泄露!(The handler class should be static or leaks might occur),具體代碼見上方!
5.1 在子線程中創(chuàng)建Handler
思考: 在上面代碼中, 我們都是在主線程中創(chuàng)建了 Handler 對象,那如果在子線程中創(chuàng)建一個 Handler 對象呢?會發(fā)生什么呢?
如下所示:我們在 CustomChildThread 線程中,新建一個 Handler 對象。
public class CustomChildThread extends Thread {
@Override
public void run() {
Handler handler = new Handler(Activity.this);
//會報錯:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
}
}
結(jié)果: 拋出異常: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()。
原因: 因為在創(chuàng)建 Handler對象時要關(guān)聯(lián)所處線程的 Looper對象,而我們的子線程沒有 Looper,所以會拋出上述異常。
解決方法: 通過調(diào)用,Looper.prepare() 方法為子線程創(chuàng)建一個 Looper 對象,并且調(diào)用 Looper.loop() 方法開始消息循環(huán)。如下所示:
class CustomChildThread extends Thread {
@Override
public void run() {
//為當(dāng)前線程創(chuàng)建一個 Looper 對象
Looper.prepare();
//在子線程中創(chuàng)建一個 Handler 對象
Handler handler = new Handler() {
public void handleMessage(Message msg) {
// 在這里處理傳入的消息
}
};
//開始消息循環(huán)
Looper.loop();
}
}
至此關(guān)于 Handler 的使用方法也就介紹完畢了,如果你想深入學(xué)習(xí) Handler,了解其通信機(jī)制以及源碼分析,請看博客 Android Handler深入學(xué)習(xí)(源碼分析)
其實分享文章的最大目的正是等待著有人指出我的錯誤,如果你發(fā)現(xiàn)哪里有錯誤,請毫無保留的指出即可,虛心請教。
另外,如果你覺得文章不錯,對你有所幫助,請給我點(diǎn)個贊,就當(dāng)鼓勵,謝謝~Peace~!