Android:遇到Handler中有Loop死循環(huán),還沒有阻塞主線程,這是為什么呢?大佬教你“一招”解決

image

面試官: Handler中有Loop死循環(huán),為什么沒有阻塞主線程,原理是什么
心理分析:該問題很難被考到,但是如果一旦問到,100%會回答不上來。開發(fā)者很難注意到一個主線程的四循環(huán)居然沒有阻塞住主線程
求職者:應(yīng)該從 主線程的消息循環(huán)機(jī)制 與Linux的循環(huán)異步等待作用講起。最后將handle引起的內(nèi)存泄漏,內(nèi)存泄漏一定是一個加分項

先上一份整理好的面試目錄

image
前言

Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制,對于大家來說Handler已經(jīng)是輕車熟路了,可是真的掌握了Handler?本文主要通過幾個問題圍繞著Handler展開深入并拓展的了解。

站在巨人的肩膀上會看的更遠(yuǎn)。大家有興趣的也可以到Gityuan的博客上多了解了解,全部都是干貨。而且他寫的東西比較權(quán)威,畢竟也是小米系統(tǒng)工程師的骨干成員。

Questions

  1. Looper 死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死,會消耗大量資源嗎?

  2. 主線程的消息循環(huán)機(jī)制是什么(死循環(huán)如何處理其它事務(wù))?

  3. ActivityThread 的動力是什么?(ActivityThread執(zhí)行Looper的線程是什么)

  4. Handler 是如何能夠線程切換,發(fā)送Message的?(線程間通訊)

  5. 子線程有哪些更新UI的方法。

  6. 子線程中Toast,showDialog,的方法。(和子線程不能更新UI有關(guān)嗎)

  7. 如何處理Handler 使用不當(dāng)導(dǎo)致的內(nèi)存泄露?

回答一: Looper 死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死?

線程默認(rèn)沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建Looper。我們經(jīng)常提到的主線程,也叫UI線程,它就是ActivityThread,ActivityThread被創(chuàng)建時就會初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因。

首先我們看一段代碼

  new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e("qdx", "step 0 ");
            Looper.prepare();

            Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();

            Log.e("qdx", "step 1 ");
            Looper.loop();

            Log.e("qdx", "step 2 ");

        }
    }).start();

我們知道Looper.loop();里面維護(hù)了一個死循環(huán)方法,所以按照理論,上述代碼執(zhí)行的應(yīng)該是 step 0 –>step 1 也就是說循環(huán)在Looper.prepare();與Looper.loop();之間。

在子線程中,如果手動為其創(chuàng)建了Looper,那么在所有的事情完成以后應(yīng)該調(diào)用quit方法來終止消息循環(huán),否則這個子線程就會一直處于等待(阻塞)狀態(tài),而如果退出Looper以后,這個線程就會立刻(執(zhí)行所有方法并)終止,因此建議不需要的時候終止Looper。

執(zhí)行結(jié)果也正如我們所說,這時候如果了解了ActivityThread,并且在main方法中我們會看到主線程也是通過Looper方式來維持一個消息循環(huán)

