Handler是Android中的消息處理機制,是一種線程間通信的解決方案,同時你也可以理解爲它天然的爲我們在主線程創建一個隊列,隊列中的消息順序就是我們設置的延遲的時間,如果你想在Android中實現一個隊列的功能,不妨第一時間考慮一下它。本文分爲三部分:
Handler的源碼和常見問題的解答
- 一個線程中最多有多少個Handler,Looper,MessageQueue?
- Looper死循環爲什麼不會導致應用卡死,會耗費大量資源嗎?
- 子線程的如何更新UI,比如Dialog,Toast等?系統爲什麼不建議子線程中更新UI?
- 主線程如何訪問網絡?
- 如何處理Handler使用不當造成的內存泄漏?
- Handler的消息優先級,有什麼應用場景?
- 主線程的Looper何時退出?能否手動退出?
- 如何判斷當前線程是安卓主線程?
- 正確創建Message實例的方式?
Handler深層次問題解答
- ThreadLocal
- epoll機制
- Handle同步屏障機制
- Handler的鎖相關問題
- Handler中的同步方法
Handler在系統以及第三方框架的一些應用
- HandlerThread
- IntentService
- 如何打造一個不崩潰的APP
- 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控件的訪問加上鎖機制呢?缺點有兩個:
- 首先加上鎖機制會讓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.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使用不當造成的內存泄漏?
- 有延時消息,在界面關閉後及時移除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虛擬機原理+反射與類加載+動態代理+高效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、面試前夕,刷題衝刺
面試的前一週時間內,就可以開始刷題衝刺了。請記住,刷題的時候,技術的優先,算法的看些基本的,比如排序等即可,而智力題,除非是校招,否則一般不怎麼會問。
關於面試刷題,我個人也準備了一套系統的面試題,幫助你舉一反三:
以上內容均免費分享給大家,需要完整版的朋友,點這裏可以看到全部內容。或者關注主頁掃描加 微信 獲取。