【從原始碼看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層的一部分原始碼。