Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中Handler的使用

Android中Handler的使用

編輯:關於Android編程

在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中引入的一種多線程通信的機制,在另一個線程中去更新UI線程中的UI控件只是Handler使用中的一種典型案例,除此之外,Handler可以做很多其他的事情。每個Handler都綁定了一個線程,假設存在兩個線程ThreadA和ThreadB,並且HandlerA綁定了 ThreadA,在ThreadB中的代碼執行到某處時,出於某些原因,我們需要讓ThreadA執行某些代碼,此時我們就可以使用Handler,我們可以在ThreadB中向HandlerA中加入某些信息以告知ThreadA中該做某些處理了。由此可以看出,Handler是Thread的代言人,是多線程之間通信的橋梁,通過Handler,我們可以在一個線程中控制另一個線程去做某事。
Handler中提供了兩種方式解決上述問題,一種是通過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.pZ喎?/kf/ware/vc/" target="_blank" class="keylink">vc3QoUnVubmFibGUp1eK+5LT6wuvWrrrzo6zUy9DQUnVubmFibGW0+sLrtcTP37PM0+tIYW5kbGVyy/mw87aotcTP37PMysfSu9bCtcSjrLb40+vWtNDQSGFuZGxlci5wb3N0KFJ1bm5hYmxlKdXivuS0+sLrtcTP37PMo6hEb3dubG9hZFRocmVhZKOpzt652KGjPC9wPg0KPHA+Yi4gyrnTw3NlbmRNZXNzYWdlt723qKOstPrC68jnz8KjujwvcD4NCjxwcmUgY2xhc3M9"brush:java;"> 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的內部實現原理,包括Looper、MessageQueue等。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved