Handler蔓钟,Looper,Thread卵贱,Message滥沫,MessageQueue

【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ù)組娘赴,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市跟啤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唉锌,老刑警劉巖隅肥,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異袄简,居然都是意外死亡腥放,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門绿语,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秃症,“玉大人,你說我怎么就攤上這事吕粹≈指蹋” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵匹耕,是天一觀的道長聚请。 經(jīng)常有香客問我,道長稳其,這世上最難降的妖魔是什么驶赏? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮既鞠,結(jié)果婚禮上煤傍,老公的妹妹穿的比我還像新娘。我一直安慰自己嘱蛋,他們只是感情好蚯姆,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布五续。 她就那樣靜靜地躺著,像睡著了一般蒋失。 火紅的嫁衣襯著肌膚如雪返帕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天篙挽,我揣著相機與錄音荆萤,去河邊找鬼。 笑死铣卡,一個胖子當著我的面吹牛链韭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播煮落,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼敞峭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝉仇?” 一聲冷哼從身側(cè)響起旋讹,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轿衔,沒想到半個月后沉迹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡害驹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年鞭呕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宛官。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡葫松,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出底洗,到底是詐尸還是另有隱情腋么,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布枷恕,位于F島的核電站党晋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏徐块。R本人自食惡果不足惜未玻,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胡控。 院中可真熱鬧扳剿,春花似錦、人聲如沸昼激。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞧掺,卻和暖如春耕餐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辟狈。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工肠缔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哼转。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓明未,卻偏偏與公主長得像,于是被迫代替她去往敵國和親壹蔓。 傳聞我的和親對象是個殘疾皇子趟妥,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容