Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之異步線程原理

Android之異步線程原理

編輯:關於Android編程

基礎介紹

異步消息處理線程是指,線程在啟動後會進入一個無線循環體中,沒循環一次,從內部的消息隊列中取出一個一個消息,並回調相應的消息處理函數,執行完一個消息後則繼續循環。如果消息隊列為空,線程會暫停,知道消息隊列中有新的消息。
異步消息處理線程本質上仍然是一個線程,只不過這種線程的執行代碼設置成如上所述的邏輯而已。在android中實現異步線程主要涉及到如下幾個類:ThreadLocal,Looper,MessageQueue,Handler,Message,接下來我們一一介紹這幾個類,解析Android的異步線程機制。
先上一張框架圖:
這裡寫圖片描述

ThreadLocal

ThreadLocal並不是Android的sdk中的類,而是java.lang中的一個類,該類的作用是為線程創建一個基於線程的變量存儲,我們可以稱之為線程局部存儲。ThreadLocal可以使對象達到線程隔離的目的,它為每一個線程維護自己的變量拷貝,通過其中的set方法將變量綁定到線程上。ThreadLocal提供了一種解決多線程同步的問題解決方案,通過為每一份變量進行拷貝,這樣的話,每個線程操作的都是屬於自己的變量,而不是共同的一個變量,因此也就不需要同步鎖了。舉個栗子,我們創建出一個變量,而這個變量會被兩個線程操作。一般情況下,我們會給這個變量加鎖,通過這種方式來解決同步的問題。但是當我們使用ThreaLoca時,我們就可以通過ThreadLoca來為每一個線程做一個拷貝,而且這個拷貝是跟線程綁定在一起的,也就是說每個線程可以更改自己的變量而不影響另外一個線程。這樣也就不需要鎖了。ThreadLocal在set時會自動綁定到當前的線程,而不需要自己去綁定。代碼這裡就不寫了,知道中心思想就行。有人可能會問,這跟Android有什麼關系呢。這就要說到我們的Looper了,因為在Android的異步線程中,ThreadLocal綁定的這個線程變量就是Looper的一個對象。

Looper

Looper有什麼用呢,要實現異步線程,必須要Looper,因為Looper是用來產生一個MessageQueue。我們可以通過查看源代碼知道,在Looper類中有一個成員變量mQueue(MessageQueue類的實例),該變量用於保存Looper中MessageQueue。
Looper通過靜態方法Looper.prepare()方法來創建出一個MessageQueue對象。注意,Looper.prepare()方法在一個線程中只能執行一次。

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

以上為android的源代碼,執行了兩次prepare時,就會拋出一個異常。這個其實很自然就可以想到,因為前面提到,Looper是用來創建MessageQueue的。一個線程只能有一個MessageQueue,因此自然只能有一個Looper對象。看完prepare()源代碼好像並沒有發現它創建出了MessageQueue的一個實例啊,對的,這裡是看不到,但是。我們來看看在創建一個新的Looper對象時所做的工作:
我們找到Looper的構造函數:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
    }

我們可以發現,Looper的這個構造函數是私有的,而且Looper只有這一個構造函數。所以我們可以看出我們不能在其他的類中new出一個Looper對象。但是,這不是重點,重點是Looper在這裡創建出了一個MessageQueue對象。

loop()方法

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

函數myLooper用來返回當前線程的Looper對象,通過一個for(;;)來執行循環。在for循環內部,通過MessageQueue的next方法來去取出其中的Message,這裡有一點需要注意,就是取出來的message,最後會調用msg.target.dispatchMessage(msg);來處理消息,該方法在Handler中,因為msg.target是一個Handler對象。這個放到Handler中說

MessageQueue

MessageQueue是用來處理消息隊列的。該類中有幾個方法,一個是next()方法,用於取出隊列中下一個元素的。另外一個是enqueueMessage方法,用於向消息隊裡中添加一個元素。該方法會在Handler中的sendMessage中使用到,這裡稍微提一下,在Handler的sendMessage中,會調用這個方法,將Message添加到接收線程的MessageQueue中。

Handler

Handler是用於發送信息的,我們熟知的方法就是其中的sendMessage方法了,用於向消息接收線程發送消息。上面的框架圖中有說到,Handler一定要在接收消息的線程中創建,只有這樣的話才可以給該線程發送消息。因為創建出的Handler對象handler必須要有該線程的MessageQueue消息隊列才可以給該線程發送消息。當Handler在創建時,會用到如下的構造函數:

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時, 會通過mLooper = Looper.myLooper();來獲取當前線程的Looper對象,而Looper對象又是獲取MessageQueue對象前提。Handler在調用handler.sendMessage(Message)發送message時,最終會調用到這個方法:

handler的sendMessage方法(最終會調用的方法)

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

在這裡可以看出,handler會獲得當前線程的MessageQueue對象。之後調用enqueueMessage(queue, msg, uptimeMillis);這個方法是Handler中的方法,而不是MessgaeQueue中的enqueueMessage方法,我們查看Handler中的enqueueMessage方法:

handler中的enqueueMessage

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到個方法體中的第一行代碼:msg.target = this;將當前發送的message.target對象設置成當前的handler。這個在Looper.loop()方法中,調用了 msg.target.dispatchMessage(msg);方法。這個很快就會介紹。先不說這個。可以看到,handler中的enqueueMessage方法會設置message的target為當前對象,之後會調用MessageQueue對象的enqueueMessage(該方法在MessageQueue中介紹過,是用於向消息隊列添加消息的方法)方法,將message添加到MessageQueue對象中。
好了,我 們現在來說一下handler的dispatchMessage(msg)方法。我們看一下這個方法的源代碼,該方法會在looper的loop方法中使用的,當獲取每個消息時,會調用方法 msg.target.dispatchMessage(msg);來處理每個消息。

dispatchMessage

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

該方法中先判斷消息本身是否有回調函數,有的話則調用消息的回調函數,如果沒有,再判斷創建這個Handler時有沒有傳遞Callback接口,注意,這個 接口的名字就是Callback,Handler可以通過構造函數來給這個mCallback賦值,如果這個mCallback傳遞了值的話 就會調用這個方法,否則調用Handler本身的handleMessage方法。這個方法在Handler中是空的,需要在繼承Handler的類中,重寫該方法。也就是說重寫的該方法實際上是優先級最低的。
後面兩種比較常見,但是前面一種,也就是消息本身攜帶了接口的一般通過Message.obtain(Handler h, Runnable callback)方法來設置。
好了 Android的異步線程說到這裡就拆不多了。

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