【1】Looper如何和Thread聯(lián)系起來?
答:以主線程為例解釋:
在ActivityThread類中的程序的入口键俱,即main方法兰绣,該方法中調(diào)用了:
Looper.prepareMainLooper();
Step:接下來我們解析Looper類中的該方法:
public static void prepareMainLooper() {
prepare(false); synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared."); }
sMainLooper = myLooper(); }
}
官方對該方法的解釋:
(1)將當前線程初始化為looper,將其標記為應用程序的主looper编振。
(2)應用程序的主looper是由Android環(huán)境創(chuàng)建的缀辩,所以你應該永遠不要自己調(diào)用該方法。
Step:接下來我們解析:
prepare(false);
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));}
官方對該方法的解釋:
(1)該方法讓你有機會創(chuàng)建handlers,然后引用這個looper臀玄,然后才開始真正循環(huán)(loop)瓢阴。
(2)調(diào)用此方法后一定要調(diào)用loop(),通過調(diào)用quit()來結(jié)束它。
Step:接下來我們解析:sThreadLocal.set(new Looper(quitAllowed));
(1)首先我們關(guān)注創(chuàng)建Looper對象:Looper looper = new Looper(quitAllowed)
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}
這里主要是:
(1)創(chuàng)建了MessageQueue的對象mQueue镐牺;
(2)創(chuàng)建了Thread的對象mThread;
Thread.currentThread()方法可以獲取到當前的線程炫掐,當應用程序啟動的時候,系統(tǒng)會為該應用程序創(chuàng)建一個線程睬涧,我們叫它主線程。
(2)其次我們關(guān)注Looper對象的存儲:sThreadLocal.set(looper)
我們看看sThreadLocal是什么東東旗唁?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
官方對ThreadLocal的解釋:
用來實現(xiàn)線程本地存儲畦浓,即每個線程的變量有自己對應的值。
所有的線程共享相同的ThreadLocal對象检疫,但是每個線程訪問它時都會看到不同的值讶请,并且有一個線程更改不會影響其他線程。
Step:接下來我們解析: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);}
官方對該方法的解釋:
為當前線程設(shè)置此變量的值屎媳。
這里不對ThreadLocal類進行深入的了解夺溢,到這里我們知道使用該類可以實現(xiàn)存儲Thread和Looper就可以了,類似于(key-value)烛谊。
因為Thread默認沒有與它關(guān)聯(lián)的消息循環(huán)风响,(Thread默認不能進行消息循環(huán))
要創(chuàng)建一個,我們在運行循環(huán)的線程中調(diào)用prepare(),
然后調(diào)用loop()方法讓他處理消息丹禀,
最后調(diào)用quit()方法推出循環(huán)
Looper最重要的方法以及順序:prepare() -> loop() ->quit()
實際開發(fā)中的使用:參考HandlerThread
【1】extends Thread
【2】Looper.prepare() //將Thread初始化為looper
【3】創(chuàng)建一個或者多個Handler状勤,用來處理message
【4】Looper.loop() //分發(fā)message,直到loop被告知quit()
【2】Handler如何和Thread聯(lián)系起來双泪?
主要從分析Handler源碼來解析:
Step:先看默認構(gòu)造函數(shù):
public Handler() {
this(null, false);}
官方對該構(gòu)造函數(shù)的解釋:
默認構(gòu)造函數(shù)將該Handler與當前線程的Looper關(guān)聯(lián)起來持搜。如果此線程沒有l(wèi)ooper,該Handler將無法接收Message,因此會拋出異常焙矛。
Step:再看看另一個構(gòu)造函數(shù):
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;}
官方對該構(gòu)造函數(shù)的解釋:
對于具有指定的回調(diào)接口的當前Thread使用Looper葫盼,并設(shè)置該Handler是否應該是異步的祟昭。
Handler默認情況下是同步的署隘,除非此構(gòu)造函數(shù)用于創(chuàng)建嚴格異步的饰迹。
mLooper:在Looper中通過prepare()方法創(chuàng)建的再扭,這樣Handler就和Looper聯(lián)系起來了女仰,【同時和Thread聯(lián)系起來励背∶隼樱】
mQueue:Handler中的MessageQueue和Looper中的MessageQueue是一致的每界。
Step:接著看Handler的兩個主要用途:
【1】調(diào)度message 和 runnable 日缨,在未來的某個點執(zhí)行钱反。
【2】在與自己不同的線程上執(zhí)行某個事件。
調(diào)度消息可以通過如下方法完成,大致有兩類型:post 和 send
post方式允許在接收到消息時將Runnable對象入隊面哥;
send方式允許在接收到消息時將Message對象入隊哎壳;
Step:關(guān)于post方式做如下說明:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);}
官方解釋:
將Runnable添加到MessageQueue.Runnable將在Handler所在的Thread中運行。
Step:再看看getPostMessage(r):
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); m.callback = r; return m;}
將Runnable用Message包裹起來尚卫,以Message的形式發(fā)送出去归榕。
記住:無論是post方式吱涉,還是send方式刹泄,最終都只調(diào)用一個方法:
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);}
官方解釋:
將Message置入MessageQueue中。(這里時間先不做解釋了)
Step:再看看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;這是非常重要的怎爵,因為Looper中的loop() 方法會用到它特石,即:msg.target.dispatchMessage(msg);
目的是要求發(fā)送Message的Handler和處理Message的Handler是一致的。
Step:接下來重點就是MessageQueue的enqueueMessage()方法鳖链,
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target."); }
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use."); }
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; }
msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else {
// Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) {
prev = p; p = p.next; if (p == null || when < p.when) {
break; }
if (needWake && p.isAsynchronous()) {
needWake = false; }
}
msg.next = p; // invariant: p == prev.next prev.next = msg; }
// We can assume mPtr != 0 because mQuitting is false. if (needWake) {
nativeWake(mPtr); }
}
return true;}
至此姆蘸,Message已經(jīng)放入MessageQueue中了;
Step:接下來就是從MessageQueue中取Message了芙委,這時候就需要發(fā)揮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 final Printer logging = me.mLogging; if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what); }
final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); }
try {
msg.target.dispatchMessage(msg); } finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag); }
}
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(); }
}
這里重點看:msg.target.dispatchMessage(msg)
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); } else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; }
}
handleMessage(msg); }
}
這里我們看到它分情況處理了,msg.callback是否為null灌侣。
如果msg.callback不為null,說明傳遞過來的是Runnable,然后進行handleCallback(msg)
private static void handleCallback(Message message) {
message.callback.run();}
如果msg.callback為null推捐,說明傳遞過來的是Message,然后進行handleMessage(msg)
public void handleMessage(Message msg) {
}
我們需要在run()方法和handleMessage()方法中寫自己的實現(xiàn)。
【3】通過對【1】和【2】的分析顶瞳,我發(fā)現(xiàn):
我們主要了解Thread玖姑,Looper,Handler它們?nèi)齻€之間的關(guān)系就可以了慨菱,Message和MessageQueue可以把它們當作媒介就可以焰络。
出場順序是這樣的:Thread -> Looper -> Handler
Thread創(chuàng)建后,就需要對Looper進行操作符喝,主要是執(zhí)行兩個方法:prepare(),loop(), 這是準備工作闪彼。
然后就是對Handler的使用,創(chuàng)建Handler對象协饲,并將其和Looper聯(lián)系起來畏腕,并和Thread聯(lián)系起來,
Handler就可以發(fā)揮它強大的功能茉稠。
【4】思考:
UI線程是如何創(chuàng)建的描馅?
應用啟動時,系統(tǒng)會為應用創(chuàng)建一個名為主線程的執(zhí)行線程而线。主線程負責將事件分派給相應的用戶界面小部件铭污,其中包括繪圖事件恋日。
此外,UI線程也是應用與Android UI工具包組件進行交互的線程嘹狞。
UI線程才能處理UI相關(guān)的操作岂膳,為什么?
答:Android UI工具包不是線程安全的磅网。因此谈截,單線程模型確保UI不能被不同的線程同時修改。
Android關(guān)于線程的詳細說明:https://developer.android.com/guide/components/processes-and-threads.html
【5】Handler的工作原理:
Handler創(chuàng)建時會采用當前線程的Looper來構(gòu)建內(nèi)部的消息循環(huán)系統(tǒng)涧偷。接下來看Handler的運行機制
【6】ThreadLocal:
在不同的線程中訪問的是同一個ThreadLocal對象簸喂,但是他們通過ThreadLocal獲取到的值是不同的。
why? ->不同線程訪問同一個ThreadLocal的get()方法燎潮,ThreadLocal內(nèi)部會從各自的線程中取出一個數(shù)組娘赴,