無限恐怖之Looper蛤虐!

1.前言

故事要從遠(yuǎn)古時(shí)代的Java說起。

首先隨便寫一個(gè)簡單的Java程序肝陪,編譯后運(yùn)行驳庭,程序“嗖”的一下執(zhí)行完畢,接著就退出運(yùn)行。這時(shí)候饲常,如果我們?cè)诖a中添加一個(gè)無限循環(huán)的函數(shù)蹲堂,再次編譯運(yùn)行,會(huì)發(fā)生什么事呢贝淤?程序會(huì)一直執(zhí)行著直到天荒地老柒竞,那顆象征著程序運(yùn)行的紅燈將永遠(yuǎn)亮著。

現(xiàn)在回到Android程序中播聪,隨便寫一個(gè)簡單的程序朽基,編譯后運(yùn)行,哇离陶,那顆象征著程序運(yùn)行的紅燈一直在亮啊踩晶。

眾所周知,ActivityThread的main()方法是應(yīng)用程序的入口函數(shù)枕磁。所以在這個(gè)方法里一定存在著一個(gè)死循環(huán):

 public static void main(String[] args) {
        ...

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        ...
        Looper.loop();

        ...
    }

從名字上就可以看出渡蜻, Looper就是你沒跑了!

不過問題又來了计济,既然是死循環(huán)茸苇,那為什么我的手機(jī)屏幕還能響應(yīng)點(diǎn)擊事件,還能執(zhí)行各種各樣的操作呢沦寂?

這就是Looper的神奇之處了学密,下面聽我緩緩道來。

2.Looper與它的朋友們

先總體的介紹下這家人的情況

每一個(gè)主線程都綁定了一個(gè)Looper循環(huán)器传藏,每一個(gè)Looper循環(huán)器又擁有一個(gè)MessageQueue消息隊(duì)列腻暮,Looper做的就是不停的從MessageQueue中拿出Message并交給主線程進(jìn)行處理。

因此如果需要讓主線程執(zhí)行操作的話毯侦,只要向MessageQueue發(fā)送Message即可哭靖。

發(fā)送Message就需要Handler了。Handler分系統(tǒng)與客戶端兩種侈离,多個(gè)Handler可以向同一個(gè)MessageQueue發(fā)送Message试幽。同時(shí),Handler也負(fù)責(zé)Message的處理卦碾,即Handler會(huì)在主線程執(zhí)行具體的操作铺坞。

2.1系統(tǒng)級(jí)!H

在ActivityThread中有這樣一個(gè)餓加載

 final H mH = new H();

點(diǎn)進(jìn)去看看洲胖,發(fā)現(xiàn)這是繼承自Handler的一個(gè)類济榨,其本身長得不得了。找到他的handleMessage()方法绿映,里面有一萬個(gè)對(duì)消息類型的判斷擒滑。

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

                ...省略剩余9999個(gè)...
}

仔細(xì)閱讀這些類型,發(fā)現(xiàn)其大多數(shù)與四大組件的生命周期有關(guān)。也就是說橘忱,系統(tǒng)級(jí)Handler H負(fù)責(zé)了四大組件生命周期的操作,還包括一些進(jìn)程間通信卸奉、應(yīng)用的退出等等钝诚,總之功能強(qiáng)大。

這里有必要提一下應(yīng)用退出:

  case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;

其實(shí)應(yīng)用退出的邏輯與代碼都很簡單榄棵,就是粗暴的退出了消息循環(huán)凝颇。此時(shí)循環(huán)已結(jié)束,程序就運(yùn)行完了疹鳄,相應(yīng)的進(jìn)程也會(huì)被虛擬機(jī)回收拧略。所以說,整個(gè)應(yīng)用都說結(jié)束就結(jié)束了瘪弓,Activity的onDestory是不能保證一定會(huì)被執(zhí)行的垫蛆!Service的一些回調(diào)也是同樣的道理,千萬別在這些地方做什么不可描述的操作腺怯!

