1、什么是Handler,为什么要有Handler?
Android中主线程也叫UI线程,主线程主要是用来创建、更新UI的。而其他耗时操作,比如网络访问、文件处理、多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢失帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。
2、Handler的使用方法
post(runnable)
sendMessage(message)
其实post(runnable)和sendMessage(message)最终底层都是调用了sendMessageAtTime方法。
3、Handler消息机制中涉及到哪些类,各自的功能是什么?
Handler主要用于跨线程通信。涉及MessageQueue、Message、Looper、Handler这4个类。
Message:消息对象。对象的内部实现是单列链表,最大长度是50,用于缓存消息对象,达到重复利用消息对象的目的,以减少消息对象的创建。
MessageQueue:消息队列。主要功能是向消息池投递信息(MessageQueue.enqueueMessage)和取走消息池的信息(MessageQueue.next) 。
Handler:消息对象的发送者和处理者。负责向消息池中发送消息(Handler.enqueueMessage)和处理消息(Handler.handleMessage) 。
Looper:消息队列的处理者。用于轮询消息队列里面的消息对象,不断从MessageQueue队列中轮询取出Message,交由handler的dispatchMessage()方法进行消息的分发,dispatchMessage方法再调用handleMessage()方法进行消息回调。
它们之间的类关系:Looper里面有一个MessageQueue消息队列,MessageQueue有一组待处理的Message,Message中有一个用于处理消息的Handler,Handler中有Looper和MessageQueue。
4、Handler消息机制的原理
在应用启动时,ActivityThread类的main方法里面初始化调用Looper.preperMainLooper方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中(ThreadLocal)。当调用Handler的sendMessage方法的时候,就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。由于在main方法里面初始化时执行了Looper.loop()方法,该方法从Looper的成员变量MessageQueue队列中不断轮询取出Message,交由handler的dispatchMessage()方法进行消息的分发,dispatchMessage方法再调用handleMessage()方法进行消息回调,这样就完成了整个消息机制。
5、Handler线程安全问题
对于子线程访问主线程的Handler对象,你可能会问,多个子线程都访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据的不一致呢?答案是不会出现数据的不一致问题。因为Handler对象通过ThreadLocal管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息都是通过synchronized同步保护的,所以不会出现数据不一致现象。
6、Handler引起的内存泄漏以及解决办法
原因:
非静态内部类持有外部类的强引用,导致外部Activity无法释放。
解决办法:
1、handler内部持有外部activity的弱引用
2、把handler改为静态内部类
3、mHandler.removeCallbacksAndMessages
//代码示例
public class MainActivity extends AppCompatActivity {
//创建静态内部类
private static class MyHandler extends Handler{
//持有弱引用MainActivity,GC回收时会被回收掉.
private final WeakReference mAct;
public MyHandler(MainActivity mainActivity) {
mAct = new WeakReference(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainAct = mAct.get();
super.handleMessage(msg);
if(mainAct != null){
//执行业务逻辑
}
}
}
private static final Runnable myRunnable = new Runnable() {
@Override
public void run() {
//执行业务逻辑
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler myHandler=new MyHandler(this);
//通过postDelayed延迟发送
//myHandler.postDelayed(myRunnable, 1000 * 5);
//通过sendMessageDelayed延迟发送
Message message = Message.obtain();
message.what = 1;
myHandler.sendMessageDelayed(message, 1000 * 5);
}
}
7、一个线程可以有几个Looper、几个MessageQueue和几个Handler?
在Android中,Looper类利用了ThreadLocal的特性,保证了每个线程只存在一个Looper对象。
static final ThreadLocal sThreadLocal = new ThreadLocal();
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构造函数中创建了MessageQueue 对象,因此一个线程只有一个MessageQueue。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以有多个Handler。
Handler在创建时将Looper和MessageQueue关联起来:
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
...
}
8、可以在子线程直接创建一个Handler吗?会出现什么问题,那该怎么做?
不能在子线程直接new一个Handler。因为Handler的工作依赖于Looper,而Looper又是属于某一个线程的,其他线程不能访问,所以在线程中使用Handler时必须要保证当前线程中Looper对象创建并且启动循环。不然会抛出异常throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");
正确做法是:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();//为线程创建Looper对象
mHandler = new Handler() {
public void handleMessage(Message msg) {
}
};
Looper.loop();//启动消息循环
}
}
9、既然线程中创建Handler时需要Looper对象,为什么主线程不用调用Looper.prepare()创建Looper对象?
在应用启动时,ActivityThread类的main方法里面调用了Looper.prepareMainLooper()方法,Looper.prepareMainLooper()方法里面调用了Looper.prepare()方法,方法里面将Looper对象set到sThreadLocal对象当中。
10、Looper死循环为什么不会导致应用卡死,会消耗大量资源吗?
对于线程执行一段可执行代码,当可执行代码执行完成后,线程生命周期便终止了,线程退出。而对于主线程,我们是绝不希望运行一段时间后,自己就退出了,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,Binder线程也是采用死循环的方法,通过循环方式与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,Looper.loop()本身不会导致应用卡死。
主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在Looper的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
11、MessageQueue是队列吗?它是什么数据结构?
MessageQueue不是队列,它内部使用一个Message链表来实现消息的存和取。 链表的排列依据是Message.when,表示Message期望被分发的时间,该值是SystemClock. uptimeMillis()与delayMillis之和。
12、handler.postDelayed()函数延时执行计时是否准确?
当上一个消息存在耗时任务的时候,会占用延时任务执行的时机,实际延迟时间可能会超过预设延时时间,这时候就不准确了。
13、handler发送延时消息是怎么处理的?
根据消息队列入队规制,如果队列中没消息,那么不管要入队的消息有没有延时,都放到队列头。如果队列中有消息,那么要跟队列头的消息比较一下延时,如果要入队的消息延时短,则放队列头,否则,放到队列中去,需要移动链表。
入队规制的好处是,延时越长的消息在队列越后面,所以next方法取到一个延时消息时,如果判断时间没有到,就进行阻塞,不用管后面的消息,因为队列后面的消息延迟时间更长。
14、你了解HandlerThread吗?
HandlerThread继承自Thread,它是一种可以使用Handler的Thread,它的实现也很简单,在run方法中也是通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环(与我们手动创建方法基本一致),这样在实际的使用中就允许在HandlerThread中创建Handler了。
public class HandlerThread extends Thread {
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
由于HandlerThread的run方法是一个无限循环,因此当不需要使用的时候通过quit或者quitSafely方法来终止线程的执行。