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),因此我们先来看看主线程中是如何处理的:
代码语言:javascript复制//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()方法。
代码语言:javascript复制//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。
代码语言:javascript复制//Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
再来看看主线程sMainThreadHandler = thread.getHandler(),getHandler获取到的实际上就是mH这个Handler。
代码语言:javascript复制//ActivityThread.java
final H mH = new H();
@UnsupportedAppUsage
final Handler getHandler() {
return mH;
}
mH这个Handler是ActivityThread的内部类,通过查看handMessage方法,可以看到这个Handler处理四大组件,Application等的一些消息,比如创建Service,绑定Service的一些消息。
代码语言:javascript复制//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()方法:
代码语言:javascript复制//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。
代码语言:javascript复制@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进行“关联”。
代码语言:javascript复制 --------- 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()方法开始处理消息。
代码语言:javascript复制 @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的更新并非只能在主线程才能进行。
代码语言:javascript复制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方法。
代码语言:javascript复制//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的添加过程。
代码语言:javascript复制//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方法来检验线程的合法性。
代码语言:javascript复制@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
因此,我们的ViewRootImpl的创建是在子线程,所以mThread的值也是子线程,同时我们的更新也是在子线程,所以不会产生异常,同时也可以参考这篇文章分析,写的非常详细。同理下面的代码也可以验证这个情况。
代码语言:javascript复制//子线程中调用
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异常了。
代码语言:javascript复制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所在的线程是否一致。
如何在主线程中访问网络
在网络请求之前添加如下代码:
代码语言:javascript复制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)收到消息后,执行退出。
代码语言:javascript复制//ActivityThread.java
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
如果你尝试手动退出主线程Looper,便会抛出如下异常。
代码语言:javascript复制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对象。
代码语言:javascript复制msg.target.dispatchMessage(msg);
我们来看看dispatchMessage的源码:
代码语言:javascript复制 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代码如下:
代码语言:javascript复制Message msgCallBack = Message.obtain(handler, new Runnable() {
@Override
public void run() {
}
});
而handleCallback方法中调用的是Runnable的run方法。
代码语言:javascript复制private static void handleCallback(Message message) {
message.callback.run();
}
2.如果Message对象没有CallBack回调,进入else分支判断Handler的CallBack是否为空,不为空执行CallBack的handleMessage方法,然后return,构建Handler的CallBack代码如下:
代码语言:javascript复制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方法,根本并没有启动一个线程,源码如下:
代码语言:javascript复制//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、面试前夕,刷题冲刺
面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。
关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三。
以上内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。