想實現Android隊列功能?Handler內功心法,你值得擁有!——Handler源碼和常見問題的解答

Handler是Android中的消息處理機制,是一種線程間通信的解決方案,同時你也可以理解爲它天然的爲我們在主線程創建一個隊列,隊列中的消息順序就是我們設置的延遲的時間,如果你想在Android中實現一個隊列的功能,不妨第一時間考慮一下它。本文分爲三部分:

Handler的源碼和常見問題的解答

  1. 一個線程中最多有多少個Handler,Looper,MessageQueue?
  2. Looper死循環爲什麼不會導致應用卡死,會耗費大量資源嗎?
  3. 子線程的如何更新UI,比如Dialog,Toast等?系統爲什麼不建議子線程中更新UI?
  4. 主線程如何訪問網絡?
  5. 如何處理Handler使用不當造成的內存泄漏?
  6. Handler的消息優先級,有什麼應用場景?
  7. 主線程的Looper何時退出?能否手動退出?
  8. 如何判斷當前線程是安卓主線程?
  9. 正確創建Message實例的方式?

Handler深層次問題解答

  1. ThreadLocal
  2. epoll機制
  3. Handle同步屏障機制
  4. Handler的鎖相關問題
  5. Handler中的同步方法

Handler在系統以及第三方框架的一些應用

  1. HandlerThread
  2. IntentService
  3. 如何打造一個不崩潰的APP
  4. Glide中的運用

Handler的源碼和常見問題的解答

下面來看一下官方對其的定義:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.

大意就是Handler允許你發送Message/Runnable到線程的消息隊列(MessageQueue)中,每個Handler實例和一個線程以及那個線程的消息隊列相關聯。當你創建一個Handler時應該和一個Looper進行綁定(主線程默認已經創建Looper了,子線程需要自己創建Looper),它向Looper的對應的消息隊列傳送Message/Runnable同時在那個Looper所在線程處理對應的Message/Runnable。下面這張圖就是Handler的工作流程。

Handler工作流程圖

可以看到在Thread中,Looper的這個傳送帶其實就一個死循環,它不斷的從消息隊列MessageQueue中不斷的取消息,最後交給Handler.dispatchMessage進行消息的分發,而Handler.sendXXX,Handler.postXXX這些方法把消息發送到消息隊列中MessageQueue,整個模式其實就是一個生產者-消費者模式,源源不斷的生產消息,處理消息,沒有消息時進行休眠。MessageQueue是一個由單鏈表構成的優先級隊列(取的都是頭部,所以說是隊列)。

前面說過,當你創建一個Handler時應該和一個Looper進行綁定(綁定也可以理解爲創建,主線程默認已經創建Looper了,子線程需要自己創建Looper),因此我們先來看看主線程中是如何處理的:

//ActivityThread.java
public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();
        ···
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

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

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到在ActivityThread中的main方法中,我們先調用了Looper.prepareMainLooper()方法,然後獲取當前線程的Handler,最後調用Looper.loop()。先來看一下Looper.prepareMainLooper()方法。

//Looper.java  
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself.  See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
     prepare(false);
     synchronized (Looper.class) {
         if (sMainLooper != null) {
             throw new IllegalStateException("The main Looper has already been prepared.");
         }
         sMainLooper = myLooper();
     }
}
//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.prepareMainLooper()方法中創建了當前線程的Looper,同時將Looper實例存放到線程局部變量sThreadLocal(ThreadLocal)中,也就是每個線程有自己的Looper。在創建Looper的時候也創建了該線程的消息隊列,可以看到prepareMainLooper會判斷sMainLooper是否有值,如果調用多次,就會拋出異常,所以也就是說主線程的Looper和MessageQueue只會有一個。同理子線程中調用Looper.prepare()時,會調用prepare(true)方法,如果多次調用,也會拋出每個線程只能由一個Looper的異常,總結起來就是每個線程中只有一個Looper和MessageQueue。

//Looper.java
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

再來看看主線程sMainThreadHandler = thread.getHandler(),getHandler獲取到的實際上就是mH這個Handler。

//ActivityThread.java
final H mH = new H(); 
@UnsupportedAppUsage
    final Handler getHandler() {
      return mH;
}

