面試必備:異步 Handler 十大必問!

2021-03-02 Carson帶你學習Android

在安卓面試中,關於 Handler 的問題是必備的,主要是以下十個:

Handler 的基本原理

子線程中怎麼使用 Handler

MessageQueue 獲取消息是怎麼等待

為什麼不用 wait 而用 epoll 呢?

線程和 Handler Looper MessageQueue 的關係

多個線程給 MessageQueue 發消息,如何保證線程安全

Handler 消息延遲是怎麼處理的

View.post 和 Handler.post 的區別

Handler 導致的內存洩漏

非 UI 線程真的不能操作 View 嗎

1. Handler 的基本原理關於 Handler 的原理,相比不用多說了,大家都應該知道,一張圖就可以說明(圖片來自網絡)。

2. 子線程中怎麼使用 Handler除了上面 Handler 的基本原理,子線程中如何使用 Handler 也是一個常見的問題。子線程中使用 Handler 需要先執行兩個操作:Looper.prepare 和 Looper.loop。為什麼需要這樣做呢?Looper.prepare 和 Looper.loop 都做了什麼事情呢?
我們知道如果在子線程中直接創建一個 Handler 的話,會報如下的錯誤:

"Can't create handler inside thread xxx that has not called Looper.prepare()

我們可以看一下 Handler 的構造函數,裡面會對 Looper 進行判斷,如果通過 ThreadLocal 獲取的 Looper 為空,則報上面的錯誤。

    public Handler(Callback callback, boolean async) {        mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread " + Thread.currentThread()                        + " that has not called Looper.prepare()");        }    }
public static @Nullable Looper myLooper() { return sThreadLocal.get();    }

那麼 Looper.prepare 裡做了什麼事情呢?

    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.prepare 就是創建了 Looper 並設置給 ThreadLocal,這裡的一個細節是每個 Thread 只能有一個 Looper,否則也會拋出異常。而 Looper.loop 就是開始讀取 MessageQueue 中的消息,進行執行了。這裡一般會引申一個問題,就是主線程中為什麼不用手動調用這兩個方法呢?相信大家也都明白,就是 ActivityThread.main 中已經進行了調用。通過這個問題,又可以引申到 ActivityThread 相關的知識,這裡就不細說了。3. MessageQueue 如何等待消息上面說到 Looper.loop 其實就是開始讀取 MessageQueue 中的消息了,那 MessageQueue 中沒有消息的時候,Looper 在做什麼呢?我們知道是在等待消息,那是怎麼等待的呢?通過 Looper.loop 方法,我們知道是 MessageQueue.next() 來獲取消息的,如果沒有消息,那就會阻塞在這裡,MessageQueue.next 是怎麼等待的呢?

    public static void loop() {        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;            }        }    }

    Message next() {        for (;;) {            nativePollOnce(ptr, nextPollTimeoutMillis);            // ...        }    }

在 MessageQueue.next 裡調用了 native 方法 nativePollOnce。

// android_os_MessageQueue.cppstatic void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,        jlong ptr, jint timeoutMillis) {    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { // ... mLooper->pollOnce(timeoutMillis); // ...}
// Looper.cppint Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { // ... result = pollInner(timeoutMillis); // ...}
int Looper::pollInner(int timeoutMillis) { // ... int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);}

從上面代碼中我們可以看到,在 native 側,最終是使用了 epoll_wait 來進行等待的。這裡的 epoll_wait 是 Linux 中 epoll 機制中的一環,關於 epoll 機制這裡就不進行過多介紹了,大家有興趣可以參考 https://segmentfault.com/a/1190000003063859        那其實說到這裡,又有一個問題,為什麼不用 java 中的 wait / notify 而是要用 native 的 epoll 機制呢?4. 為什麼不用 wait 而用 epoll 呢?說起來 java 中的 wait / notify 也能實現阻塞等待消息的功能,在 Android 2.2 及以前,也確實是這樣做的。可以參考這個 commit https://www.androidos.net.cn/android/2.1_r2.1p2/xref/frameworks/base/core/java/android/os/MessageQueue.java   那為什麼後面要改成使用 epoll 呢?通過看 commit 記錄,是需要處理 native 側的事件,所以只使用 java 的 wait / notify 就不夠用了。具體的改動就是這個 commit https://android.googlesource.com/platform/frameworks/base/+/fa9e7c05c7be6891a6cf85a11dc635a6e6853078%5E%21/#F0 

Sketch of Native input for MessageQueue / Looper / ViewRoot
MessageQueue now uses a socket for internal signalling, and is preparedto also handle any number of event input pipes, once the plumbing isset up with ViewRoot / Looper to tell it about them as appropriate.
Change-Id: If9eda174a6c26887dc51b12b14b390e724e73ab3

不過這裡最開始使用的還是 select,後面才改成 epoll。具體可見這個 commit https://android.googlesource.com/platform/frameworks/base/+/46b9ac0ae2162309774a7478cd9d4e578747bfc2%5E%21/#F16           至於 select 和 epoll 的區別,這裡也不細說了,大家可以在上面的參考文章中一起看看。
5. 線程和 Handler Looper MessageQueue 的關係這裡的關係是一個線程對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。6. 多個線程給 MessageQueue 發消息,如何保證線程安全既然一個線程對應一個 MessageQueue,那多個線程給 MessageQueue 發消息時是如何保證線程安全的呢?
說來簡單,就是加了個鎖而已。

// MessageQueue.javaboolean enqueueMessage(Message msg, long when) {    synchronized (this) {        // ...    }}

7. Handler 消息延遲是怎麼處理的Handler 引申的另一個問題就是延遲消息在 Handler 中是怎麼處理的?定時器還是其他方法?

// Handler.javapublic final boolean postDelayed(Runnable r, long delayMillis){    return sendMessageDelayed(getPostMessage(r), delayMillis);}
public final boolean sendMessageDelayed(Message msg, long delayMillis){ // 傳入的 time 是 uptimeMillis + delayMillis return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { // ... return enqueueMessage(queue, msg, uptimeMillis);}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 調用 MessageQueue.enqueueMessage return queue.enqueueMessage(msg, uptimeMillis);}

從上面的代碼邏輯來看,Handler post 消息以後,一直調用到 MessageQueue.enqueueMessage 裡,其中最重要的一步操作就是傳入的時間是 uptimeMillis + delayMillis。

boolean enqueueMessage(Message msg, long when) {    synchronized (this) {        // ...        msg.when = when;        Message p = mMessages; // 下一條消息        // 根據 when 進行順序排序,將消息插入到其中        if (p == null || when == 0 || when < p.when) {            msg.next = p;            mMessages = msg;            needWake = mBlocked;        } else {            // 找到 合適的節點            Message prev;            for (;;) {                prev = p;                p = p.next;                if (p == null || when < p.when) {                    break;                }            }            // 插入操作            msg.next = p; // invariant: p == prev.next            prev.next = msg;        }
// 喚醒隊列進行取消息 if (needWake) { nativeWake(mPtr); } } return true;}

通過上面代碼我們看到,post 一個延遲消息時,在 MessageQueue 中會根據 when 的時長進行一個順序排序。

Message next() {    // ...    for (;;) {        // 通過 epoll_wait 等待消息,等待 nextPollTimeoutMillis 時長        nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) { // 當前時間 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 獲得一個有效的消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 說明需要延遲執行,通過;nativePollOnce 的 timeout 來進行延遲 // 獲取需要等待執行的時間 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; msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; }
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { // 當前沒有消息要執行,則執行 IdleHandler 中的內容 pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // 如果沒有 IdleHandler 需要執行,則去等待 消息的執行 mBlocked = true; continue; }
if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); }
// 執行 idle handlers 內容 for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); }
if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } }
// Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0;
// 如果執行了 idle handlers 的內容,現在消息可能已經到了執行時間,所以這個時候就不等待了,再去檢查一下消息是否可以執行, nextPollTimeoutMillis 需要置為 0 nextPollTimeoutMillis = 0; }}

通過上面的代碼分析,我們知道了執行 Handler.postDelayd 時候,會執行下面幾個步驟:

MessageQueue 中根據上一步轉化的時間進行順序排序在 MessageQueue.next 獲取消息時,對比當前時間(now)和第一步轉化的時間(when),如果 now < when,則通過 epoll_wait 的 timeout 進行等待如果該消息需要等待,會進行 idel handlers 的執行,執行完以後會再去檢查此消息是否可以執行8. View.post 和 Handler.post 的區別我們最常用的 Handler 功能就是 Handler.post,除此之外,還有 View.post 也經常會用到,那麼這兩個有什麼區別呢?

// View.javapublic 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;}

通過代碼來看,如果 AttachInfo 不為空,則通過 handler 去執行,如果 handler 為空,則通過 RunQueue 去執行。那我們先看看這裡的 AttachInfo 是什麼。這個就需要追溯到 ViewRootImpl 的流程裡了,我們先看下面這段代碼。

// ViewRootImpl.javafinal ViewRootHandler mHandler = new ViewRootHandler();
public ViewRootImpl(Context context, Display display) { // ... mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);}
private void performTraversals() { final View host = mView; // ... if (mFirst) { host.dispatchAttachedToWindow(mAttachInfo, 0); mFirst = false; } // ...}

代碼寫了一些關鍵部分,在 ViewRootImpl 構造函數裡,創建了 mAttachInfo,然後在 performTraversals 裡,如果 mFirst 為 true,則調用 host.dispatchAttachedToWindow。這裡的 host 就是 DecorView,如果有讀者朋友對這裡不太清楚,可以看看前面【面試官帶你學安卓-從View的繪製流程】說起這篇文章複習一下。這裡還有一個知識點就是 mAttachInfo 中的 mHandler 其實是 ViewRootImpl 內部的 ViewRootHandler。然後就調用到了 DecorView.dispatchAttachedToWindow,其實就是 ViewGroup 的 dispatchAttachedToWindow。一般 ViewGroup 中相關的方法,都是去依次調用 child 的對應方法。這個也不例外,依次調用子 View 的 dispatchAttachedToWindow,把 AttachInfo 傳進去,在 子 View 中給 mAttachInfo 賦值。

// ViewGroupvoid dispatchAttachedToWindow(AttachInfo info, int visibility) {    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;    super.dispatchAttachedToWindow(info, visibility);    mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); } final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility())); }}
// Viewvoid dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; // ...}

我們是在看 View.post 的流程,再回顧一下 View.post 的代碼:

// View.javapublic boolean post(Runnable action) {    final AttachInfo attachInfo = mAttachInfo;    if (attachInfo != null) {        return attachInfo.mHandler.post(action);    }
getRunQueue().post(action); return true;}

現在我們知道 attachInfo 是什麼了,是 ViewRootImpl 首次觸發 performTraversals 傳進來的,也就是觸發 performTraversals 之後,View.post 都是通過 ViewRootImpl 內部的 Handler 進行處理的。如果在 performTraversals 之前或者 mAttachInfo 置為空以後進行執行,則通過 RunQueue 進行處理。那我們再看看 getRunQueue().post(action); 做了些什麼事情。這裡的 RunQueue 其實是 HandlerActionQueue。HandlerActionQueue 的代碼看一下。

public class HandlerActionQueue {    public void post(Runnable action) {        postDelayed(action, 0);    }
public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; } }
public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); }
mActions = null; mCount = 0; } }}