public static void main(String[] args) {
    Looper.prepareMainLooper();//創(chuàng)建Looper和MessageQueue對象,用于處理主線程的消息

    ActivityThread thread = new ActivityThread();
    thread.attach(false);//建立Binder通道 (創(chuàng)建新線程)

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    //如果能執(zhí)行下面方法,說明應(yīng)用崩潰或者是退出了...
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

那么回到我們的問題上,這個死循環(huán)會不會導(dǎo)致應(yīng)用卡死,即使不會的話,它會慢慢的消耗越來越多的資源嗎?

對于線程即是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了,線程退出。而對于主線程,我們是絕不希望會被運(yùn)行一段時間,自己就退出,那么如何保證能一直存活呢?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會被退出,例如,binder線程也是采用死循環(huán)的方法,通過循環(huán)方式不同與Binder驅(qū)動進(jìn)行讀寫操作,當(dāng)然并非簡單地死循環(huán),無消息時會休眠。但這里可能又引發(fā)了另一個問題,既然是死循環(huán)又如何去處理其他事務(wù)呢?通過創(chuàng)建新線程的方式。真正會卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時間過長,會導(dǎo)致掉幀,甚至發(fā)生ANR,looper.loop本身不會導(dǎo)致應(yīng)用卡死。

主線程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢? 其實不然,這里就涉及到Linux pipe/epoll機(jī)制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此時主線程會釋放CPU資源進(jìn)入休眠狀態(tài),直到下個消息到達(dá)或者有事務(wù)發(fā)生,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作。這里采用的epoll機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時監(jiān)控多個描述符,當(dāng)某個描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮?,本質(zhì)同步I/O,即讀寫是阻塞的。 所以說,主線程大多數(shù)時候都是處于休眠狀態(tài),并不會消耗大量CPU資源。 Gityuan–Handler(Native層)

回答二:主線程的消息循環(huán)機(jī)制是什么?

事實上,會在進(jìn)入死循環(huán)之前便創(chuàng)建了新binder線程,在代碼ActivityThread.main()中:

public static void main(String[] args) {
//創(chuàng)建Looper和MessageQueue對象,用于處理主線程的消息
 Looper.prepareMainLooper();

 //創(chuàng)建ActivityThread對象
 ActivityThread thread = new ActivityThread(); 

 //建立Binder通道 (創(chuàng)建新線程)
 thread.attach(false);

 Looper.loop(); //消息循環(huán)運(yùn)行
 throw new RuntimeException("Main thread loop unexpectedly exited");
}

Activity的生命周期都是依靠主線程的Looper.loop,當(dāng)收到不同Message時則采用相應(yīng)措施:一旦退出消息循環(huán),那么你的程序也就可以退出了。 從消息隊列中取消息可能會阻塞,取到消息會做出相應(yīng)的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現(xiàn)象。

thread.attach(false)方法函數(shù)中便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務(wù)端,用于接收系統(tǒng)服務(wù)AMS發(fā)送來的事件),該Binder線程通過Handler將Message發(fā)送給主線程?!窤ctivity 啟動過程」

比如收到msg=H.LAUNCH_ACTIVITY,則調(diào)用ActivityThread.handleLaunchActivity()方法,最終會通過反射機(jī)制,創(chuàng)建Activity實例,然后再執(zhí)行Activity.onCreate()等方法;

再比如收到msg=H.PAUSE_ACTIVITY,則調(diào)用ActivityThread.handlePauseActivity()方法,最終會執(zhí)行Activity.onPause()等方法。

主線程的消息又是哪來的呢?當(dāng)然是App進(jìn)程中的其他線程通過Handler發(fā)送給主線程

system_server進(jìn)程

system_server進(jìn)程是系統(tǒng)進(jìn)程,java framework框架的核心載體,里面運(yùn)行了大量的系統(tǒng)服務(wù),比如這里提供ApplicationThreadProxy(簡稱ATP),ActivityManagerService(簡稱AMS),這個兩個服務(wù)都運(yùn)行在system_server進(jìn)程的不同線程中,由于ATP和AMS都是基于IBinder接口,都是binder線程,binder線程的創(chuàng)建與銷毀都是由binder驅(qū)動來決定的。

App進(jìn)程

App進(jìn)程則是我們常說的應(yīng)用程序,主線程主要負(fù)責(zé)Activity/Service等組件的生命周期以及UI相關(guān)操作都運(yùn)行在這個線程; 另外,每個App進(jìn)程中至少會有兩個binder線程 ApplicationThread(簡稱AT)和ActivityManagerProxy(簡稱AMP),除了圖中畫的線程,其中還有很多線程

Binder

Binder用于不同進(jìn)程之間通信,由一個進(jìn)程的Binder客戶端向另一個進(jìn)程的服務(wù)端發(fā)送事務(wù),比如圖中線程2向線程4發(fā)送事務(wù);而handler用于同一個進(jìn)程中不同線程的通信,比如圖中線程4向主線程發(fā)送消息。

image

結(jié)合圖說說Activity生命周期,比如暫停Activity,流程如下:

1.線程1的AMS中調(diào)用線程2的ATP;(由于同一個進(jìn)程的線程間資源共享,可以相互直接調(diào)用,但需要注意多線程并發(fā)問題)
2.線程2通過binder傳輸?shù)紸pp進(jìn)程的線程4;
3.線程4通過handler消息機(jī)制,將暫停Activity的消息發(fā)送給主線程;
4.主線程在looper.loop()中循環(huán)遍歷消息,當(dāng)收到暫停Activity的消息時,便將消息分發(fā)給 ActivityThread.H.handleMessage()方法,再經(jīng)過方法的調(diào)用,
5.最后便會調(diào)用到Activity.onPause(),當(dāng)onPause()處理完后,繼續(xù)循環(huán)loop下去。

