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、面試前夕,刷題衝刺
面試的前一週時間內,就可以開始刷題衝刺了。請記住,刷題的時候,技術的優先,演算法的看些基本的,比如排序等即可,而智力題,除非是校招,否則一般不怎麼會問。
關於面試刷題,我個人也準備了一套系統的面試題,幫助你舉一反三: