言不必当,极口称是,
未交此人,故意底毁;
卑庸可耻,不足与论事。
——《冰鉴》
1. 背景
2020年注定是不平凡的一年,“金三银四”怕是被疫情给变了性质,希望面试者都能进入自己心仪的公司。
今天面试了一个5年左右的Android开发者,感觉java基础和Android知识都比较不错。在面试Android岗位时,Handler总是绕不开的一个话题 (PS:如果一切顺利,就不存在这篇文章啦)。
下面是我提问的问题:
- 大致讲一下Handler的工作原理;
- 一个线程可以有多个Looper吗? 怎么保证的?
- send 和 post 这两种发送消息的方式(刚才面试者说到本质上post还是使用的send方式),你认为两者在使用场景上有什么不同?
晴天霹雳…
原来还有人认为Handler.post(Runnable) 这种方式是处理异步的,这是典型的形式主义错误。
2.再谈post方式
Handler的post发送消息不是处理异步消息的。
2.1 post方式不是异步
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
// post 方式发送的消息
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
通过上述代码,我们发现此处并没有出现Thread的身影,更没有出现线程启动的start()。这足以证明post方式是同步方式,其Runnable中的代码执行和Handler所在的线程一致。(PS:可以联想Thread 不启动start,直接调用run()的逻辑)
2.1 send 和 post方式的使用场景
2.1.1 send方式
结合我们在开发中的使用场景,如果我们发送消息的类型比较多,我们一般更倾向于使用send方式 ,然后结合下面的代码根据情况判断执行逻辑。(PS : 如果阅读过源码,我们会发现源码中很多使用该种方式的)
private static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
//TODO
break;
case 1:
// TODO
break;
...
}
}
};
2.1.2 post方式
与其认为Post Runnable,倒不如直接理解为Post 出 代码块。
如果消息类型只有1个,使用上述代码就显得不太合理啦。这时候就体现出post的快捷方便啦。
new Handler().post(new Runnable() {
@Override
public void run() {
//TODO
}
});
相信大家在实际的开发中,也能体会到两者用法上的稍许区别。
一定不要理解错误,即:不要在Handler.post() 或者 Handler.postDelay()中做耗时操作。
3. Handler 的内存泄漏
3.1 什么是内存泄漏?
简而言之,申请的内存,用完不能如数归还甚至(无法归还)
假如整个内存就是一家银行(这里的 “借” 指的是无息贷款啊)
- 正常现象: 借多少及时还多少;
- 内存泄漏:借多少还一部分(甚至不还) [不及时还款照样是内存泄漏],(银行倒闭了你才有偿还能力那还有个屁用)
- 内存溢出:没有人能从银行借出来钱,即 银行倒闭
内存泄漏的最终结果就是内存溢出(Out of memory)
点击至:知乎高赞
3.2 Handler的内存泄漏
在面试过程中,我发现有很大一部分的面试者,对于Handler的内存泄漏存在认识不足的情况,这里特地总结一下,希望能够帮助大家。
这里不再对GC相关知识展开介绍。
Handler的内存泄漏问题主要发生在两个场景:
- (匿名)内部类默认持有外部类的引用;
- 逻辑漏洞导致;
这里简要分析一下:
场景1: 在开发中存在这两种写法(PS:AS会自动提示我们可能发生内存泄漏)
This Handler class should be static or leaks might occur
a.匿名内部类的形式
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// TODO
}
};
对于a形式,我的同事曾经直接在变量前加上static关键字,然后底气十足的告诉我这样可以吧… (有此想法的童鞋,麻烦再翻译一下上面那段英文,关键是AS的提示依然健在)
b.内部类的形式
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// TODO
}
}
相信大家总会遇见各种奇葩队友,他知道将内部类声明为静态的,所以出现了下面的局面(场面一度失控)
public class TestHandlerActivity extends AppCompatActivity {
// 这里应该是不得已而为之,将其声明为静态变量
private static TextView tvHello;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_handler);
tvHello = findViewById(R.id.tv_hello);
}
// 注意这里:确实是静态内部类
static class TestHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tvHello.setText("Hello");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 补救措施
tvHello = null;
}
}
这种写法就会导致所有的View全部要声明为static类型。最可恨的是:内存泄漏的问题将会更加严重。
所有的View在初始化的时候,都会有Context,即当前Activity。
此时将View设置为static,若Activity生命周期结束后,该静态变量没有被回收,则持有Activity,将导致Activity无法被回收。所以内存泄漏就出现啦。
当然有解决办法:在onDestory()中将该变量置为空即可。
如果View过多,鬼见愁啊…
建议大家深入了解static关键字的含义
关于更多的内存泄漏场景:Android 内存优化
公布最佳答案:
弱引用(WeakReference) : GC执行则必被回收。
关于Java的四种引用类型:强、软、弱、虚
static class MyHandler extends Handler {
WeakReference<Activity> weakReference;
public MyHandler(Activity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
TestHandlerActivity activity = (TestHandlerActivity) weakReference.get();
// 有人对此有疑问
if(activity != null){
activity.tvHello.setText("Hello");
}
}
}
可能有的同学会存在疑惑?如果GC回收了Activity,那更新UI的操作是不是就出问题啦。
这里我们要明确一点,大家要搞明白在什么情况下该Activity才会被回收,自然就明白啦。(PS : 就好比银行让你还款也是在还款日让你还而不是随便的让你还啊)
上面这种场景的引用链
执行耗时任务的线程 ----> Handler ----> Activity
该线程一定持有Handler的引用,否则无法发消息。
场景2: 逻辑漏洞问题
想象一下,银行已经下班啦(Activity被销毁),
有些人还在赶往银行的路上——Handler的延时功能 sendMessageDelayed() 和 postDelayed()
另一些人还在银行门口的排队呢—— MessageQueue中的待处理消息
引用链:
MessageQueue —> Message —> Handler —> Activity
解决办法:
@Override
protected void onDestroy() {
super.onDestroy();
// 移除所有消息
if(handler != null){
handler.removeCallbacksAndMessages(null);
handler = null;
}
}