通過上面的代碼我們可以看到,執行 getRunQueue().post(action); 其實是將代碼添加到 mActions 進行保存,然後在 executeActions 的時候進行執行。executeActions 執行的時機只有一個,就是在 dispatchAttachedToWindow(AttachInfo info, int visibility) 裡面調用的。

void dispatchAttachedToWindow(AttachInfo info, int visibility) {    mAttachInfo = info;    if (mRunQueue != null) {        mRunQueue.executeActions(info.mHandler);        mRunQueue = null;    }}

看到這裡我們就知道了,View.post 和 Handler.post 的區別就是:如果在 performTraversals 前調用 View.post,則會將消息進行保存,之後在 dispatchAttachedToWindow 的時候通過 ViewRootImpl 中的 Handler 進行調用。如果在 performTraversals 以後調用 View.post,則直接通過 ViewRootImpl 中的 Handler 進行調用。這裡我們又可以回答一個問題了,就是為什麼 View.post 裡可以拿到 View 的寬高信息呢?因為 View.post 的 Runnable 執行的時候,已經執行過 performTraversals 了,也就是 View 的 measure layout draw 方法都執行過了,自然可以獲取到 View 的寬高信息了。9. Handler 導致的內存洩漏這個問題就是老生常談了,可以由此再引申出內存洩漏的知識點,比如:如何排查內存洩漏,如何避免內存洩漏等等。10. 非 UI 線程真的不能操作 View 嗎我們使用 Handler 最多的一個場景就是在非主線程通過 Handler 去操作 主線程的 View。我們在執行 UI 操作的時候,都會調用到 ViewRootImpl 裡,以 requestLayout 為例,在 requestLayout 裡會通過 checkThread 進行線程的檢查。

