1. 程式人生 > >Android 非同步訊息處理機制解析

Android 非同步訊息處理機制解析

一、Message、Handler、MessageQueue、Looper

  Android 中的非同步訊息處理主要由四個部分組成,Message、Handler、MessageQueue、Looper。

  1. Message: Message 是線上程之間傳遞的訊息,它可以在內部攜帶少量的資訊,用於在不同執行緒之間交換資料。通常使用 Message 的 what 欄位攜帶命令,除此之外還可以使用 arg1 和arg2 欄位來攜帶一些整形資料,使用 obj 欄位攜帶一個 Object 物件。

  2. Handler: Handler 顧名思義也就是處理者的意思,它主要是用於傳送和處理訊息的。傳送訊息一般是使用 Handler 的 sendMessage()方法,而發出的訊息經過一系列地輾轉處理後,最終會傳遞到 Handler 的 handlerMessage()方法中。

  3. MessageQueue: MessageQueue 是訊息佇列的意思,它主要用於存放所有通過 Handler 傳送的訊息。這部分訊息會一直存在於訊息佇列中,等待被處理。每個執行緒中只會有一個 MessageQueue 物件。

  4. Looper: Looper 是每個執行緒中的 MessageQueue 的管家,呼叫 Looper 的 loop() 方法後,就會進入到一個無限迴圈當中,然後每當發現 MessageQueue 中存在一條訊息,就會將它取出,並傳遞到 Handler 的 handleMessage() 方法中。每個執行緒中也只會有一個 Looper 物件。
  
  

二、Handler 、 Looper 、Message的關係

  其實Looper負責的就是建立一個MessageQueue,然後進入一個無限迴圈體不斷從該MessageQueue中讀取訊息,而訊息的建立者就是一個或多個Handler 。

1、Looper:

prepare()和loop()兩個方法。

    public static final void prepare() {  
        if (sThreadLocal.get() != null) {  
            throw new RuntimeException("Only one Looper may be created per thread"
); } sThreadLocal.set(new Looper(true)); } public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } }

Looper主要作用:

1、 與當前執行緒繫結,保證一個執行緒只會有一個Looper例項,同時一個Looper例項也只有一個MessageQueue。
2、 loop()方法,不斷從MessageQueue中去取訊息,交給訊息的target屬性的dispatchMessage去處理。

  好了,我們的非同步訊息處理執行緒已經有了訊息佇列(MessageQueue),也有了在無限迴圈體中取出訊息的哥們,現在缺的就是傳送訊息的物件了,於是乎:Handler登場了。

2、Handler:

  使用Handler之前,我們都是初始化一個例項,比如用於更新UI執行緒,我們會在宣告的時候直接初始化,或者在onCreate中初始化Handler例項。所以我們首先看Handler的構造方法,看其如何與MessageQueue聯絡上的,它在子執行緒中傳送的訊息(一般傳送訊息都在非UI執行緒)怎麼傳送到MessageQueue中的。

    public Handler() {  
            this(null, false);  
    }  
    public Handler(Callback callback, boolean async) {  
            if (FIND_POTENTIAL_LEAKS) {  
                final Class<? extends Handler> klass = getClass();  
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
                        (klass.getModifiers() & Modifier.STATIC) == 0) {  
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
                        klass.getCanonicalName());  
                }  
            }  

            mLooper = Looper.myLooper();  
            if (mLooper == null) {  
                throw new RuntimeException(  
                    "Can't create handler inside thread that has not called Looper.prepare()");  
            }  
            mQueue = mLooper.mQueue;  
            mCallback = callback;  
            mAsynchronous = async;  
        }  

  通過Looper.myLooper()獲取了當前執行緒儲存的Looper例項,之後再獲取了這個Looper例項中儲存的MessageQueue(訊息佇列),這樣就保證了handler的例項與我們Looper例項中MessageQueue關聯上了。

總結:

1、首先Looper.prepare()在本執行緒中儲存一個Looper例項,然後該例項中儲存一個MessageQueue物件;因為Looper.prepare()在一個執行緒中只能呼叫一次,所以MessageQueue在一個執行緒中只會存在一個。

2、Looper.loop()會讓當前執行緒進入一個無限迴圈,不端從MessageQueue的例項中讀取訊息,然後回撥msg.target.dispatchMessage(msg)方法。

3、Handler的構造方法,會首先得到當前執行緒中儲存的Looper例項,進而與Looper例項中的MessageQueue想關聯。

4、Handler的sendMessage方法,會給msg的target賦值為handler自身,然後加入MessageQueue中。

5、在構造Handler例項時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終呼叫的方法。

  好了,總結完成,大家可能還會問,那麼在Activity中,我們並沒有顯示的呼叫Looper.prepare()和Looper.loop()方法,為啥Handler可以成功建立呢,這是因為在Activity的啟動程式碼中,已經在當前UI執行緒呼叫了Looper.prepare()和Looper.loop()方法。

三、非同步訊息處理機制流程

  首先需要在主執行緒當中建立一個 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 是一個抽象類,所以如果我們想使用它,就必須建立一個子類去繼承它。在繼承時我們可以為 AsyncTask 類指定三個泛型引數,這三個引數的用途如下:

    Params:在執行 AsyncTask 時需要傳入的引數,可用於在後臺任務中使用。
    
    Progress:後臺任務執行時,如果需要在介面上顯示當前的進度,則使用這裡指定的泛型作為進度單位。
    
    Result:當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值型別。

一個簡單的自定義AsyncTask:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {...}

這個自定義的DownloadTask還是一個空任務,並不能進行任何實際的操作,我們還需要重寫 AsyncTask 中的幾個方法才能完成對任務的定製。

    onPreExecute():這個方法會在後臺任務開始執行之前呼叫,用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊等。

    doInBackground(Params…):這個方法中的所有程式碼都會在子執行緒中執行,我們應該在這裡去處理所有的耗時任務。注意,在這個方法中是不可以進行 UI 操作的。
    
  onProgressUpdate(Progress…):當後臺任務中呼叫了 publishProgress(Progress…)方法後,這個方法就會很快被呼叫,方法中攜帶的引數就是在後臺任務中傳遞過來的。在這個方法中可以對 UI 進行操作,利用引數中的數值就可以對介面元素進行相應地更新。

    onPostExecute(Result):當後臺任務執行完畢並通過 return 語句進行返回時,這個方法就很快會被呼叫。返回的資料會作為引數傳遞到此方法中,可以利用返回的資料來進行一些 UI 操作,比如提醒任務執行的結果,以及關閉掉進度條對話方塊等。

        class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

        @Override
        protected void onPreExecute() {
            //顯示進度對話方塊
    //        ProgressDialog.show();
        }

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

        @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 執行緒了。