2.2Message

2.2.1 Message的獲取

下面介紹Message袱饭,先來看看如何獲取一個(gè)Message:

 public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

這是一個(gè)典型的獲取鏈表對(duì)象的操作。開始sPool指向第一個(gè)message m呛占, 接著讓sPool指向m的next虑乖,然后將m.next賦空(為了將m拿出來),最后返回m晾虑。還不能理解的話只能看我隨手畫的圖了疹味。

鏈表操作流程

2.2.2 Message的回收

接著看看消息的回收,也是同樣的道理帜篇,這里就不再多做解釋(不想畫圖)

 void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
       ...

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

看到這個(gè)方法糙捺,有人也許會(huì)好奇,哪來的笙隙?我咋從來沒用過呢继找?
當(dāng)然啦,回收的操作Google工程師已經(jīng)給安頓好了逃沿,當(dāng)Handler處理完成以后婴渡,Message會(huì)自動(dòng)執(zhí)行回收操作。

在下面的loop()方法中凯亮,先來看注釋五msg.recycleUnchecked()边臼,message的回收操作就是在這里自動(dòng)進(jìn)行的,在loop()的死循環(huán)過程中假消,首先是通過queue.next()獲取msg柠并,接著通過dispatchMessage()讓相應(yīng)的handler進(jìn)行消息的處理,最后調(diào)用msg.recycleUnchecked()完成消息的回收。

2.3Looper

Looper中loop()方法的內(nèi)容非常豐富臼予,且慢慢學(xué)習(xí)鸣戴。

 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;

        ...
//注釋三
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
         ...
            try {
//注釋四
                msg.target.dispatchMessage(msg);
            } 
         ...
//注釋五
            msg.recycleUnchecked();
        }
    }

2.3.1 Looper與ThreadLocal的故事

注釋一,獲取looper對(duì)象是通過myLooper()這個(gè)方法

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

進(jìn)去一看粘拾,發(fā)現(xiàn)這里有一個(gè)叫做sThreadLocal的東西窄锅,去查一查他的三代

// sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

跟著上面的注釋,找到prepare()方法缰雇。類似的還有一個(gè)叫prepareMainLooper()的家伙入偷,區(qū)別是后者會(huì)將當(dāng)前線程作為主線程,在ActivityThread的main()方法中會(huì)調(diào)用后者械哟。

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

從上面的set疏之、get可以大致猜測(cè)出,sThreadLocal是一個(gè)類似于map的數(shù)據(jù)結(jié)構(gòu)暇咆,它的作用就是將looper與thread進(jìn)行綁定锋爪。來看看具體的set()方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

果然這里出現(xiàn)了ThreadLocalMap ,map對(duì)象是通過getMap(t)獲取的爸业。

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

也就是說几缭,每個(gè)Thread中都有一個(gè)ThreadLocalMap ,在set()中沃呢,首先獲取當(dāng)前Thread年栓,在從Thread中獲取其ThreadLocalMap,如果為空則創(chuàng)建薄霜,最后向ThreadLocalMap中插入鍵值對(duì)某抓,鍵是當(dāng)前的ThreadLocal對(duì)象,值就是通過參數(shù)傳入的新建的Looper對(duì)象惰瓜。要注意否副,每新建一個(gè)Looper對(duì)象,就會(huì)在其內(nèi)部新建一個(gè)ThreadLocal與之對(duì)應(yīng)崎坊!

這樣做的意義是什么呢备禀?是將Looper與Thread綁定,使得每一個(gè)Thread都有且只有一個(gè)Looper與之對(duì)應(yīng)奈揍,借此來解線程安全的問題曲尸。

2.3.2 Looper與其他人的故事

注釋二,從Looper對(duì)象中獲取了MessageQueue男翰,這里驗(yàn)證了之前所說的另患,每一個(gè)Looper循環(huán)器又擁有一個(gè)MessageQueue消息隊(duì)列。

