Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 淺談Android消息異步處理機制

淺談Android消息異步處理機制

編輯:關於Android編程

Android 確實是不允許在子線程中進行UI 操作。但有些時候,我們必須在子線程裡去執行一些耗時任務,然後根據任務的執行結果來更新相應的UI 控件,這該如何是好呢?

對於這種情況,Android 提供了一套異步消息處理機制,完美地解決了在子線程中進行UI 操作的問題。

現在我們就來學習異步消息處理。

先看一段代碼:

 

package gdtest.com.quan.car.servicemessagetest;

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 View.OnClickListener {

    public static final int UPDATE_TEXT = 1;

    private TextView content_tv_main;

    private Button change_btn_main;

    private Boolean flag = true;

    private Handler handler = new Handler() {

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    if(flag){
                        content_tv_main.setText("Nice to meet you.");
                        flag = false;
                    }else{
                        content_tv_main.setText("Nice to meet you too.");
                        flag = true;
                    }
                    break;
                default:
                    break;
            }
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        content_tv_main = (TextView) findViewById(R.id.content_tv_main);
        change_btn_main = (Button) findViewById(R.id.change_btn_main);
        change_btn_main.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.change_btn_main:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

 

 

這裡我們先是定義了一個整型常量UPDATE_TEXT,用於表示更新TextView 這個動作。然後新增一個Handler 對象,並重寫父類的handleMessage 方法,在這裡對具體的Message進行處理。如果發現Message 的what 字段的值等於UPDATE_TEXT,就將TextView 顯示的內容改成Nice to meet you。
下面再來看一下Change Text 按鈕的點擊事件中的代碼。可以看到,這次我們創建Message(android.os.Message)對象,並將它的what 字段的值指定為UPDATE_TEXT,然後調用Handler 的sendMessage()方法將這條Message 發送出去。很快,Handler 就會收到這條Message,並在handleMessage()方法中對它進行處理。注意此時handleMessage()方法中的代碼就是在主線程當中運行的了,所以我們可以放心地在這裡進行UI 操作。

接下來對Message 攜帶的what 字段的值進行判斷,如果等於UPDATE_TEXT,就將TextView 顯示的內容改成Nice to meet you,設置了一個flag標志位,再次點擊就變成Nice to meet you too,來回切換。
現在重新運行程序,可以看到屏幕的正中央顯示著Hello world。然後點擊一下ChangeText 按鈕,顯示的內容著就被替換成Nice to meet you/Nice to meet you too,如圖所示。

\

 

 

這就是Android 異步消息處理的基本用法,使用這種機制就可以出色地解決掉在子線程中更新UI 的問題。

下面我們分析一下Android 異步消息處理機制到底是如何工作的。

Android 中的異步消息處理主要由四個部分組成,Message、Handler、MessageQueue 和Looper。其中Message 和Handler 我們已經接觸過了,而MessageQueue 和Looper對於你來說還是全新的概念,下面我就對這四個部分進行一下簡要的介紹。
1. Message
Message 是在線程之間傳遞的消息,它可以在內部攜帶少量的信息,用於在不同線程之間交換數據。上一小節中我們使用到了Message 的what 字段,除此之外還可以使用arg1 和arg2 字段來攜帶一些整型數據,使用obj 字段攜帶一個Object 對象。
2. Handler
Handler 顧名思義也就是處理者的意思,它主要是用於發送和處理消息的。發送消息一般是使用Handler 的sendMessage()方法,而發出的消息經過一系列地輾轉處理後,最終會傳遞到Handler的handleMessage()方法中。
3. MessageQueue
MessageQueue 是消息隊列的意思,它主要用於存放所有通過Handler 發送的消息。這部分消息會一直存在於消息隊列中,等待被處理。每個線程中只會有一個MessageQueue對象。
4. Looper
Looper 是每個線程中的MessageQueue 的管家,調用Looper 的loop()方法後,就會進入到一個無限循環當中,然後每當發現MessageQueue 中存在一條消息,就會將它取出,並傳遞到Handler 的handleMessage()方法中。每個線程中也只會有一個Looper 對象。
了解了Message、Handler、MessageQueue 以及Looper 的基本概念後,我們再來對異步消息處理的整個流程梳理一遍。首先需要在主線程當中創建一個Handler 對象,並重寫handleMessage()方法。然後當子線程中需要進行UI 操作時,就創建一個Message 對象,並通過Handler 將這條消息發送出去。之後這條消息會被添加到MessageQueue 的隊列中等待被處理,而Looper 則會一直嘗試從MessageQueue 中取出待處理消息,最後分發回Handler的handleMessage()方法中。由於Handler 是在主線程中創建的,所以此時handleMessage()方法中的代碼也會在主線程中運行,於是我們在這裡就可以安心地進行UI 操作了。整個異步消息處理機制的流程示意圖如圖所示。

\

 

一條Message 經過這樣一個流程的輾轉調用後,也就從子線程進入到了主線程,從不能更新UI 變成了可以更新UI,整個異步消息處理的核心思想也就是如此。

 

使用AsyncTask
不過為了更加方便我們在子線程中對UI 進行操作,Android 還提供了另外一些好用的工具,AsyncTask 就是其中之一。借助AsyncTask,即使你對異步消息處理機制完全不了解,也可以十分簡單地從子線程切換到主線程。

當然,AsyncTask 背後的實現原理也是基於異步消息處理機制的,只是Android 幫我們做了很好的封裝而已。
首先來看一下AsyncTask 的基本用法,由於AsyncTask 是一個抽象類,所以如果我們想使用它,就必須要創建一個子類去繼承它。在繼承時我們可以為AsyncTask 類指定三個泛型參數,這三個參數的用途如下。
1. Params
在執行AsyncTask 時需要傳入的參數,可用於在後台任務中使用。
2. Progress
後台任務執行時,如果需要在界面上顯示當前的進度,則使用這裡指定的泛型作為進度單位。

3. Result
當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值類型。
因此,一個最簡單的自定義AsyncTask 就可以寫成如下方式:
class DownloadTask extends AsyncTask {
……
}
這裡我們把AsyncTask 的第一個泛型參數指定為Void,表示在執行AsyncTask 的時候不需要傳入參數給後台任務。第二個泛型參數指定為Integer,表示使用整型數據來作為進度顯示單位。第三個泛型參數指定為Boolean,則表示使用布爾型數據來反饋執行結果。

當然,目前我們自定義的DownloadTask 還是一個空任務,並不能進行任何實際的操作,
我們還需要去重寫AsyncTask 中的幾個方法才能完成對任務的定制。經常需要去重寫的方法有以下四個。
1. onPreExecute()
這個方法會在後台任務開始執行之前調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。
2. doInBackground(Params...)
這個方法中的所有代碼都會在子線程中運行,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return 語句來將任務的執行結果返回,如果AsyncTask 的第三個泛型參數指定的是Void,就可以不返回任務執行結果。

注意,在這個方法中是不可以進行UI 操作的,如果需要更新UI 元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。
3. onProgressUpdate(Progress...)
當在後台任務中調用了publishProgress(Progress...)方法後,這個方法就會很快被調用,方法中攜帶的參數就是在後台任務中傳遞過來的。在這個方法中可以對UI 進行操作,利用參數中的數值就可以對界面元素進行相應地更新。
4. onPostExecute(Result)
當後台任務執行完畢並通過return 語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到此方法中,可以利用返回的數據來進行一些UI 操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。

因此,一個比較完整的自定義AsyncTask 就可以寫成如下方式:

class DownloadTask extends AsyncTask{

    @Override
    protected void onPreExecute() {
        //progressDialog.show();//顯示進度對話框
        super.onPreExecute();
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            while (true) {
                int downloadPercent = doDownload(); // 這是一個虛構的方法
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    private int doDownload() {
        return 0;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // 在這裡更新下載進度
        //progressDialog.setMessage("Downloaded " + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean result) {
//        progressDialog.dismiss(); // 關閉進度對話框
//        // 在這裡提示下載結果
//        if (result) {
//            Toast.makeText(context, "Download succeeded",
//                    Toast.LENGTH_SHORT).show();
//        } else {
//            Toast.makeText(context, " Download failed",
//                    Toast.LENGTH_SHORT).show();
//        }
    }
}

 

 

在這個DownloadTask 中,我們在doInBackground()方法裡去執行具體的下載任務。這個方法裡的代碼都是在子線程中運行的,因而不會影響到主線程的運行。注意這裡虛構了一個doDownload()方法,這個方法用於計算當前的下載進度並返回。在得到了當前的下載進度後, 下面就該考慮如何把它顯示到界面上了, 由於doInBackground()方法是在子線程中運行的,在這裡肯定不能進行UI 操作,

所以我們可以調用publishProgress()方法並將當前的下載進度傳進來,這樣onProgressUpdate()方法就會很快

被調用,在這裡就可以進行UI 操作了。
當下載完成後,doInBackground()方法會返回一個布爾型變量,這樣onPostExecute()方法就會很快被調用,這個方法也是在主線程中運行的。然後在這裡我們會根據下載的結果來彈出相應的Toast 提示,從而完成整個DownloadTask 任務。
簡單來說,使用AsyncTask 的訣竅就是,在doInBackground()方法中去執行具體的耗時任務,在onProgressUpdate()方法中進行UI 操作,在onPostExecute()方法中執行一些任務的收尾工作。
如果想要啟動這個任務,只需編寫以下代碼即可:
new DownloadTask().execute();
以上就是AsyncTask 的基本用法,怎麼樣,是不是感覺簡單方便了許多?我們並不需要去考慮什麼異步消息處理機制,也不需要專門使用一個Handler 來發送和接收消息,只需要調用一下publishProgress()方法就可以輕松地從子線程切換到UI 線程了。

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