1. 程式人生 > >【從原始碼看Android】01從Looper說起

【從原始碼看Android】01從Looper說起

1 為什麼以這一個點為開頭?

因為面試的時候被問到ThreadLocal完全不懂,前幾天發現Looper內正好使用了ThreadLocal,那麼從哪裡跌倒就從哪裡爬起來。

2 什麼是Looper

首先看/sdk/docs/reference/android/os/Looper.html內的定義

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

Looper類用於在一個執行緒中執行一個訊息迴圈。預設的執行緒不包含訊息迴圈。

如果需要建立一個Looper,在Thread的run函式中呼叫Looper.prepare()

接著呼叫Thread.loop()函式,直到訊息佇列停止。

簡單的說,Looper就是一個訊息迴圈,在一個執行緒中不停的去訊息佇列裡poll新訊息出來給Handler處理,

3 如何建立Looper

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;


public class MainActivity extends ActionBarActivity {

    private static final String TAG = "MainActivity";

    private class LooperThread extends Thread
    {
        public Handler mHandler;
        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg)
                {
                    Log.i(TAG,msg.toString());
                }
            };
            Looper.loop();
        }
    }

    private LooperThread mLooperThread = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLooperThread = new LooperThread();
        mLooperThread.start();
    }

}

這是reference中給出的子執行緒中Looper的構建方法

a 建立新執行緒,重寫run函式

b 呼叫Looper.prepare()

c 建立Handler

d 呼叫Looper.loop()

我們下面分4部分解讀原始碼

3.1 Looper概覽

- 成員變數

public final class Looper {
    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    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擁有6個成員變數,其中mLogging和Tag先忽略

sMainLooper、ThreadLocal為static成員

mQueue、mThread為例項的成員變數


- 成員函式
Public Methods
()Returns the application's main looper, which lives in the main thread of the application.
()Return the Thread associated with this Looper.
static void loop()Run the message queue in this thread.
()Return the Looper object associated with the current thread.
()Return the  object associated with the current thread.
static void ()Initialize the current thread as a looper.
static void ()Initialize the current thread as a looper, marking it as an application's main looper.
void quit()Quits the looper.
void ()Quits the looper safely.
void (Printer printer)Control logging of messages as they are processed by this Looper.
()Returns a string containing a concise, human-readable description of this object.

3.2 呼叫Looper.prepare()

我們先看看Looper.prepare()定義

public static void prepare() {
        prepare(true);
    }

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函式可以每一個Thread只能對應一個Looper,不然會報RuntimeException

而prepare函式所做的事情只是在全域性的sThreadLocal中存放了個新的Looper

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
new Looper時賦值了mQueue和mThread

既然sThreadLocal是個全域性的靜態變數,那麼所有的Looper類都共享同一個sThreadLocal

sThreadLocal.set操作定義如下

java.lang.ThreadLocal.java

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
即從當前呼叫執行緒中取出values物件,然後往這個values物件存放這個Looper

需要注意的是每個Thread中都有一個values物件,

這個values物件再按照ThreadLocal<Looper> sThreadLocal物件在當前執行緒的values雜湊表中找出對應的Looper

那麼這個Looper就對應為當前執行緒的Looper

那麼使用ThreadLocal有什麼好處呢?

好處是顯而易見的,如果用全域性的HashMap管理一個Thread對應一個Looper,

那麼增刪改某個Looper物件時就需要進行同步操作,這大大增加了系統開銷

而如果有一個ThreadLocal.Values物件存放在Thread裡,需要用到時就直接獲取,不與其他執行緒的資料進行互動,
那麼就避免了同步帶來的低效率問題,所以這個ThreadLocal正好被應用到了一個Thread對應一個Looper中

3.3 建立Handler

android.os.Handler.java

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

因篇(lan)幅(de)限(xie)制(le),Handler只提到1個函式,在建立Handler時無引數代入,即呼叫到Handler(null,false)

在下面一個函式中handler進行了一些初始化的賦值,最重要的一步是賦值了mLooper和mQueue

Looper.myLooper()函式定義如下

public static Looper myLooper() {
        return sThreadLocal.get();
}

即從sThreadLocal中獲取了當前Thread中得looper,賦值給了Handler,

這也就解釋了為什麼需要先Looper.prepare()

再new Handler的原因了

3.4 呼叫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.recycle();
        }
}
因為是靜態函式,需要先獲得當前執行緒的looper例項,

接著呼叫Binder.clearCallingIdentity(),這個函式返回的是當前程序的pid、uid等資訊混合後的唯一標示

接著一個for不斷迴圈獲取訊息佇列中得下一條訊息,

如果呼叫的queue.next返回的msg為null則立即結束此執行緒

如果不為null則呼叫msg.target.dispatchMessage(此target為handler例項,即在當前執行緒呼叫到了handler的handlerMessage)

接著再獲取一次Binder.clearCallingIdentity(),判斷前後兩次的唯一標示是否相同

我很奇怪既然在同一個執行緒為什麼會需要這樣的判斷,後來檢視Log.wtf定義發現註釋如下:

What a Terrible Failure: Report a condition that should never happen.

也就是永遠不會發生的情況發生了做一個記錄,

因為uid是安裝完app就已經生成了,pid是在程式跑起來時也生成了,如果這兩個其中一個改變,

說明app被kill後重啟或者重新安裝,既然被kill後就不應該存在這個handler,

所以理論上應該不會發生這個情況

最後呼叫msg.recycle()對msg進行一個重置回收

我們來看看

android.os.Message.java的recycle函式實現

public void recycle() {
        clearForRecycle();

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
}
正如程式碼中所示,首先把所有訊息內容清空,然後放入物件池提供給其他物件使用

4 最佳實踐

既然上面的Message使用了物件池,

那麼我們在使用Message msg = new Message()就太瞎了

在需要使用新的Message時,使用Message.obtain()或者Handler.obtainMessage()是個不錯的選擇!

5 總結

本文只是簡單的敘述了Looper的構建過程,但其實內部中mQueue.next()是誇執行緒呼叫的最重要體現,mQueue是如何把其他執行緒上的msg dispatch給當前執行緒的Handler,我將在下一講中給出解釋,順便具體學習下MessageQueue,也一定會講到MessageQueue cpp層的一部分原始碼。