Android訊息處理機制:源碼剖析Handler、Looper,並實現圖片非同步載入,

來源:互聯網
上載者:User

Android訊息處理機制:源碼剖析Handler、Looper,並實現圖片非同步載入,
引言

我們在做 Android 開發時,常常需要實現非同步載入圖片/網頁/其他。事實上,要實現非同步載入,就需要實現線程間通訊,而在 Android 中結合使用 Handler、Looper、Message 能夠讓不同的線程通訊,完成非同步任務。雖然 Android 官方為我們提供了 AsyncTask 類來完成非同步任務,但這個類存在許多問題,並不好用,而且,AsyncTask 也是通過 Handler 和 Thread 來實現非同步載入的,所以學習這方面的知識是有必要的

本文講解思路大致如下:繪製 Android 訊息處理機製圖 -> 源碼剖析訊息處理機制中的組件 -> 實現一個圖片非同步載入 Demo。最終 Demo 如下:

Android 訊息處理機制剖析訊息處理模型

我們不妨先想想,一個訊息處理機制需要什嗎?當然是:

  • 訊息源
  • 訊息佇列
  • 訊息處理器
  • 訊息管理器

其中訊息管理器又將劃分為三個子模組:訊息擷取、訊息分發、訊息迴圈。我們先不管 Android 內部將如何?訊息處理機制(因為處理機制的抽象結構肯定是一樣的,只是具體實現不一樣),按照我們列出來的4大模組畫出一個簡單的訊息處理模型:

Android 訊息處理組件

現在我們已經知道訊息處理模型需要哪些組件了,那就去 Android SDK 裡面找相應的類吧~然後我們會發現下面四個類:

  • Message 類代表訊息
  • MessageQueue 類代表訊息佇列
  • Handler 代表訊息擷取、訊息處理
  • Looper 代表訊息迴圈、訊息分發

可能有人會懷疑我在吹nb,在騙大家,這個時候我只能選擇看源碼了……為了方便大家理解,我將從 Looper 類開始分析,因為 Looper 類在訊息處理機制中是個“承上啟下”的功能模組。

Looper

在解析 Looper 之前,不妨先來想想為什麼需要 Looper 吧。

我們在進行 Android 開發的時候,為了不阻塞主線程(UI 線程),常常需要另開一個線程完成一些操作,而這些操作有一些執行一次就完了,有一些可能需要執行幾次,幾十次,甚至只要程式進程存活就要不斷執行該操作。而普通線程通過 start() 方法只能執行相關動作一次,為了滿足多次執行的需求,於是有了 Looper。

那麼我們就進入 Looper 的源碼,看看 Looper 中有哪些成員吧:

public final class Looper {    private static final String TAG = "Looper";    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    private static Looper sMainLooper;  // guarded by Looper.class    final MessageQueue mQueue;    final Thread mThread;    private Printer mLogging;}

大家可以看到,Looper 的核心成員是一個訊息佇列,該Looper 對應的線程,ThreadLocal 對象,和一個主線程 Looper 的引用。我們根據 Looper 的使用流程來分析它們的作用:

要使用 Looper,就必須調用 Looper 的 prepare() 方法:

    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));    }

我們可以看到,調用 prepare 方法後會通過 ThreadLocal 的 set 方法建立一個 Looper 對象,而且一個線程只能建立一個 Looper,我們不妨看看 ThreadLocal 通過 set 方法對 Looper 對象幹了啥:

    public void set(T value) {        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values == null) {            values = initializeValues(currentThread);        }        values.put(this, value);    }

實際操作 Looper 對象的是 values() 方法返回對象

Values values(Thread current) {        return current.localValues;    }

values() 方法返回的對象是一個線程的內部變數,我們再進去看看會發現:在 Thread 類內部是這樣定義 localValues 的 - ThreadLocal.Values localValues。也就是說,set 方法實際完成的操作是,將 Looper 對象與線程綁定,並且該 Looper 對象只在該線程內有效,其他線程無法訪問該 Looper 對象。

執行完 prepare() 方法之後,我們就要調用 Looper 的 loop() 方法來實現迴圈了:

    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.recycleUnchecked();        }    }

方法有點長,但實際邏輯比較簡單:首先判斷 prepare() 方法是否被調用,以確保 Looper 和某個線程綁定(需要注意的是:預設情況下,建立的 Looper 對象都與主線程綁定),然後擷取對應線程的訊息佇列,之後就不斷迴圈讀取隊列中的訊息,如果隊列中沒有訊息時 Loop() 方法就會結束(一般不會出現這種情況),否則將訊息交給 msg.target 對象分發。

以上就是 Looper 的核心代碼了,通過分析我們可以瞭解到 Looper 與線程的關係,以及在訊息分發機制中所起的作用,

Message

