Android中Handler的使用
在Android開發中,我們經常會遇到這樣一種情況:在UI介面上進行某項操作後要執行一段很耗時的程式碼,比如我們在介面上點選了一個”下載“按鈕,那麼我們需要執行網路請求,這是一個耗時操作,因為不知道什麼時候才能完成。為了保證不影響UI執行緒,所以我們會建立一個新的執行緒去執行我們的耗時的程式碼。當我們的耗時操作完成時,我們需要更新UI介面以告知使用者操作完成了。所以我們可能會寫出如下的程式碼:
package ispring.com.testhandler;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("開始下載檔案");
//此處讓執行緒DownloadThread休眠5秒中,模擬檔案的耗時過程
Thread.sleep(5000);
System.out.println("檔案下載完成");
//檔案下載完成後更新UI
MainActivity.this.statusTextView.setText("檔案下載完成");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
上面的程式碼演示了單擊”下載“按鈕後會啟動一個新的執行緒去執行實際的下載操作,執行完畢後更新UI介面。但是在實際執行到程式碼MainActivity.this.statusTextView.setText(“檔案下載完成”)時,會報錯如下,系統崩潰退出:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
錯誤的意思是隻有建立View的原始執行緒才能更新View。出現這樣錯誤的原因是Android中的View不是執行緒安全的,在Android應用啟動時,會自動建立一個執行緒,即程式的主執行緒,主執行緒負責UI的展示、UI事件訊息的派發處理等等,因此主執行緒也叫做UI執行緒,statusTextView是在UI執行緒中建立的,當我們在DownloadThread執行緒中去更新UI執行緒中建立的statusTextView時自然會報上面的錯誤。Android的UI控制元件是非執行緒安全的,其實很多平臺的UI控制元件都是非執行緒安全的,比如C#的.Net Framework中的UI控制元件也是非執行緒安全的,所以不僅僅在Android平臺中存在從一個新執行緒中去更新UI執行緒中建立的UI控制元件的問題。不同的平臺提供了不同的解決方案以實現跨執行緒跟新UI控制元件,Android為了解決這種問題引入了Handler機制。
那麼Handler到底是什麼呢?Handler是Android中引入的一種讓開發者參與處理執行緒中訊息迴圈的機制。每個Hanlder都關聯了一個執行緒,每個執行緒內部都維護了一個訊息佇列MessageQueue,這樣Handler實際上也就關聯了一個訊息佇列。可以通過Handler將Message和Runnable物件傳送到該Handler所關聯執行緒的MessageQueue(訊息佇列)中,然後該訊息佇列一直在迴圈拿出一個Message,對其進行處理,處理完之後拿出下一個Message,繼續進行處理,周而復始。當建立一個Handler的時候,該Handler就繫結了當前建立Hanlder的執行緒。從這時起,該Hanlder就可以傳送Message和Runnable物件到該Handler對應的訊息佇列中,當從MessageQueue取出某個Message時,會讓Handler對其進行處理。
Handler可以用來在多執行緒間進行通訊,在另一個執行緒中去更新UI執行緒中的UI控制元件只是Handler使用中的一種典型案例,除此之外,Handler可以做很多其他的事情。每個Handler都繫結了一個執行緒,假設存在兩個執行緒ThreadA和ThreadB,並且HandlerA繫結了 ThreadA,在ThreadB中的程式碼執行到某處時,出於某些原因,我們需要讓ThreadA執行某些程式碼,此時我們就可以使用Handler,我們可以在ThreadB中向HandlerA中加入某些資訊以告知ThreadA中該做某些處理了。由此可以看出,Handler是Thread的代言人,是多執行緒之間通訊的橋樑,通過Handler,我們可以在一個執行緒中控制另一個執行緒去做某事。
Handler提供了兩種方式解決我們在本文一開始遇到的問題(在一個新執行緒中更新主執行緒中的UI控制元件),一種是通過post方法,一種是呼叫sendMessage方法。
a. 使用post方法,程式碼如下:
package ispring.com.testhandler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主執行緒中建立,所以自動繫結主執行緒
private Handler uiHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("DownloadThread id " + Thread.currentThread().getId());
System.out.println("開始下載檔案");
//此處讓執行緒DownloadThread休眠5秒中,模擬檔案的耗時過程
Thread.sleep(5000);
System.out.println("檔案下載完成");
//檔案下載完成後更新UI
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Runnable thread id " + Thread.currentThread().getId());
MainActivity.this.statusTextView.setText("檔案下載完成");
}
};
uiHandler.post(runnable);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
我們在Activity中建立了一個Handler成員變數uiHandler,Handler有個特點,在執行new Handler()的時候,預設情況下Handler會繫結當前程式碼執行的執行緒,我們在主執行緒中例項化了uiHandler,所以uiHandler就自動繫結了主執行緒,即UI執行緒。當我們在DownloadThread中執行完耗時程式碼後,我們將一個Runnable物件通過post方法傳入到了Handler中,Handler會在合適的時候讓主執行緒執行Runnable中的程式碼,這樣Runnable就在主執行緒中執行了,從而正確更新了主執行緒中的UI。以下是輸出結果:
通過輸出結果可以看出,Runnable中的程式碼所執行的執行緒ID與DownloadThread的執行緒ID不同,而與主執行緒的執行緒ID相同,因此我們也由此看出在執行了Handler.post(Runnable)這句程式碼之後,執行Runnable程式碼的執行緒與Handler所繫結的執行緒是一致的,而與執行Handler.post(Runnable)這句程式碼的執行緒(DownloadThread)無關。
b. 使用sendMessage方法,程式碼如下:
package ispring.com.testhandler;
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.TextView;
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主執行緒中建立,所以自動繫結主執行緒
private Handler uiHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
System.out.println("handleMessage thread id " + Thread.currentThread().getId());
System.out.println("msg.arg1:" + msg.arg1);
System.out.println("msg.arg2:" + msg.arg2);
MainActivity.this.statusTextView.setText("檔案下載完成");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("DownloadThread id " + Thread.currentThread().getId());
System.out.println("開始下載檔案");
//此處讓執行緒DownloadThread休眠5秒中,模擬檔案的耗時過程
Thread.sleep(5000);
System.out.println("檔案下載完成");
//檔案下載完成後更新UI
Message msg = new Message();
//雖然Message的建構函式式public的,我們也可以通過以下兩種方式通過迴圈物件獲取Message
//msg = Message.obtain(uiHandler);
//msg = uiHandler.obtainMessage();
//what是我們自定義的一個Message的識別碼,以便於在Handler的handleMessage方法中根據what識別
//出不同的Message,以便我們做出不同的處理操作
msg.what = 1;
//我們可以通過arg1和arg2給Message傳入簡單的資料
msg.arg1 = 123;
msg.arg2 = 321;
//我們也可以通過給obj賦值Object型別傳遞向Message傳入任意資料
//msg.obj = null;
//我們還可以通過setData方法和getData方法向Message中寫入和讀取Bundle型別的資料
//msg.setData(null);
//Bundle data = msg.getData();
//將該Message傳送給對應的Handler
uiHandler.sendMessage(msg);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
通過Message與Handler進行通訊的步驟是:
1. 重寫Handler的handleMessage方法,根據Message的what值進行不同的處理操作
2. 建立Message物件
雖然Message的建構函式式public的,我們還可以通過Message.obtain()或Handler.obtainMessage()來獲得一個Message物件(Handler.obtainMessage()內部其實呼叫了Message.obtain())。
3. 設定Message的what值
Message.what是我們自定義的一個Message的識別碼,以便於在Handler的handleMessage方法中根據what識別出不同的Message,以便我們做出不同的處理操作。
4. 設定Message的所攜帶的資料,簡單資料可以通過兩個int型別的field arg1和arg2來賦值,並可以在handleMessage中讀取。
5. 如果Message需要攜帶複雜的資料,那麼可以設定Message的obj欄位,obj是Object型別,可以賦予任意型別的資料。或者可以通過呼叫Message的setData方法賦值Bundle型別的資料,可以通過getData方法獲取該Bundle資料。
6. 我們通過Handler.sendMessage(Message)方法將Message傳入Handler中讓其在handleMessage中對其進行處理。
需要說明的是,如果在handleMessage中 不需要判斷Message型別,那麼就無須設定Message的what值;而且讓Message攜帶資料也不是必須的,只有在需要的時候才需要讓其攜帶資料;如果確實需要讓Message攜帶資料,應該儘量使用arg1或arg2或兩者,能用arg1和arg2解決的話就不要用obj,因為用arg1和arg2更高效。
程式的執行結果如下:
由上我們可以看出,執行handleMessage的執行緒與建立Handler的執行緒是同一執行緒,在本示例中都是主執行緒。執行handleMessage的執行緒與執行uiHandler.sendMessage(msg)的執行緒沒有關係。
本文主要是對Android中Handler的作用於如何使用進行了初步介紹,如果大家想了解Handler的內部實現原理,可以參見下一篇博文《深入原始碼解析Android中的Handler,Message,MessageQueue,Looper》。
相關文章
- Android中Handler的正確使用Android
- Android中的handlerAndroid
- Android中HandlerAndroid
- Android中handler問題彙總Android
- Android中handler倒數計時Android
- Android Handler的使用方式和注意事項Android
- Android---元件篇---Handler的使用(1)[轉]Android元件
- Android學習筆記06——handler的使用Android筆記
- [Handler]android-Handler解釋Android
- Android中Handler引起的記憶體洩露Android記憶體洩露
- Android 中 Handler 引起的記憶體洩露Android記憶體洩露
- Android中的Handler, Looper, MessageQueue和ThreadAndroidOOPthread
- Android Handler機制使用,原始碼分析Android原始碼
- Android-如何正確地使用HandlerAndroid
- Android Handler原理Android
- Android之HandlerAndroid
- Android 面試(五):探索 Android 的 HandlerAndroid面試
- Android Handler詳細使用方法例項Android
- jquery , find the event handler,找到jquery中的event handlerjQuery
- 在asp.net handler 中 使用 sessionASP.NETSession
- Android中Handler Runnable與Thread的區別詳解Androidthread
- Android Handler機制之Handler 、MessageQueue 、LooperAndroidOOP
- Android學習-HandlerAndroid
- Android Handler機制理解和AsyncTask使用小記Android
- 深入原始碼解析Android中的Handler,Message,MessageQueue,Looper原始碼AndroidOOP
- Android Handler 原始碼解析Android原始碼
- Android Handler 原始碼探索Android原始碼
- Android 基礎之 HandlerAndroid
- Android Handler面試總結Android面試
- Android開發之HandlerAndroid
- Android——Handler原始碼分析Android原始碼
- Android Handler機制理解Android
- Android Handler原理詳解Android
- Android的Handler訊息機制 解析Android
- Android中使用Handler造成記憶體洩露的分析和解決Android記憶體洩露
- Android 中Message,MessageQueue,Looper,Handler詳解+例項AndroidOOP
- 移動架構 (二) Android 中 Handler 架構分析,並實現自己簡易版本 Handler 框架架構Android框架
- Android 進階 ———— Handler系列之建立子執行緒HandlerAndroid執行緒