mH這個Handler是ActivityThread的內部類,通過查看handMessage方法,可以看到這個Handler處理四大組件,Application等的一些消息,比如創建Service,綁定Service的一些消息。

//ActivityThread.java
class H extends Handler {
    ···

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case RECEIVER:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                handleReceiver((ReceiverData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case CREATE_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                handleCreateService((CreateServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case BIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                handleBindService((BindServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case UNBIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
                handleUnbindService((BindServiceData)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case SERVICE_ARGS:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
                handleServiceArgs((ServiceArgsData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case STOP_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
                handleStopService((IBinder)msg.obj);
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            ···
            case APPLICATION_INFO_CHANGED:
                mUpdatingSystemConfig = true;
                try {
                    handleApplicationInfoChanged((ApplicationInfo) msg.obj);
                } finally {
                    mUpdatingSystemConfig = false;
                }
                break;
            case RUN_ISOLATED_ENTRY_POINT:
                handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
                        (String[]) ((SomeArgs) msg.obj).arg2);
                break;
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                if (isSystem()) {
                    // Client transactions inside system process are recycled on the client side
                    // instead of ClientLifecycleManager to avoid being cleared before this
                    // message is handled.
                    transaction.recycle();
                }
                // TODO(lifecycler): Recycle locally scheduled transactions.
                break;
            case RELAUNCH_ACTIVITY:
                handleRelaunchActivityLocally((IBinder) msg.obj);
                break;
            case PURGE_RESOURCES:
                schedulePurgeIdler();
                break;
        }
        Object obj = msg.obj;
        if (obj instanceof SomeArgs) {
            ((SomeArgs) obj).recycle();
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
    }
}

最後我們查看Looper.loop()方法:

//Looper.java
public static void loop() {
      //獲取ThreadLocal中的Looper
    final Looper me = myLooper();
        ···
    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;
        }
                ···
        msg.target.dispatchMessage(msg);
                ···
        //回收複用  
        msg.recycleUnchecked();
    }
}

在loop方法中是一個死循環,在這裏從消息隊列中不斷的獲取消息queue.next(),然後通過Handler(msg.target)進行消息的分發,其實並沒有什麼具體的綁定,因爲Handler在每個線程中對應只有一個Looper和消息隊列MessageQueue,自然要靠它來處理,也就是是調用Looper.loop()方法。在Looper.loop()的死循環中不斷的取消息,最後回收複用。

這裏要強調一下Message中的參數target(Handler),正是這個變量,每個Message才能找到對應的Handler進行消息分發,讓多個Handler同時工作。

再來看看子線程中是如何處理的,首先在子線程中創建一個Handler併發送Runnable。

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
                    }
                });
            }
        }).start();

    }

運行後可以看到錯誤日誌,可以看到提示我們需要在子線程中調用Looper.prepare()方法,實際上就是要創建一個Looper和你的Handler進行“關聯”。

   --------- beginning of crash
2020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.jackie.testdialog, PID: 21122
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:207)
        at android.os.Handler.<init>(Handler.java:119)
        at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)
        at java.lang.Thread.run(Thread.java:919)

添加Looper.prepare()創建Looper,同時調用Looper.loop()方法開始處理消息。

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //創建Looper,MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
                    }
                });
                //開始處理消息
                Looper.loop();
            }
        }).start();
    }

這裏需要注意在所有事情處理完成後應該調用quit方法來終止消息循環,否則這個子線程就會一直處於循環等待的狀態,因此不需要的時候終止Looper,調用Looper.myLooper().quit()。

看完上面的代碼可能你會有一個疑問,在子線程中更新UI(進行Toast)不會有問題嗎,我們Android不是不允許在子線程更新UI嗎,實際上並不是這樣的,在ViewRootImpl中的checkThread方法會校驗mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構造器中,也就是說一個創建ViewRootImpl線程必須和調用checkThread所在的線程一致,UI的更新並非只能在主線程才能進行。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

