首頁>技術>

Handler是Android中的訊息處理機制,是一種執行緒間通訊的解決方案,同時你也可以理解為它天然的為我們在主執行緒建立一個佇列,佇列中的訊息順序就是我們設定的延遲的時間,如果你想在Android中實現一個佇列的功能,不妨第一時間考慮一下它。本文分為三部分:

Handler的原始碼和常見問題的解答

一個執行緒中最多有多少個Handler,Looper,MessageQueue?Looper死迴圈為什麼不會導致應用卡死,會耗費大量資源嗎?子執行緒的如何更新UI,比如Dialog,Toast等?系統為什麼不建議子執行緒中更新UI?主執行緒如何訪問網路?如何處理Handler使用不當造成的記憶體洩漏?Handler的訊息優先順序,有什麼應用場景?主執行緒的Looper何時退出?能否手動退出?如何判斷當前執行緒是安卓主執行緒?正確建立Message例項的方式?

Handler深層次問題解答

ThreadLocalepoll機制Handle同步屏障機制Handler的鎖相關問題Handler中的同步方法

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

HandlerThreadIntentService如何打造一個不崩潰的APPGlide中的運用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.javapublic 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();     }}//prepareprivate 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.javaprivate Looper(boolean quitAllowed) {    mQueue = new MessageQueue(quitAllowed);    mThread = Thread.currentThread();}

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

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

mH這個Handler是ActivityThread的內部類,透過檢視handMessage方法,可以看到這個Handler處理四大元件,Application等的一些訊息,比如建立Service,繫結Service的一些訊息。

//ActivityThread.javaclass 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.javapublic 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 crash2020-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.javapublic 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.javaroot = 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方法來檢驗執行緒的合法性。

@Overridepublic 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控制元件的訪問加上鎖機制呢?缺點有兩個:

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

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

子執行緒如何通知主執行緒更新UI(都是透過Handle傳送訊息到主執行緒操作UI的)主執行緒中定義 Handler,子執行緒透過 mHandler 傳送訊息,主執行緒 Handler 的 handleMessage 更新UI。用 Activity 物件的 runOnUiThread 方法。建立 Handler,傳入 getMainLooper。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.javacase 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.javapublic 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使用不當造成的記憶體洩漏?有延時訊息,在介面關閉後及時移除Message/Runnable,呼叫handler.removeCallbacksAndMessages(null)內部類導致的記憶體洩漏改為靜態內部類,並對上下文或者Activity/Fragment使用弱引用。

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

正確建立Message例項透過 Message 的靜態方法 Message.obtain() 獲取;透過 Handler 的公有方法 handler.obtainMessage()

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

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

1、看影片進行系統學習

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

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

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

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

系統學習方向:

架構師築基必備技能:深入Java泛型+註解深入淺出+併發程式設計+資料傳輸與序列化+Java虛擬機器原理+反射與類載入+動態代理+高效IOAndroid高階UI與FrameWork原始碼:高階UI晉升+Framework核心解析+Android元件核心+資料持久化360°全方面效能調優:設計思想與程式碼質量最佳化+程式效能最佳化+開發效率最佳化解讀開源框架設計思想:熱修復設計+外掛化框架解讀+元件化框架設計+圖片載入框架+網路訪問框架設計+RXJava響應式程式設計框架設計+IOC架構設計+Android架構元件JetpackNDK模組開發:NDK基礎知識體系+底層圖片處理+音影片開發微信小程式:小程式介紹+UI開發+API操作+微信對接Hybrid 開發與Flutter:Html5專案實戰+Flutter進階

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

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

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

4、面試前夕,刷題衝刺

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

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

23
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • SpringCloud開發課程查詢功能