補(bǔ)充:

ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán),那么你的程序也就可以退出了。
從消息隊列中取消息可能會阻塞,取到消息會做出相應(yīng)的處理。如果某個消息處理時間過長,就可能會影響UI線程的刷新速率,造成卡頓的現(xiàn)象。

最后通過《Android開發(fā)藝術(shù)探索》的一段話總結(jié) :

ActivityThread通過ApplicationThread和AMS進(jìn)行進(jìn)程間通訊,AMS以進(jìn)程間通信的方式完成ActivityThread的請求后會回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會向H發(fā)送消息,H收到消息后會將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程中去執(zhí)行,這個過程就是。主線程的消息循環(huán)模型

另外,ActivityThread實際上并非線程,不像HandlerThread類,ActivityThread并沒有真正繼承Thread類

那么問題又來了,既然ActivityThread不是一個線程,那么ActivityThread中Looper綁定的是哪個Thread,也可以說它的動力是什么?

回答三:ActivityThread 的動力是什么?

進(jìn)程每個app運(yùn)行時前首先創(chuàng)建一個進(jìn)程,該進(jìn)程是由Zygote fork出來的,用于承載App上運(yùn)行的各種Activity/Service等組件。進(jìn)程對于上層應(yīng)用來說是完全透明的,這也是google有意為之,讓App程序都是運(yùn)行在Android Runtime。大多數(shù)情況一個App就運(yùn)行在一個進(jìn)程中,除非在AndroidManifest.xml中配置Android:process屬性,或通過native代碼fork進(jìn)程。

線程 線程對應(yīng)用來說非常常見,比如每次new Thread().start都會創(chuàng)建一個新的線程。該線程與App所在進(jìn)程之間資源共享,從Linux角度來說進(jìn)程與線程除了是否共享資源外,并沒有本質(zhì)的區(qū)別,都是一個task_struct結(jié)構(gòu)體,在CPU看來進(jìn)程或線程無非就是一段可執(zhí)行的代碼,CPU采用CFS調(diào)度算法,保證每個task都盡可能公平的享有CPU時間片。

其實承載ActivityThread的主線程就是由Zygote fork而創(chuàng)建的進(jìn)程。

回答四:Handler 是如何能夠線程切換

其實看完上面我們大致也清楚線程間是共享資源的。所以Handler處理不同線程問題就只要注意異步情況即可。

這里再引申出Handler的一些小知識點(diǎn)。 Handler創(chuàng)建的時候會采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng),Looper在哪個線程創(chuàng)建,就跟哪個線程綁定,并且Handler是在他關(guān)聯(lián)的Looper對應(yīng)的線程中處理消息的。(敲黑板)

那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的線程中互不干擾的存儲并提供數(shù)據(jù),通過ThreadLocal可以輕松獲取每個線程的Looper。

當(dāng)然需要注意的是:

①線程是默認(rèn)沒有Looper的,如果需要使用Handler,就必須為線程創(chuàng)建Looper。我們經(jīng)常提到的主線程,也叫UI線程,它就是ActivityThread,
ActivityThread被創(chuàng)建時就會初始化Looper,這也是在主線程中默認(rèn)可以使用Handler的原因。

系統(tǒng)為什么不允許在子線程中訪問UI?(摘自《Android開發(fā)藝術(shù)探索》) 這是因為Android的UI控件不是線程安全的,如果在多線程中并發(fā)訪問可能會導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對UI控件的訪問加上鎖機(jī)制呢?

缺點(diǎn)有兩個:

①首先加上鎖機(jī)制會讓UI訪問的邏輯變得復(fù)雜
②鎖機(jī)制會降低UI訪問的效率,因為鎖機(jī)制會阻塞某些線程的執(zhí)行。 所以最簡單且高效的方法就是采用單線程模型來處理UI操作。

那么問題又來了,子線程一定不能更新UI?

看到這里,又留下兩個知識點(diǎn)等待下篇詳解:View的繪制機(jī)制與Android Window內(nèi)部機(jī)制。