這裏需要引入一些概念,Window是Android中的窗口,每個Activity和Dialog,Toast分別對應一個具體的Window,Window是一個抽象的概念,每一個Window都對應着一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯繫,因此,它是以View的形式存在的。我們來看一下Toast中的ViewRootImpl的創建過程,調用toast的show方法最終會調用到其handleShow方法。

//Toast.java
public void handleShow(IBinder windowToken) {
        ···
    if (mView != mNextView) {
        // Since the notification manager service cancels the token right
        // after it notifies us to cancel the toast there is an inherent
        // race and we may attempt to add a window after the token has been
        // invalidated. Let us hedge against that.
        try {
            mWM.addView(mView, mParams); //進行ViewRootImpl的創建
            trySendAccessibilityEvent();
        } catch (WindowManager.BadTokenException e) {
            /* ignore */
        }
    }
}

這個mWM(WindowManager)的最終實現者是WindowManagerGlobal,其的addView方法中會創建ViewRootImpl,然後進行root.setView(view, wparams, panelParentView),通過ViewRootImpl來更新界面並完成Window的添加過程。

//WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); //創建ViewRootImpl

    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // do this last because it fires off messages to start doing things
    try {
        //ViewRootImpl
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

setView內部會通過requestLayout來完成異步刷新請求,同時也會調用checkThread方法來檢驗線程的合法性。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

因此,我們的ViewRootImpl的創建是在子線程,所以mThread的值也是子線程,同時我們的更新也是在子線程,所以不會產生異常,同時也可以參考這篇文章分析,寫的非常詳細。同理下面的代碼也可以驗證這個情況。

//子線程中調用    
public void showDialog(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //創建Looper,MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        builder = new AlertDialog.Builder(HandlerActivity.this);
                        builder.setTitle("jackie");
                        alertDialog = builder.create();
                        alertDialog.show();
                        alertDialog.hide();
                    }
                });
                //開始處理消息
                Looper.loop();
            }
        }).start();
    }

在子線程中調用showDialog方法,先調用alertDialog.show()方法,再調用alertDialog.hide()方法,hide方法只是將Dialog隱藏,並沒有做其他任何操作(沒有移除Window),然後再在主線程調用alertDialog.show();便會拋出Only the original thread that created a view hierarchy can touch its views異常了。

2020-11-09 18:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.jackie.testdialog, PID: 24819
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
        at android.view.View.requestLayout(View.java:24454)
        at android.view.View.setFlags(View.java:15187)
        at android.view.View.setVisibility(View.java:10836)
        at android.app.Dialog.show(Dialog.java:307)
        at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
        at android.view.View.performClick(View.java:7125)
        at android.view.View.performClickInternal(View.java:7102)

所以在線程中更新UI的重點是創建它的ViewRootImpl和checkThread所在的線程是否一致。

如何在主線程中訪問網絡

在網絡請求之前添加如下代碼:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);

StrictMode(嚴苛模式)Android2.3引入,用於檢測兩大問題:ThreadPolicy(線程策略)和VmPolicy(VM策略),這裏把嚴苛模式的網絡檢測關了,就可以在主線程中執行網絡操作了,一般是不建議這麼做的。

系統爲什麼不建議在子線程中訪問UI?

這是因爲 Android 的UI控件不是線程安全的,如果在多線程中併發訪問可能會導致UI控件處於不可預期的狀態,那麼爲什麼系統不對UI控件的訪問加上鎖機制呢?缺點有兩個:

  1. 首先加上鎖機制會讓UI訪問的邏輯變得複雜
  2. 鎖機制會降低UI訪問的效率,因爲鎖機制會阻塞某些線程的執行。

所以最簡單且高效的方法就是採用單線程模型來處理UI操作。(安卓開發藝術探索)

子線程如何通知主線程更新UI(都是通過Handle發送消息到主線程操作UI的)

  1. 主線程中定義 Handler,子線程通過 mHandler 發送消息,主線程 Handler 的 handleMessage 更新UI。
  2. 用 Activity 對象的 runOnUiThread 方法。
  3. 創建 Handler,傳入 getMainLooper。
  4. View.post(Runnable r) 。

Looper死循環爲什麼不會導致應用卡死,會耗費大量資源嗎?

