Handler 相關(guān)面試題

1.一個線程可以有多個Handler嗎斜友?

答:可以。例如垃它,Android 的 每一個Activity都可以創(chuàng)建Handler對象鲜屏,但它們都是運(yùn)行在同一個主線程中的。

2. 一個線程有幾個Looper国拇?如何保證的墙歪?

答:一個線程只有一個Looper對象。

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

從Looper的源碼得知贝奇,在looper的prepare()函數(shù)中虹菲,其通過threadlocal來獲取looper的對象。

 /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

上面為threadlocal的get()函數(shù)掉瞳”显矗可以看出threadlocal的內(nèi)部維護(hù)了一個ThreadLocalMap類。該類是以當(dāng)前thread做為key的陕习。因此可以得知霎褐,一個線程最多只能有一個looper對象。

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

以上為threadlocal的set方法该镣。

3.Handler內(nèi)存泄露的原因冻璃?為什么其他的內(nèi)部類沒有說有這個問題?

1.所有的非靜態(tài)內(nèi)部類都會持有外部類的引用损合。當(dāng)外部類需要銷毀而內(nèi)部類仍在運(yùn)行時省艳,java的GC機(jī)制將導(dǎo)致外部類的引用不會被銷毀,從而導(dǎo)致內(nèi)存泄露嫁审。所以所有的非靜態(tài)內(nèi)部類都可能會導(dǎo)致內(nèi)存泄露跋炕。
2.MessageQueue存儲了Message,而Message的target屬性為handler對象律适,handler又持有的Activity等context的對象辐烂,這就導(dǎo)致了內(nèi)存泄露遏插。

 public static void loop() {
//省略代碼
  try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
//省略代碼
}

以上為looper的loop方法。

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

以上為handler的enqueueMessage函數(shù)(所有的send和post方法最終都會調(diào)用該函數(shù))

4.怎么處理handler的內(nèi)存泄露纠修;

1.靜態(tài)內(nèi)部類+弱引用胳嘲;
2.針對與在子線程開啟的消息隊(duì)列,可以在需要結(jié)束的時候扣草,調(diào)用lopper的quitSafely()方法了牛。
上代碼:

  public void quit() {
        mQueue.quit(false);
    }

    /**
     * Quits the looper safely.
     * <p>
     * Causes the {@link #loop} method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * However pending delayed messages with due times in the future will not be
     * delivered before the loop terminates.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p>
     */
    public void quitSafely() {
        mQueue.quit(true);
    }

以上為looper的quit函數(shù)。

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

以上為MessageQueue的quit函數(shù)德召。在此函數(shù)里,消息隊(duì)列會移除隊(duì)列里的所有消息汽纤。此時MessageQueue將不會持有message,更不會間接的持有handler的引用上岗。從而消除handler的內(nèi)存泄露問題。
補(bǔ)充:
在子線程內(nèi)開啟消息隊(duì)列蕴坪,
1.先通過調(diào)用Looper.prepare()函數(shù)來創(chuàng)建looper對象肴掷;
2.創(chuàng)建handler對象;
3.調(diào)用looper.loop()函數(shù)來循環(huán)不斷的從messagequeue里獲取message背传;
4.結(jié)束消息隊(duì)列時呆瞻,調(diào)用looper.quit()函數(shù)來清空messagequeue里的消息,來防止內(nèi)存泄露径玖。

5.既然可以存在多個Handler往MessageQueue中添加數(shù)據(jù)(發(fā)送消息時痴脾,各個Handler可能處于不同的線程),它內(nèi)部是如何保持線程安全的梳星?

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

通過synchronized鎖機(jī)制保證線程安全

6.使用Handler的postDelay后消息隊(duì)列有什么變化赞赖?
消息隊(duì)列會重新排序。

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

handler的postDelay()函數(shù)最終會調(diào)用messagequeue的enqueueMessage()函數(shù)冤灾。
該函數(shù)里會按照執(zhí)行開始時間前域,對messagequeue里的message進(jìn)行重新排序。

7.Looper死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死韵吨?

1.導(dǎo)致應(yīng)用卡死的原因只有一下幾種情況:
a)輸入事件在5秒內(nèi)未響應(yīng)匿垄;
b)在主線程執(zhí)行耗時操作;
2.Looper.loop()函數(shù)會在死循環(huán)里不斷的去獲取messagequeue里的message归粉,當(dāng)消息隊(duì)列里沒有消息的時候椿疗,looper會進(jìn)入睡眠階段。Looper為了防止message執(zhí)行過于耗時的操作糠悼,導(dǎo)致隊(duì)列阻塞变丧,就給message設(shè)置一個ANR的狀態(tài)【铌可見痒蓬,looper的死循環(huán)和應(yīng)用卡死是兩個不同的概念童擎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市攻晒,隨后出現(xiàn)的幾起案子顾复,更是在濱河造成了極大的恐慌,老刑警劉巖鲁捏,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芯砸,死亡現(xiàn)場離奇詭異,居然都是意外死亡给梅,警方通過查閱死者的電腦和手機(jī)假丧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來动羽,“玉大人包帚,你說我怎么就攤上這事≡讼牛” “怎么了渴邦?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拘哨。 經(jīng)常有香客問我谋梭,道長,這世上最難降的妖魔是什么倦青? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任瓮床,我火速辦了婚禮,結(jié)果婚禮上产镐,老公的妹妹穿的比我還像新娘纤垂。我一直安慰自己,他們只是感情好磷账,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布峭沦。 她就那樣靜靜地躺著,像睡著了一般逃糟。 火紅的嫁衣襯著肌膚如雪吼鱼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天绰咽,我揣著相機(jī)與錄音菇肃,去河邊找鬼。 笑死取募,一個胖子當(dāng)著我的面吹牛琐谤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玩敏,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼斗忌,長吁一口氣:“原來是場噩夢啊……” “哼质礼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起织阳,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眶蕉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后唧躲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體造挽,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年弄痹,在試婚紗的時候發(fā)現(xiàn)自己被綠了饭入。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡肛真,死狀恐怖谐丢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毁欣,我是刑警寧澤钝吮,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布恐似,位于F島的核電站苗缩,受9級特大地震影響承二,放射性物質(zhì)發(fā)生泄漏莱找。R本人自食惡果不足惜皿淋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一帝洪、第九天 我趴在偏房一處隱蔽的房頂上張望哲身。 院中可真熱鬧纲酗,春花似錦衰腌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吮螺,卻和暖如春饶囚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸠补。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工萝风, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人紫岩。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓规惰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泉蝌。 傳聞我的和親對象是個殘疾皇子歇万,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355