回答五:子線程有哪些更新UI的方法
主線程中定義Handler,子線程通過mHandler發(fā)送消息,主線程Handler的handleMessage更新UI。 用Activity對象的runOnUiThread方法。 創(chuàng)建Handler,傳入getMainLooper。 View.post(Runnabler)。
runOnUiThread 第一種咱們就不分析了,我們來看看第二種比較常用的寫法。

先重新溫習(xí)一下上面說的

Looper在哪個線程創(chuàng)建,就跟哪個線程綁定,并且Handler是在他關(guān)聯(lián)的Looper對應(yīng)的線程中處理消息的。(敲黑板)

   new Thread(new Runnable() {
        @Override
        public void run() {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //DO UI method

                }
            });

        }
    }).start();

final Handler mHandler = new Handler();

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);//子線程(非UI線程)
    } else {
        action.run();
    }
}

進(jìn)入Activity類里面,可以看到如果是在子線程中,通過mHandler發(fā)送的更新UI消息。 而這個Handler是在Activity中創(chuàng)建的,也就是說在主線程中創(chuàng)建,所以便和我們在主線程中使用Handler更新UI沒有差別。 因為這個Looper,就是ActivityThread中創(chuàng)建的Looper(Looper.prepareMainLooper())

創(chuàng)建Handler,傳入getMainLooper 那么同理,我們在子線程中,是否也可以創(chuàng)建一個Handler,并獲取MainLooper,從而在子線程中更新UI呢? 首先我們看到,在Looper類中有靜態(tài)對象sMainLooper,并且這個sMainLooper就是在ActivityThread中創(chuàng)建的MainLooper

private static Looper sMainLooper;  // guarded by Looper.class

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

所以不用多說,我們就可以通過這個sMainLooper來進(jìn)行更新UI操作。

 new Thread(new Runnable() {
        @Override
        public void run() {

            Log.e("qdx", "step 1 "+Thread.currentThread().getName());

            Handler handler=new Handler(getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {

                    //Do Ui method
                    Log.e("qdx", "step 2 "+Thread.currentThread().getName());
                }
            });

        }
    }).start();

View.post(Runnabler) 老樣子,我們點(diǎn)入源碼

//View

/**
 * <p>Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 */
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action); //一般情況走這里
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

    /**
     * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
     * handler can be used to pump events in the UI events queue.
     */
    final Handler mHandler;

居然也是Handler從中作祟,根據(jù)Handler的注釋,也可以清楚該Handler可以處理UI事件,也就是說它的Looper也是主線程的sMainLooper。這就是說我們常用的更新UI都是通過Handler實現(xiàn)的。

另外更新UI 也可以通過AsyncTask來實現(xiàn),難道這個AsyncTask的線程切換也是通過 Handler 嗎? 沒錯,也是通過Handler……

Handler實在是......

回答六:子線程中Toast,showDialog,的方法

可能有些人看到這個問題,就會想: 子線程本來就不可以更新UI的啊 而且上面也說了更新UI的方法

兄臺且慢,且聽我把話寫完

  new Thread(new Runnable() {
        @Override
        public void run() {

            Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();//崩潰無疑

        }
    }).start();

看到這個崩潰日志,是否有些疑惑,因為一般如果子線程不能更新UI控件是會報如下錯誤的(子線程不能更新UI)

所以子線程不能更新Toast的原因就和Handler有關(guān)了,據(jù)我們了解,每一個Handler都要有對應(yīng)的Looper對象,那么。 滿足你。

   new Thread(new Runnable() {
        @Override
        public void run() {

            Looper.prepare();
            Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();
            Looper.loop();

        }
    }).start();

這樣便能在子線程中Toast,不是說子線程…? 老樣子,我們追根到底看一下Toast內(nèi)部執(zhí)行方式。

//Toast

/**
 * Show the view for the specified duration.
 */
public void show() {
    ``````

    INotificationManager service = getService();//從SMgr中獲取名為notification的服務(wù)
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;

    try {
        service.enqueueToast(pkg, tn, mDuration);//enqueue? 難不成和Handler的隊列有關(guān)?
    } catch (RemoteException e) {
        // Empty
    }
}

在show方法中,我們看到Toast的show方法和普通UI 控件不太一樣,并且也是通過Binder進(jìn)程間通訊方法執(zhí)行Toast繪制。這其中的過程就不在多討論了,有興趣的可以在NotificationManagerService類中分析。