// ViewRootImpl.javapublic ViewRootImpl(Context context, Display display) {    mThread = Thread.currentThread();}
public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); }}

我們看這裡的檢查,其實並不是檢查主線程,是檢查 mThread != Thread.currentThread,而 mThread 指的是 ViewRootImpl 創建的線程。所以非 UI 線程確實不能操作 View,但是檢查的是創建的線程是否是當前線程,因為 ViewRootImpl 創建是在主線程創建的,所以在非主線程操作 UI 過不了這裡的檢查。

一個小小的 Handler,其實可以引申出很多問題,這裡這是列舉了一些大家可能忽略的問題,更多的問題就等待大家去探索了~

1. Handler 的基本原理

2. 子線程中怎麼使用 HandlerLooper.prepare 創建 Looper 並添加到 ThreadLocal 中Looper.loop 啟動 Looper 的循環3. MessageQueue 獲取消息是怎麼等待4. 為什麼不用 wait 而用 epoll 呢?在 Android 2.2 及之前,使用 Java wait / notify 進行等待,在 2.3 以後,使用 epoll 機制,為了可以同時處理 native 側的消息。5. 線程和 Handler Looper MessageQueue 的關係一個線程對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。6. 多個線程給 MessageQueue 發消息,如何保證線程安全通過對 MessageQueue 加鎖來保證線程安全。7. Handler 消息延遲是怎麼處理的MessageQueue 中根據上一步轉化的時間進行順序排序在 MessageQueue.next 獲取消息時,對比當前時間(now)和第一步轉化的時間(when),如果 now < when,則通過 epoll_wait 的 timeout 進行等待如果該消息需要等待,會進行 idel handlers 的執行,執行完以後會再去檢查此消息是否可以執行8. View.post 和 Handler.post 的區別View.post 最終也是通過 Handler.post 來執行消息的,執行過程如下:如果在 performTraversals 前調用 View.post,則會將消息進行保存,之後在 dispatchAttachedToWindow 的時候通過 ViewRootImpl 中的 Handler 進行調用。如果在 performTraversals 以後調用 View.post,則直接通過 ViewRootImpl 中的 Handler 進行調用。9. Handler 導致的內存洩漏10. 非 UI 線程真的不能操作 View 嗎不能操作,原因是 ViewRootImpl 會檢查創建 ViewRootImpl 的線程和當前操作的線程是否一致。而 ViewRootImpl 是在主線程創建的,所以非主線程不能操作 View。至此,關於 面試時異步消息機制Handler十大必問 講解完畢。「Carson每天帶你學習一個Android知識點」,長按掃描關注公眾號,我們明天見哦!

