標籤:android style blog io ar color 使用 sp for
一、 在MainActivity中為什麼只是類似的寫一行如下代碼就可以使用handler了呢?
Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // handle the nsg message... }}; private void sendMessage() { handler.sendEmptyMessage(11);}
開啟handler的源碼可以在它的建構函式中,看到如下的幾行代碼:
mLooper = Looper.myLooper(); // (1)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;
如果繼續跟進 (1) 號注釋中的 Looper.myLooper(); 這條語句,近兒,可以看到下面的代碼:
public static Looper myLooper() { return sThreadLocal.get(); // (2) }
其中 sThreadLocal 在Looper 類中被聲明為:
static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>();
可得它是一個靜態不可變的索引值,因為它是靜態成員,由類的載入過程可知在 Looper 被載入後就立即對該對象進行了初始化,而且它被聲明為final類型,在接下來的生命周期中將是一個不可改變的引用,這也是稱呼它為一個索引值一個原因;當然,當你繼續跟進時會發現,稱呼它為索引值原來是有更好的理由的。跟進(2)號注釋中的sThreadLocal.get(); 語句,可以得到下面的邏輯:
public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); // (3) Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); // (4) }
我們只關註上面的代碼中的 注釋(3)和 (4)兩行代碼,其中從注釋 (3)處我們得知這裡的讀取操作是從當前線程的內部獲得資料的,而注釋(4)則進一步告訴我們,它是一個以 ThreadLocal 類型的對象為索引值,也就是Looper中的 static final 存取控制的 sThreadLocal 對象。奇妙的地方就在這裡,因為每次調用時 ThreadLocal 雖然都是同一個 sThreadLocal 對象,但在ThreadLocal 內部它是從當前正在活動的線程中取出 Looper 對象的,所以達到了一種不同的線程調用同一個 Looper 中的同一個 sThreadLocal 對象的 get 方法,而得到的 Looper是不一樣的目的。並且從注釋(1)處可以得知,當我們調用 sThreadLocal.get();如果返回是一個null時,我們的調用將是失敗的,也就是在從當前正在活動的線程的內部讀取時還沒有初始化,拋出的異常提醒我們先執行ooper.prepare()方法,跟進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)); }
可得知,它同樣是通過sThreadLocal對象來存放資料的,跟進一步:
public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
從上面的代碼可得知,每個線程至多隻能存放一個Looper對象,注意這裡強調的是每個線程只能存放一份,並不是sThreadLocal 只能放一個,這也是執行緒區域化儲存的秘密所在。所以我們得到一個前提條件,在調用類似new Handler(){};語句時,必須事先已執行 Looper.prepare(); 語句,而且要確保兩條語句都是在同一個線程中被調用的,不然可能得不到我們期望的結果。但是為什麼我們在 MainAcitivity 中沒有顯示調用 Looper.prepare(); 方法,而只是簡單調用 new Handler(); 語句呢?原來在 MainActivity運行所在的主線程(也成UI線程,getId()得到的id號是1)被android系統啟動時,就主動調用了一次Looper.prepare()方法:
public static final void More ...main(String[] args) { SamplingProfilerIntegration.start(); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); (跟進該方法) ActivityThread thread = new ActivityThread(); thread.attach(false); Looper.loop(); if (Process.supportsProcesses()) { throw new RuntimeException("Main thread loop unexpectedly exited"); } thread.detach(); String name = (thread.mInitialApplication != null) ? thread.mInitialApplication.getPackageName() : "<unknown>"; Log.i(TAG, "Main thread of " + name + " is now exiting"); }}
二 、 Handler中是什麼原理,使得發送的訊息時只要使用handler對象,而在訊息被接受並處理時就可以直接調用到
handler中覆寫的
handleMessge方法?
就從下面的這行代碼開始:
private void sendMessage() { handler.sendEmptyMessage(0x001); }
在handler類中一直跟進該方法,可以發現下面的幾行代碼:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; //(5) if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis);}
在上面的注釋 (5) 處可以看到,這裡對每個msg中的target欄位進行了設定,這裡就是設定了每個訊息將來被處理時用到的handler對象。
可以跟進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); // (6) 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(); } }
在注釋 (6)處可以看到, msg.target.dispatchMessage(msg); 原來是調用的Message的 dispatchMessage()方法,不妨我們跟進去看看:
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); //(7) } }
一系列優先考慮的回調判斷之後,終於輪到了自訂的處理的函數處理。
三、遇到This Handler class should be static or leaks might occur 警告 為什嗎?
大致意思是說:Handler 類應該為static類型,否則有可能造成泄露。在程式訊息佇列中排隊的訊息保持了對目標Handler類的引用。如果Handler是個內部類,那 麼它也會保持它所在的外部類的引用。為了避免泄露這個外部類,應該將Handler聲明為static嵌套類,並且使用對外部類的弱應用。說的很明白,因為內部類對外部類有個引用,所以當我們在內部類中儲存對外部類的弱引用時,這裡的內部類對外引用還是存在的,這是jdk實現的,而只有我們聲明為靜態內部類,此時是對外部類沒有強引用的(這時它和普通的外部類沒有什麼區別,只是在存取控制上有很大的方便性,它可以直接存取外部類的私人成員),這樣當線程中還有 Looper 和 Looper 的 MessageQueue 還有 Message 時( message 中的 target 保持了對 Handler 的引用),而此時 Activity已經死亡時,就可以對Activity 進行回收了。
四、注意事項
1. 在自己定義的子線程中如果想實現handler?
Looper.prepare();handler=new Handler();...Looper.loop();
這幾行代碼一定要在run方法中調用,而不是在自訂的 Thread 子類的建構函式中調用。因為子線程的構造方法被調用時,其實子線程還沒有執行,也就是說當前的線程並不是子線程,還仍然是父線程,所以調用 Looper.prepare(); 也只是對父線程進行了執行緒區域化儲存了一個 Looper 對象。
2.
簡而言之
兩個線程之間用 handler 進行通訊,一個線程先線上程本地存放一個Looper對象,該對象中有訊息佇列 MessageQueue 成員,只是這個Looper對象,是線上程運行Looper.prepare() 時初始化的,且保證只初始化一次,進而該線程進入Loop.loop()訊息迴圈狀態,等待其它線程調用自己的 hanldler 對象的 sendMessage() 方法往自己的訊息佇列中存放訊息,並在有訊息時讀取並處理,沒有則阻塞。
android 中的 Handler 線程間通訊