現(xiàn)在把目光放在TN 這個類上(難道越重要的類命名就越簡潔,如H類),通過TN 類,可以了解到它是Binder的本地類。在Toast的show方法中,將這個TN對象傳給NotificationManagerService就是為了通訊!并且我們也在TN中發(fā)現(xiàn)了它的show方法。

private static class TN extends ITransientNotification.Stub {//Binder服務(wù)端的具體實現(xiàn)類

    /**
     * schedule handleShow into the right thread
     */
    @Override
    public void show(IBinder windowToken) {
        mHandler.obtainMessage(0, windowToken).sendToTarget();
    }

    final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            IBinder token = (IBinder) msg.obj;
            handleShow(token);
        }
    };

}

看完上面代碼,就知道子線程中Toast報錯的原因,因為在TN中使用Handler,所以需要創(chuàng)建Looper對象。 那么既然用Handler來發(fā)送消息,就可以在handleMessage中找到更新Toast的方法。 在handleMessage看到由handleShow處理。

//Toast的TN類

  public void handleShow(IBinder windowToken) {

            ``````
            mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

            mParams.x = mX;
            mParams.y = mY;
            mParams.verticalMargin = mVerticalMargin;
            mParams.horizontalMargin = mHorizontalMargin;
            mParams.packageName = packageName;
            mParams.hideTimeoutMilliseconds = mDuration ==
                Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
            mParams.token = windowToken;
            if (mView.getParent() != null) {
                mWM.removeView(mView);
            }
            mWM.addView(mView, mParams);//使用WindowManager的addView方法
            trySendAccessibilityEvent();
        }
    }

看到這里就可以總結(jié)一下:

Toast本質(zhì)是通過window顯示和繪制的(操作的是window),而主線程不能更新UI 是因為ViewRootImplcheckThread方法在Activity維護(hù)的View樹的行為。 Toast中TN類使用Handler是為了用隊列和時間控制排隊顯示Toast,所以為了防止在創(chuàng)建TN時拋出異常,需要在子線程中使用Looper.prepare();和Looper.loop();(但是不建議這么做,因為它會使線程無法執(zhí)行結(jié)束,導(dǎo)致內(nèi)存泄露)

Dialog亦是如此。同時我們又多了一個知識點(diǎn)要去研究:Android 中Window是什么,它內(nèi)部有什么機(jī)制?

回答七:如何處理Handler 使用不當(dāng)導(dǎo)致的內(nèi)存泄露? 首先上文在子線程中為了節(jié)目效果,使用如下方式創(chuàng)建Looper

        Looper.prepare();
        ``````
        Looper.loop();

實際上這是非常危險的一種做法

在子線程中,如果手動為其創(chuàng)建Looper,那么在所有的事情完成以后應(yīng)該調(diào)用quit方法來終止消息循環(huán),否則這個子線程就會一直處于等待的狀態(tài),而如果退出Looper以后,這個線程就會立刻終止,因此建議不需要的時候終止Looper。(【 Looper.myLooper().quit(); 】)

那么,如果在Handler的handleMessage方法中(或者是run方法)處理消息,如果這個是一個延時消息,會一直保存在主線程的消息隊列里,并且會影響系統(tǒng)對Activity的回收,造成內(nèi)存泄露。

具體可以參考Handler內(nèi)存泄漏分析及解決

總結(jié)一下,解決Handler內(nèi)存泄露主要2點(diǎn)

1 有延時消息,要在Activity銷毀的時候移除Messages 2 匿名內(nèi)部類導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類,并且對上下文或者Activity使用弱引用。

總結(jié)

想不到Handler居然可以騰出這么多浪花,與此同時感謝前輩的摸索。

另外Handler還有許多不為人知的秘密,等待大家探索,下面我再簡單的介紹兩分鐘

  • HandlerThread
  • IdleHandler

HandlerThread

HandlerThread繼承Thread,它是一種可以使用Handler的Thread,它的實現(xiàn)也很簡單,在run方法中也是通過Looper.prepare()來創(chuàng)建消息隊列,并通過Looper.loop()來開啟消息循環(huán)(與我們手動創(chuàng)建方法基本一致),這樣在實際的使用中就允許在HandlerThread中創(chuàng)建Handler了。
由于HandlerThread的run方法是一個無限循環(huán),因此當(dāng)不需要使用的時候通過quit或者quitSafely方法來終止線程的執(zhí)行。