從前面的主線程、子線程的分析可以看出,Looper會在線程中不斷的檢索消息,如果是子線程的Looper死循環,一旦任務完成,用戶應該手動退出,而不是讓其一直休眠等待。(引用自Gityuan)線程其實就是一段可執行的代碼,當可執行的代碼執行完成後,線程的生命週期便該終止了,線程退出。而對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder 線程也是採用死循環的方法,通過循環方式不同與 Binder 驅動進行讀寫操作,當然並非簡單地死循環,無消息時會休眠。Android是基於消息處理機制的,用戶的行爲都在這個Looper循環中,我們在休眠時點擊屏幕,便喚醒主線程繼續進行工作。

主線程的死循環一直運行是不是特別消耗 CPU 資源呢?其實不然,這裏就涉及到 Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法裏,此時主線程會釋放 CPU 資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往 pipe 管道寫端寫入數據來喚醒主線程工作。這裏採用的 epoll 機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。所以說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

主線程的Looper何時退出

在App退出時,ActivityThread中的mH(Handler)收到消息後,執行退出。

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

如果你嘗試手動退出主線程Looper,便會拋出如下異常。

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
    at android.os.MessageQueue.quit(MessageQueue.java:428)
    at android.os.Looper.quit(Looper.java:354)
    at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)
    at android.app.Activity.performCreate(Activity.java:7802)
    at android.app.Activity.performCreate(Activity.java:7791)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
    at android.os.Handler.dispatchMessage(Handler.java:107) 
    at android.os.Looper.loop(Looper.java:214) 
    at android.app.ActivityThread.main(ActivityThread.java:7356) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

爲什麼不允許退出呢,因爲主線程不允許退出,一旦退出就意味着程序掛了,退出也不應該用這種方式退出。

Handler的消息處理順序

在Looper執行消息循環loop()時會執行下面這行代碼,msg.targe就是這個Handler對象。

msg.target.dispatchMessage(msg);

我們來看看dispatchMessage的源碼:

  public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果 callback 處理了該 msg 並且返回 true, 就不會再回調 handleMessage
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

1.如果Message這個對象有CallBack回調的話,這個CallBack實際上是個Runnable,就只執行這個回調,然後就結束了,創建該Message的CallBack代碼如下:

Message msgCallBack = Message.obtain(handler, new Runnable() {
    @Override
    public void run() {
    }
});

而handleCallback方法中調用的是Runnable的run方法。

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

2.如果Message對象沒有CallBack回調,進入else分支判斷Handler的CallBack是否爲空,不爲空執行CallBack的handleMessage方法,然後return,構建Handler的CallBack代碼如下:

Handler.Callback callback = new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
          //retrun true,就不執行下面的邏輯了,可以用於做優先級的處理
        return false;
    }
};

3.最後才調用到Handler的handleMessage()函數,也就是我們經常去重寫的函數,在該方法中做消息的處理。

使用場景

可以看到Handler.Callback 有優先處理消息的權利 ,當一條消息被 Callback 處理並攔截(返回 true),那麼 Handler 的 handleMessage(msg) 方法就不會被調用了;如果 Callback 處理了消息,但是並沒有攔截,那麼就意味着一個消息可以同時被 Callback 以及 Handler 處理。我們可以利用CallBack這個攔截來攔截Handler的消息。

場景:Hook ActivityThread.mH , 在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎所有的插件化框架都使用了這個方法。

Handler.post(Runnable r)方法的執行邏輯

我們需要分析平時常用的Handler.post(Runnable r)方法是如何執行的,是否新創建了一個線程了呢,實際上並沒有,這個Runnable對象只是被調用了它的run方法,根本並沒有啓動一個線程,源碼如下:

//Handler.java
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

最終該Runnable對象被包裝成一個Message對象,也就是這個Runnable對象就是該Message的CallBack對象了,有優先執行的權利了。

Handler是如何進行線程切換的

原理很簡單,線程間是共享資源的,子線程通過handler.sendXXX,handler.postXXX等方法發送消息,然後通過Looper.loop()在消息隊列中不斷的循環檢索消息,最後交給handle.dispatchMessage方法進行消息的分發處理。