最後福利:學習資料贈送福利:由本人親自撰寫 & 整理的「Android學習方法資料」參與方式:「點擊文章右下角」在看「 -> 回復截圖到公眾號 即可,我將從中隨機抽取」

相關焦點

  • Android開發 面試必問的Handler消息機制
    最近項目提測了也閒了下來看到Handler就想起面試必問,Handler機制相信大家每個人面試的時候都被問到吧,就來總結一下看看,話不多說先看流體圖:
  • Android中Handler問題匯總【面試必備】
    地址 |  juejin.im/post/5e886bc3e51d4546fc795793Handler機制幾乎是Android面試時必問的問題
  • Vue面試必問,異步更新機制和nextTick原理
    同時也可以看出傳給 nextTick 的異步回調函數是被壓成了一個同步任務在一個tick執行完的,而不是開啟多個異步任務。強制 macro task 的方法是在綁定 DOM 事件的時候,默認會給回調的 handler 函數調用 withMacroTask 方法做一層包裝 handler = withMacroTask(handler),它保證整個回調函數執行過程中,遇到數據狀態的改變,這些改變都會被推到 macro task 中。
  • Spring MVC 的執行過程,面試必問了!
    Spring AOP、Spring MVC 這兩個框架應該是國內面試必問的題目了,當然,網上有很多答案
  • Vue 進階面試必問,異步更新機制和 nextTick 原理
    同時也可以看出傳給 nextTick 的異步回調函數是被壓成了一個同步任務在一個tick執行完的,而不是開啟多個異步任務。強制 macro task 的方法是在綁定 DOM 事件的時候,默認會給回調的 handler 函數調用 withMacroTask 方法做一層包裝 handler = withMacroTask(handler),它保證整個回調函數執行過程中,遇到數據狀態的改變,這些改變都會被推到 macro task 中。
  • 你真的了解Handler嗎
    「handler相關四大天王」looper,關聯線程並且負責從消息隊列拿出消息分發給handlerMessageQueue,消息隊列,負責消息存儲管理「用法和流程就這麼多,下面開始常見面試點講解並附上簡單的源碼解析,具體剖析Handler
  • 5分鐘了解Handler錯誤使用場景
    -> epoll_wait();2.2.Handler.dispatchMessage(msg);//消息分發  myLooper().mQueue.next()實現原理    通過myLooper().mQueue.next() 循環獲取MessageQueue中的消息,如遇到同步屏障 則優先處理異步消息
  • 重學 Android 之 Handler 機制
    機制的使用幾乎隨處可見,作為面試中的常客,我們真的了解 handler 嗎?想必很多同學會想,當然了,handler 機制不就是 Looper 、handler  MessageQueue 嗎?下面拋磚引玉,我們先來看看這些問題為什么子線程中不可以直接 new Handler() 而主線程中可以?為什麼建議使用 Message.obtain() 來創建 Message 實例?Looper 在主線程中死循環,為啥不會 ANR ?
  • redis異步接口與nginx事件機制的對接
    異步API則用於各種後端框架,例如nodejs的express框架,等等。redis自帶的例子,展示了怎麼對接libae、libuv、libevent、libev等多種異步事件庫,其中libae是redis官方自己的庫。
  • 面試官:給我說說什麼是同步異步?
    話不多說,開始今天的學習:一、同步、異步請求瀏覽器發送請求給伺服器,其有同步請求和異步請求兩種方式。1同步請求什麼叫同步請求呢?就需要引入異步的概念了。2異步請求和同步請求相對,異步不需要等待響應,隨時可以發送下一次請求。如果是同步請求,需要將信息填寫完整,再發送請求,伺服器響應填寫是否正確,再做修改。但是異步請求是局部頁面更新。
  • 幾道經常會被問到的Promise面試題
    Promise面試題題目一const promise = newPromise((resolve, reject) => {console.log(1);resolve,並將異步操作的結果,作為參數傳遞出去;reject函數將Promise對象的狀態從「未完成」變為「失敗」(即從pending變為rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。
  • Handler原理,這一篇就夠了
    前言hanlder的大概原理,可能很多人不知道,至少不清楚,網上很多文章也是到處粘貼,聽別說handler把Message發送到MessageQueue裡面去,Looper通過死循環,不斷從MessageQueue裡面獲取Message處理消息,因為Mesage.target就是當前hanlder,所以最後轉到handleMessage()方法中去處理,整個流程是這樣。
  • Android開發:HandlerThread是什麼?
    // 定義異步任務消息private final int MSG_EXECUTE_TASK = 100;/** * 創建HandlerThread */private void initHandlerThread() { // 必須按照以下三個步驟: // 1、創建HandlerThread的實例對象 mHandlerThread
  • JavaScript同步、異步、回調執行順序之經典閉包setTimeout面試題分析
    大家注意了,教大家一道口訣:同步優先、異步靠邊、回調墊底(讀起來不順)用公式表達就是:同步 => 異步 => 回調這口訣有什麼用呢?用來對付面試的。知乎有大神講解過 80% 應聘者都不及格的 JS 面試題 ,就是以這個例子為開頭的。但是沒有說為什麼setTimeout是輸出5個5。
  • Android Handler 由淺入深源碼全解析
    關於 handler的源碼已經很前人分享過了。如果我沒能給大家講明白可以參考網上其他人寫的。        註:文中所貼源碼都有精簡過,並非完整源碼,只保留主思路,刪減了一些非法校驗細節實現目錄簡單使用方法        應用層開發時handle常要用於線程切換調度和異步消息、更新UI等,但不僅限於這些。
  • 2021國考面試:打造高逼格面試答案必備素材之名言警句
    2021國考面試:打造高逼格面試答案必備素材之名言警句由北京人事考試網提供:更多關於2021國考面試,面試答案必備素材的內容請關注國家公務員考試網/北京公務員考試網!或關注北京華圖微信公眾號(bjhuatu),國家公務員考試培訓諮詢電話:400-010-1568。
  • Java 非阻塞 IO 和異步 IO
    很多初學者可能分不清楚異步和非阻塞的區別,只是在各種場合能聽到異步非阻塞這個詞。本文會先介紹並演示阻塞模式,然後引入非阻塞模式來對阻塞模式進行優化,最後再介紹 JDK7 引入的異步 IO,由於網上關於異步 IO 的介紹相對較少,所以這部分內容我會介紹得具體一些。
  • 面試官必問的這5個問題,答案都整理好了,還怕面試不成功嗎?
    面試官必問的這5個問題,答案都整理好了,還怕面試不成功嗎?原創|真的是自討苦吃在職場中,想要找到一個好工作實屬不易,現在競爭也是相當惡劣。最先考察你的就是面試官的重重阻礙,有可能你沒有發揮好,就直接被淘汰了,現實就是這麼殘酷。那對於職場新人來說,緊張是必然的,更別說臨場發揮了。俗話說得好:笨鳥先飛。那麼一定要把準備工作做好,才會在面試中遊刃有餘。面試官必問的10個問題,完美無縫隙的答案也已經備好,上有政策下有對策。這些都是值得你思考的內容。
  • Redux異步方案選型
    本文會從一些常見的Redux異步方案出發,介紹它們的優缺點,進而討論一些與異步相伴的常見場景,幫助你在選型時更好地權衡利弊。 它向我們展示了Redux處理異步的原理,即: Redux本身只能處理同步的Action,但可以通過中間件來攔截處理其它類型的action,比如函數(Thunk),再用回調觸發普通Action,從而實現異步處理,在這點上所有Redux的異步方案都是類似的。
  • 關於Handler你所需要知道的一切
    Handler如果僅僅是使用的話,確實沒什麼好講的,但是Handler卻是一個幾乎所有面試官都會問的問題,不同的要求問的深度也不一樣,今天我就帶大家學習一下關於Handler你所必須要掌握的知識。這時候,你可能會問,這種無限死循環會不會很浪費資源?其實並不會,因為當沒有message被添加到隊列的時候,程序會進入阻塞狀態,對資源的消耗是很小的。