Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android消息處理機制:源碼剖析Handler、Looper,並實現圖片異步加載

Android消息處理機制:源碼剖析Handler、Looper,並實現圖片異步加載

編輯:關於Android編程

引言

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

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

title=

Android 消息處理機制剖析

消息處理模型

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

消息源 消息隊列 消息處理器 消息管理器

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

title=

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 sThreadLocal = new ThreadLocal();
    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 與線程的關系,以及在消息分發機制中所起的作用,如圖:

title=

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

 

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