如何處理Handler使用不當造成的內存泄漏?

  1. 有延時消息,在界面關閉後及時移除Message/Runnable,調用handler.removeCallbacksAndMessages(null)
  2. 內部類導致的內存泄漏改爲靜態內部類,並對上下文或者Activity/Fragment使用弱引用。

同時還有一個很關鍵的點,如果有個延時消息,當界面關閉時,該Handler中的消息還沒有處理完畢,那麼最終這個消息是怎麼處理的?經過測試,比如我打開界面後延遲10s發送消息,關閉界面,最終在Handler(匿名內部類創建的)的handMessage方法中還是會收到消息(打印日誌)。因爲會有一條MessageQueue -> Message -> Handler -> Activity的引用鏈,所以Handler不會被銷燬,Activity也不會被銷燬。

正確創建Message實例

  1. 通過 Message 的靜態方法 Message.obtain() 獲取;
  2. 通過 Handler 的公有方法 handler.obtainMessage()

所有的消息會被回收,放入sPool中,使用享元設計模式。

面試複習路線

多餘的話就不講了,接下來將分享面試的一個複習路線,如果你也在準備面試但是不知道怎麼高效複習,可以參考一下我的複習路線,有任何問題也歡迎一起互相交流,加油吧!

這裏給大家提供一個方向,進行體系化的學習:

1、看視頻進行系統學習

前幾年的Crud經歷,讓我明白自己真的算是菜雞中的戰鬥機,也正因爲Crud,導致自己技術比較零散,也不夠深入不夠系統,所以重新進行學習是很有必要的。我差的是系統知識,差的結構框架和思路,所以通過視頻來學習,效果更好,也更全面。關於視頻學習,個人可以推薦去B站進行學習,B站上有很多學習視頻,唯一的缺點就是免費的容易過時。

另外,我自己也珍藏了好幾套視頻,有需要的我也可以分享給你。

2、進行系統梳理知識,提升儲備

客戶端開發的知識點就那麼多,面試問來問去還是那麼點東西。所以面試沒有其他的訣竅,只看你對這些知識點準備的充分程度。so,出去面試時先看看自己複習到了哪個階段就好。

系統學習方向:

  • 架構師築基必備技能:深入Java泛型+註解深入淺出+併發編程+數據傳輸與序列化+Java虛擬機原理+反射與類加載+動態代理+高效IO

  • Android高級UI與FrameWork源碼:高級UI晉升+Framework內核解析+Android組件內核+數據持久化

  • 360°全方面性能調優:設計思想與代碼質量優化+程序性能優化+開發效率優化

  • 解讀開源框架設計思想:熱修復設計+插件化框架解讀+組件化框架設計+圖片加載框架+網絡訪問框架設計+RXJava響應式編程框架設計+IOC架構設計+Android架構組件Jetpack

  • NDK模塊開發:NDK基礎知識體系+底層圖片處理+音視頻開發

  • 微信小程序:小程序介紹+UI開發+API操作+微信對接

  • Hybrid 開發與Flutter:Html5項目實戰+Flutter進階

知識梳理完之後,就需要進行查漏補缺,所以針對這些知識點,我手頭上也準備了不少的電子書和筆記,這些筆記將各個知識點進行了完美的總結。

3、讀源碼,看實戰筆記,學習大神思路

“編程語言是程序員的表達的方式,而架構是程序員對世界的認知”。所以,程序員要想快速認知並學習架構,讀源碼是必不可少的。閱讀源碼,是解決問題 + 理解事物,更重要的:看到源碼背後的想法;程序員說:讀萬行源碼,行萬種實踐。

主要內含微信 MMKV 源碼、AsyncTask 源碼、Volley 源碼、Retrofit源碼、OkHttp 源碼等等。

4、面試前夕,刷題衝刺

面試的前一週時間內,就可以開始刷題衝刺了。請記住,刷題的時候,技術的優先,算法的看些基本的,比如排序等即可,而智力題,除非是校招,否則一般不怎麼會問。

關於面試刷題,我個人也準備了一套系統的面試題,幫助你舉一反三:

以上內容均免費分享給大家,需要完整版的朋友,點這裏可以看到全部內容。或者關注主頁掃描加 微信 獲取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章