一、面试必问
1、Handler是什么
Handler是Android中的异步消息处理机制。当发送一个消息之后,这个消息是进入一个消息队列(MessageQueue),在消息队列中通过Looper去循环的获取队列中的消息,然后将消息分派给对应的处理者进行处理。
线程一旦被创建就会生成一个Looper对象,有且仅有一个。每个应用在运行的时候都会创建一个主线程(mainThread)。
主线程不能做耗时操作,子线程不能更新UI。
UIThread通常就是mian thread,而Android启动程序的时候就会替它建立一个MessageQueue。
2、Handler四模块
Message:存储需要处理操作的信息
MessageQueue:先进先出,存储handler发送过来的消息
Looper:循环器,它是消息队列和handler的通信媒介,
1:循环的取出消息队列中的消息;
2:将取出的消息发送给对应的处理者
Handler:主线程和子线程的通信媒介,
1:添加消息到消息队列;
2:处理循环器分派过来的消息
3、为什么不可在子线程中更新UI?
因为Android的UI控件不是线程安全的,如果在多线程并发访问UI控件可能会导致不可预期的状态。
4、为什么不对UI控件添加安全锁机制?
锁机制会使访问UI逻辑复杂,并且降低UI的访问效率,锁会阻塞某些线程的执行。
5、Handler为何使用管道?
同进程线程间内存共享,通过handler通信,消息的内容是不需要从一个线程拷贝到另一个线程,因为两个线程间可使用的内存是同一个区域。(注意:线程私有区域ThreadLocal)
管道的作用就是当一个线程准备好Message,并放入消息池,这时需要通知了一个线程B去处理这个消息。线程A向管道的写端写入数据,管道有数据便会唤醒线程B去处理消息。管道的作用是用于通知另一个线程的,这便是最核心的作用。
6、Handler为何使用管道而非binder
从内存角度,通信过程中binder涉及到一次内存拷贝,handler机制中的Message根本不需要拷贝,本身就是在同一片内存。
从CPU角度,为了Binder通信底层驱动还需要创建一个binder线程池,每次通信涉及binder线程的创建和内存的分配等比较浪费CPU资源
7、handler导致内存泄漏的原因
原因:handler发送的消息在当前handler的消息队列中,如果此时activity被finish掉了,那么消息队列的消息依旧由handler进行处理,若此时handler申明为内存类(非静态内部类),内部类持有外部类的实例引用,这样在GC垃圾回收时发现Activity还有其他引用存在,因而就不会去回首这个Activity,进而导致Activity泄漏。
方法:使用静态内部类,并且使用WeakReference包裹外部类的对象。首先静态内部类不持有外部类的引用,使用静态的handler不会导致activity的泄漏,handler定义static的同时,还要用WeakReference包裹外部类的对象。
MyHandler handler = new MyHandler(this);
public static class MyHandler extends Handler {
private WeakReference<MainActivity> reference;
public MyHandler(MainActivity activity) {
reference = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i("test",textView.getText().toString());
break;
default:
break;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列
handler.removeCallbacks(postRunnable); //清除runnable对应的message
}
8、Looper死循环为什么不会导致应用卡死?
- 卡死就是ANR,产生的原因有2个:
1、在5s内没有响应输入的事件(例如按键,触摸等),
2、BroadcastReceiver在10s内没有执行完毕。 - 事实上我们所有的Activity和Service都是运行在loop()函数中,以消息的方式存在,所以在没有消息产生的时候,looper会被block(阻塞),主线程会进入休眠,一旦有输入事件或者Looper添加消息的操作后主线程就会被唤醒,从而对事件进行响应,所以不会导致ANR
- 简单来说looper的阻塞表明没有事件输入,而ANR是由于有事件没响应导致,所以looper的死循环并不会导致应用卡死。
9、子线程中维护的Looper,如何终止消息循环?有什么用?
- 如果不处理的话,会阻塞线程,处理方案是调用Looper的quitSafely();
- quitSafely()会调用MessageQueue的quit()方法,清空所有的Message,并调用nativeWake()方法唤醒之前被阻塞的
- nativePollOnce(),使得方法next()方法中的for循环继续执行,接下来发现Message为null后就会结束循环,Looper结束。如此便可以释放内存和线程。
二、代码示例
使用 Handler 机制,首先需要创建一个Handler 对象,可以直接使用Handler 无参构造函创建Handler 对象,或者是继承Handler类,重写 handleMessage(Message msg)方法来创建handler对象。Google 官方提供了一个推荐的使用方式,代码如下∶
Class LooperThread extends Thread {
public Handler mHandler;
public void run(){
Looper.prepare();
mHandler = new Handler(){
public void handleMessage(Message msg){
// process incoming messages here
}
};
Looper.loop();
}
}
通过上一部分的分析,读者应该能够很容易理解上面这种方式。但是在实际的开发实践中,大部分的Handler 对象都是在主线程中创建的,此时已经存在了Looper 对象,并不需要调用Loopr.prepare()与 Looper.loop()方法,直接构建一个Handler 对象即可∶
//调用
new Thread(new MyThread()).start();
// handle
private final Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what) {
case 1:
//更新UI
}
}
super.handleMessage(msg);
}
};
// thread
private class MyThread implements Runnable{
@Override
public void run(){
while(true){
try{
Thread.sleep(60*1000);//延时操作
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}catch (Exception e) {
}
}
}
}
获取 Message 大概有如下几种方式。推荐使用前两种方式进行创建,原因是不需要重复去新建Message,可以节省内存空间。
Message message = myHandler.obtainMessage(); //通过 Handler 实例获取
Message message1 = Message.obtain(); //通过 Message 获取
Message message2 = new Message(); //直接创建新的 Message 实例