既然我們在分析 Looper 源碼的最後提到了 Message 類,那麼我們就先來看看 Message 類~那麼 Message 類作為訊息的載體到底儲存了什麼,做了什麼呢?

public final class Message implements Parcelable {    public int what;    public int arg1;     public int arg2;    public Object obj;    public Messenger replyTo;    public int sendingUid = -1;    /*package*/ static final int FLAG_IN_USE = 1 << 0;    /** If set message is asynchronous */    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;    /** Flags to clear in the copyFrom method */    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;    /*package*/ int flags;    /*package*/ long when;    /*package*/ Bundle data;    /*package*/ Handler target;    /*package*/ Runnable callback;    // sometimes we store linked lists of these things    /*package*/ Message next;

現在我們可以知道,原來剛剛處理訊息的 target 就是 Handler 類的對象啦。在 Message 類裡,what 用於辨識 Message 的用途,arg1 和 arg2 用於傳遞一些簡單的資料,obj 用於傳遞對象,data 用於傳遞複雜資料。

Message 類的方法我覺得是沒啥好說的,基本上都是 get/set 方法,當然還有回收方法,例如在 Looper 的 loop() 方法中,每一次迴圈結束都會執行 Message 的 recycleUnchecked() 方法,將被分發 Message 對象回收。

可能有人會奇怪,訊息如果沒有被處理就被回收了不會發生訊息丟失的情況嗎?莫慌,等會我會在分析 Handler 處理訊息的時候給大家解釋。

Handler

我們在前面的分析中看到,真正處理訊息的是 Handler 的 dispatchMessage() 方法,那麼我們就從這個方法入手分析 Handler 吧:

    public void dispatchMessage(Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }

在 dispatchMessage() 方法中,如果 msg 的 callback 不為 null 會調用 Handler 的 handleMessage() 方法處理訊息。也就是說,只要訊息的 callback 不為 null,就會調用 handleCallback() 方法,那麼未處理的訊息會不會回到訊息佇列呢?

    private static void handleCallback(Message message) {        message.callback.run();    }

看到這裡有沒有恍然大悟的感覺呢?剛剛我們在分析 Message 源碼的時候已經知道,callback 就是 Runnable 介面的執行個體,也就是說,如果訊息沒有被處理,就會回到訊息佇列中啦。那麼 Handler 又是怎樣處理訊息的呢?

    public void handleMessage(Message msg) {    }

竟然是個空方法……不過也很正常,因為 Handler 類只需要提供抽象,具體的處理邏輯應該由開發人員決定嘛。那我們分析就到此為止了嗎?才沒有!我們還沒有剖析 Handler 能夠實現非同步事件處理的原因呢,回到 Handler 的源碼,我們會看到下面這個程式碼片段:

    final MessageQueue mQueue;    final Looper mLooper;    final Callback mCallback;    final boolean mAsynchronous;    IMessenger mMessenger;

我靠……Handler 裡面居然擁有訊息佇列、Looper、非同步標誌位,我們回想一下剛剛分析得到過什麼結論:一個 Looper 只能屬於一個線程,Looper 有對應線程的訊息佇列。我們再來看看 Handler 的構造方法,隨便挑一個吧:

    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;    }

我們可以看到,Handler 內的訊息佇列就是 Looper 裡的訊息佇列,也就是說 Handler 能夠與任何一個線程的訊息佇列進行通訊,並處理其中的訊息,或者發送資訊到其他線程的訊息佇列中!

非同步處理 Demo

完成上面的分析以後,我們就知道在 Android 中的訊息處理機制了,那麼現在就來實現一個非同步處理 Demo吧。Demo 非常簡單,就是非同步下載一張圖片,並把它顯示到一個 ImageView 中,代碼比較多,就只給出核心代碼,下面有:

public class DownloadTask implements Runnable{…………        private void updateMsg(Message msg){        Bundle data = new Bundle();        Bitmap img = downImage(url);        data.putString("url", url);        data.putParcelable("img", img);        msg.setData(data);    }    public Bitmap downImage(String url) {        Bitmap img = null;        try {            URL mUrl = new URL(url);            HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection();            conn.setDoInput(true);            conn.connect();            InputStream is = conn.getInputStream();            img = BitmapFactory.decodeStream(is);        } catch (IOException e) {            Log.d(TAG, "downloadImg-Exception");        }        return img;    }}

DownloadTask 類負責處理下載任務,下載開始下載任務,下載任務完成後將圖片地址和圖片存到 msg 裡邊,發送給 DownloadHandler :

public class DownloadHandler extends Handler{……    @Override    public void handleMessage(Message msg) {        String url = msg.getData().getString("url");        Bitmap img = msg.getData().getParcelable("img");        Log.d(TAG, url);        loader.iLoader.update(img);    }}

最後通過 ILoader 介面更新外部 UI:

    public interface ILoader {        public void update(Bitmap img);    }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.