注釋三蛾绎,各單位注意昆箕!這里就是Android程序真正進(jìn)行死循環(huán)的地方鸦列!這個(gè)死循環(huán)寫的很死,只能從內(nèi)部通過return語句打破循環(huán)鹏倘。比如當(dāng)queue中的msg為null時(shí)薯嗤,就可以結(jié)束循環(huán)退出程序了。

2.4Handler

2.4.1 Handler處理消息的三種方式

注釋四纤泵,msg.target是封裝在message中的handler對(duì)象骆姐,來看看handler的dispatchMessage()方法:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

handler一共有三種處理message的方式
第一種方式中,出現(xiàn)了一個(gè)叫msg.callback的玩意兒夕吻,這是一個(gè)runnable對(duì)象


 /*package*/ Runnable callback;

message是handler發(fā)送的诲锹,所以說這個(gè)runnable應(yīng)該也是handler傳進(jìn)來的繁仁。還記得handler中有一個(gè)post()方法嗎

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

這兩塊代碼是不是特別簡潔明了涉馅,根本不需要多余的解釋。
再來看看具體執(zhí)行調(diào)用的handleCallback(msg)

private static void handleCallback(Message message) {
        message.callback.run();
    }

哇黄虱!寫的不能再清楚了稚矿!
這種情況下,我們不需要handler來執(zhí)行具體的處理捻浦,而是回調(diào)了runnable對(duì)象的run方法來做些特別的事情晤揣。一般可以在這里執(zhí)行一些循環(huán)調(diào)用的操作。

第二種方式朱灿,就是通過handler中自帶的mCallback來處理message昧识。這個(gè)mCallback是在handler初始化的時(shí)候調(diào)用的

 public Handler(Callback callback, boolean async) {
    ...
        mCallback = callback;
    ...

如果在創(chuàng)建handler的時(shí)候沒有指定,那就會(huì)執(zhí)行第三種盗扒,也就是最常用的方式跪楞,直接通過重寫handleMessage(msg)來完成對(duì)消息的處理。

2.4.2 Handler發(fā)送消息

上面講了handler處理消息的三種方式侣灶,現(xiàn)在就順便理一理其發(fā)送消息的過程甸祭。
發(fā)送message從sendMessage()開始經(jīng)歷一系列簡單的回調(diào),這里有個(gè)關(guān)于時(shí)間的參數(shù)需要格外注意

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

queue.enqueueMessage(msg, uptimeMillis)又會(huì)涉及到鏈表的操作褥影,uptimeMillis決定了message開始進(jìn)行處理的時(shí)間池户。

boolean enqueueMessage(Message msg, long when) {
  ...
            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;
            }

  ...
    }

p是頭指針,首先判斷p是否存在凡怎,如果不存在校焦,則讓傳進(jìn)來的message成為頭指針;否則的話進(jìn)入循環(huán)统倒,如果當(dāng)前message的開始執(zhí)行時(shí)間小于鏈表中p的開始執(zhí)行時(shí)間斟湃,就將message插入到p的前面,否則的話再將message插入到鏈表的最后檐薯。

既然發(fā)送消息涉及到了開始執(zhí)行時(shí)間凝赛,那么取出消息一定也和這個(gè)參數(shù)有關(guān)注暗。

MessageQueue.next()方法并不只是簡單的根據(jù)進(jìn)入消息隊(duì)列的順序來取出message,而是還要考慮到他們具體執(zhí)行的時(shí)間墓猎。如果當(dāng)前時(shí)間小于mesaage開始執(zhí)行的時(shí)間捆昏,那就繼續(xù)獲取下一個(gè)message。

 if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }

2.4.3 Handler與Looper是怎么勾搭起來的