HandlerThread的本質(zhì)也是線程,所以切記關(guān)聯(lián)的Handler中處理消息的handleMessage為子線程。

IdleHandler

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

根據(jù)注釋可以了解到,這個接口方法是在消息隊列全部處理完成后或者是在阻塞的過程中等待更多的消息的時候調(diào)用的,返回值false表示只回調(diào)一次,true表示可以接收多次回調(diào)。

具體使用如下代碼

   Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {

            return false;
        }
    });

另外提供一個小技巧:在HandlerThread中獲取Looper的MessageQueue`方法之反射。

因為

Looper.myQueue()如果在主線程調(diào)用就會使用主線程looper 使用handlerThread.getLooper().getQueue()最低版本需要23 //HandlerThread中獲取MessageQueue

        Field field = Looper.class.getDeclaredField("mQueue");
        field.setAccessible(true);
        MessageQueue queue = (MessageQueue) field.get(handlerThread.getLooper());

那么Android的消息循環(huán)機(jī)制是通過Handler,是否可以通過IdleHandler來判斷Activity的加載和繪制情況(measure,layout,draw等)呢?并且IdleHandler是否也隱藏著不為人知的特殊功能?

原文:https://blog.csdn.net/qian520ao/article/details/78262289

文末

今天關(guān)于面試的分享就到這里,還是那句話,有些東西你不僅要懂,而且要能夠很好地表達(dá)出來,能夠讓面試官認(rèn)可你的理解,例如Handler機(jī)制,這個是面試必問之題。有些晦澀的點(diǎn),或許它只活在面試當(dāng)中,實際工作當(dāng)中你壓根不會用到它,但是你要知道它是什么東西。

最后在這里小編分享一份自己收錄整理上述技術(shù)體系圖相關(guān)的幾十套騰訊、頭條、阿里、美團(tuán)等公司19年的面試題,把技術(shù)點(diǎn)整理成了視頻和PDF(實際上比預(yù)期多花了不少精力),包含知識脈絡(luò) + 諸多細(xì)節(jié),由于篇幅有限,這里以圖片的形式給大家展示一部分。

還有 高級架構(gòu)技術(shù)進(jìn)階腦圖、Android開發(fā)面試專題資料,高級進(jìn)階架構(gòu)資料 幫助大家學(xué)習(xí)提升進(jìn)階,也節(jié)省大家在網(wǎng)上搜索資料的時間來學(xué)習(xí),也可以分享給身邊好友一起學(xué)習(xí)。

Android學(xué)習(xí)PDF+架構(gòu)視頻+面試文檔+源碼筆記

【Android核心高級技術(shù)PDF文檔,BAT大廠面試真題解析】

image

【算法合集】

image

【延伸Android必備知識點(diǎn)】

image

【Android進(jìn)階學(xué)習(xí)視頻】、【全套Android面試秘籍】關(guān)注我【主頁簡介】或者【簡信我】查看免費(fèi)領(lǐng)取方式!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 漸變的面目拼圖要我怎么拼? 我是疲乏了還是投降了? 不是不允許自己墜落, 我沒有滴水不進(jìn)的保護(hù)膜。 就是害怕變得面...
    悶熱當(dāng)乘涼閱讀 4,471評論 0 13
  • 夜鶯2517閱讀 128,103評論 1 9
  • 版本:ios 1.2.1 亮點(diǎn): 1.app角標(biāo)可以實時更新天氣溫度或選擇空氣質(zhì)量,建議處女座就不要選了,不然老想...
    我就是沉沉閱讀 7,377評論 1 6
  • 我是一名過去式的高三狗,很可悲,在這三年里我沒有戀愛,看著同齡的小伙伴們一對兒一對兒的,我的心不好受。怎么說呢,高...
    小娘紙閱讀 3,773評論 4 7
  • 那一年,我選擇了獨(dú)立遠(yuǎn)行,火車帶著我在前進(jìn)的軌道上爬行了超過23個小時; 那一年,我走過泥濘的柏油路,在那個遠(yuǎn)離故...
    木芽閱讀 1,918評論 4 5

友情鏈接更多精彩內(nèi)容