看到這里毙沾,認(rèn)真閱讀的同學(xué)一定有一個(gè)疑問骗卜,既然MessageQueue與Looper綁定,Looper又與Thread綁定左胞,那么Handler是怎么與Looper進(jìn)行綁定的呢寇仓?換句話說,Handler怎么知道自己要將消息發(fā)給哪個(gè)Looper對(duì)象中的MessageQueue烤宙,而這個(gè)Looper從MessageQueue中取出消息之后遍烦,又怎么知道要讓哪一個(gè)Handler來進(jìn)行處理呢?

我們來看Handler的構(gòu)造方法

public Handler() {
        this(null, false);
    }

其重載了下面這個(gè)兩個(gè)參數(shù)的構(gòu)造方法

public Handler(Callback callback, boolean async) {
       ...
        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;
    }

可見躺枕,Looper對(duì)象是通過靜態(tài)方法myLooper()獲取到的服猪。

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

文章在注釋一處就介紹了這個(gè)方法,如果沒什么印象了要回去再看一遍拐云,前面講了如何set罢猪,這里就涉及到如何get,現(xiàn)在深入進(jìn)去看看

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

看明白了吧叉瘩!Handler在初始化的時(shí)候膳帕,會(huì)從當(dāng)前線程獲取到ThreadLocalMap,由于一般我們都是在主線程初始化Handler薇缅,而主線程在創(chuàng)建的時(shí)候已經(jīng)通過Looper.prepareMainLooper()將ThreadLocal與Looper以鍵值對(duì)的形式set()進(jìn)ThreadLocalMap危彩,所以此時(shí)就可以輕松的get()到Looper對(duì)象了!

2.4.4 Handler與Looper是怎么在子線程勾搭起來的

好事者這時(shí)候又會(huì)問了捅暴,那如果我要在子線程中使用Handler呢恬砂?別急,我們回到Looper的源碼蓬痒,讀一讀最上面的官方注釋:

<pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>

這下通暢了吧泻骤!在子線程中,首先通過Looper.prepare()將Looper與Thread綁定梧奢,接著創(chuàng)建的Handler就與Looper對(duì)應(yīng)了起來狱掂,最后就可以通過Looper.loop()開啟循環(huán)。這個(gè)步驟和ActivityThread的main()方法一模一樣亲轨,只是前者由系統(tǒng)代勞趋惨,而在子線程中則需要我們自己動(dòng)手。

2.5總結(jié)

沒有總結(jié)

完結(jié)撒花~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惦蚊,一起剝皮案震驚了整個(gè)濱河市器虾,隨后出現(xiàn)的幾起案子讯嫂,更是在濱河造成了極大的恐慌,老刑警劉巖兆沙,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欧芽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡葛圃,警方通過查閱死者的電腦和手機(jī)千扔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來库正,“玉大人曲楚,你說我怎么就攤上這事∪旆” “怎么了龙誊?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長属瓣。 經(jīng)常有香客問我载迄,道長讯柔,這世上最難降的妖魔是什么抡蛙? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮魂迄,結(jié)果婚禮上粗截,老公的妹妹穿的比我還像新娘。我一直安慰自己捣炬,他們只是感情好熊昌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著湿酸,像睡著了一般婿屹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上推溃,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天昂利,我揣著相機(jī)與錄音,去河邊找鬼铁坎。 笑死蜂奸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的硬萍。 我是一名探鬼主播扩所,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼朴乖!你這毒婦竟也來了祖屏?” 一聲冷哼從身側(cè)響起助赞,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袁勺,沒想到半個(gè)月后嫉拐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魁兼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年婉徘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咐汞。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盖呼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出化撕,到底是詐尸還是另有隱情几晤,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布植阴,位于F島的核電站蟹瘾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掠手。R本人自食惡果不足惜憾朴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喷鸽。 院中可真熱鬧众雷,春花似錦、人聲如沸做祝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽混槐。三九已至编兄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間声登,已是汗流浹背狠鸳。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捌刮,地道東北人碰煌。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像绅作,于是被迫代替她去往敵國和親芦圾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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