1. 一个线程可以创建几个Handler?创建Looper的两种方式?
一个线程可以创建多个Handler。
一般是在主线程中实现一个Handler,然后在子线程中使用它。
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子线程中通过自定义的 Handler 发消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
或者有时候需要在子线程中创建运行在主线程中的Handler
class HandlerActivity: AppCompatActivity() {
private var mHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
thread {
//获得main looper 运行在主线程
mHandler = MyHandler(Looper.getMainLooper())
mHandler!!.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定义一个 Handler
class MyHandler(): Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "子线程:handleMessage: ${msg.what}")
}
}
}
2. 一个线程有几个looper,几个message,几个messageQueue怎么保证?Looper的工作流程。
一个线程只有一个looper,一个message。
looper在Handler初始化的时候获取looper:mLooper = Looper.myLooper();在这一句中Handler通过Looper.myLooper方法获取到了Looper对象,那我们看看这个Looper.myLooper()方法做了什么事情呢。它是如何返回一个Looper对象的呢?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal提供了线程的局部变量,每个线程可以通过set()和get()方法来对这个局部变量进行操作,但是不会和其他线程的局部变量产生冲突,实现了线程的数据隔离,ThreadLocal中填充的变量是属于当前线程的,该变量对于其他线程而言是隔离的。
public static void prepare() {
prepare(true);
}
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));
}
可以看得出,最后调用了是prepare(boolean quitAllowed)方法,而这个方法首先判断,如果sThreadLocal有值,就抛异常,没有值才会塞进去一个值。其实很好理解,就是说prepare方法必须调用但也只能调用一次,不调用没有值,抛异常,调用多次也还抛异常
接下来再看看这行sThreadLocal.set(new Looper(quitAllowed));做了什么吧,它是如何塞进去的呢?
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法首先获取到了当前的线程,然后获取一个map。这个map是以键值对形式存储内容的。如果获取的map为空,就创建一个map。如果不为空就塞进去值。要注意的是,这里面的key是当前的线程,这里面的value就是Looper。也就是说,线程和Looper是一一对应的。也就是很多人说的Looper和线程绑定了,其实就是以键值对形式存进了一个map中。
3. handler如何延迟发送消息?
因为messageQueue是一个按时间排序的一个单链表,所有消息是按照先后顺序进行处理的。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
handler所有发送消息的方法最终都会走到sendMessageDelayed,只是delayMillis不同而已,这个delayMillis就是延时的时间。
然后这里会将DelayMillis加上当前开机的时间(这里可以理解就是这个time就是,现在的时间+需要延迟的时间=实际执行的时间),接下来进到sendMessageAtTime方法里面
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { // msg 必须有 target
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { // msg 不能正在被使用
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) { // 正在退出,回收消息并直接返回
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 插入消息队列头部,需要唤醒队列
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) { // 按消息的触发时间顺序插入队列
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。排在越前面的越早触发,那我们现在应该了解到了,这个所谓的延时呢,不是延时发送消息,而是延时去处理消息,我们在发消息都是马上插入到消息队列当中。
我们这里插入完消息之后,怎么又保证在我们预期的时间里处理消息呢,接下来我们看如何获取消息
MessageQueue::next()
@UnsupportedAppUsage
Message next() {
...
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
nativePollOnce(ptr, nextPollTimeoutMillis);
//如果阻塞操作结束,则去获取消息
synchronized (this) {
// 去获取下一条消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//当消息的Handler为空时,则查询异步消息
if (msg != null && msg.target == null) {
// 查找队列中的下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息
nextPollTimeoutMillis = -1;
}
...
}
}
msg != null 我们看下这部分,如果当前时间小于头部时间(消息队列是按时间顺序排列的),那就更新等待时间nextPollTimeoutMillis,等下次再做比较
如果时间到了,就取这个消息并返回。
如果没有消息,nextPollTimeoutMillis被赋为-1,这个循环又执行到nativePollOnce继续阻塞
其实到这里handler的任务就完成了,把message发送到messageQueue里面,每个消息都会带有一个uptimeMillis参数,这就是延时的时间。
总结:
1、postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
2、紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
3、MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
4、Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message进队;
4. Looper.loop()方法在主线程死循环,为啥不会造成ANR?
具体的流程是:
1、mainThread中ActivityThread首先创建了一个运行在主线程的Looper,并且把它和主线程进行了绑定。
2、Looper又创建了一个MessageQueue,然后调用Looper.loop方法不断地在主线程中尝试取出Message
3、Looper如果取到了Message,那么就在主线程中调用发送这个Message的Handler的handleMessage方法。
4、我们在主线程或者子线程中通过Looper.getMainLooper为参数创建了一个Handler。
5、在子线程中发送了Message,主线程中的Looper不断循环,终于收到了Message,在主线程中调用了这个Handler的handleMessage方法。
- 本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message发送到了MessageQueue中。由Looper来进行分发,然后在进行处理。用人话来说就是,我们的Android程序就是运行在这个死循环中的。一旦这个死循环结束,app也就结束了。但是只有消息循环没有被阻塞,能一直处理事件就不会发生ANR异常。
- ANR是Application Not Responding也就是Android程序无响应,只要有消息,looper就会及时的进行处理,所以不会造成消息没有被及时处理造成ANR。
只是如果没有消息到来的时候,主线程会进行休眠,消息到来的时候会进行唤醒,这里面使用了 Linux 的 epoll 机制。
5. Handler为什么会造成内存泄漏?如何避免造成内存泄漏?
内部类持有外部类的对象,handler持有activity的对象,当页面activity关闭时,handler还在发送消息,handler持有activity的对象,导致handler不能及时被回收,所以造成内存泄漏。
为啥其他内部类不会呢?
因为当handler发送消息时,会有耗时操作,并且会利用线程中的looper和messageQueue进行消息发送,looper和messageQueue的生命周期是很长的,和application一样,所以handler不容易被销毁,所以造成内存泄漏。
如何解决?
1.把handler生命成静态内部类,静态内部类不会持有activity,所以不会造成内存泄漏,
2.弱引用(WeakReference):把使用handle的activity设置成弱引用,
protected MyHandler handler = new MyHandler(this);
public abstract void handlerMessage1(Message msg);
public static class MyHandler extends Handler {
private WeakReference<BaseActivity> weakReference;
public MyHandler(BaseActivity activity) {
this.weakReference = new WeakReference<BaseActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
weakReference.get().handlerMessage1(msg);
}
}
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
3.页面销毁时,清空发送的消息:
handler.removeCallbacksAndMessages()
6. 子线程如何创建hander?主线程给子线程的Handler发送消息怎么写?
子线程如果要创建Handler,必须通过Looper.prepare()方法创建Looper,在主线程中ActivityThread已经帮我们创建好了,我们不需要自己去创建,但如果在子线程中创建Handler,要么使用Looper的mainLooper,要么自己调用Looper.prepare()方法创建属于这个线程的looper对象。如下是创建了一个子线程的Looper对象:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 子线程处理消息
}
};
Looper.loop();
}
}
mHandler.sendMessage()//主线程发送消息
7. 为什么建议使用Message.obtain()来创建Message实例?
提高消息的复用
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可以看到,obtain方法是将一个Message对象的所有数据清空,然后添加到链表头中。sPool就是个消息池,默认的缓存是50个。
Looper在分发结束以后,会将用完的消息回收掉,并添加到回收池里。
这样可以「避免重复创建多个实例对象」节约内存,还有,Message池其实是一个「单链表结构」,定位到下述代码可以看到:池的容量为50
8. 为什么子线程中不可以直接new Handler()而主线程中可以?
如果要创建Handler,必须通过Looper.prepare()方法创建Looper,在主线程中ActivityThread已经帮我们创建好了,我们不需要自己去创建,但如果在子线程中创建Handler,要么使用Looper的mainLooper,要么自己调用Looper.prepare()方法创建属于这个线程的looper对象。
9. 请描述MessageQueue的数据结构和工作流程。
按时间先后顺序排列的单链表,在Handler的构造方法中MessageQueue被赋值。最后发送消息都调用的是MessageQueue的queue.enqueueMessage(msg, uptimeMillis)方法。现在我们已经拿到了queue,然后在这个单链表中进行发消息。
// MessageQueue.java
//省略部分代码
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//【1】拿到队列头部
Message p = mMessages;
boolean needWake;
//【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//【3】消息插到队列中间
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。好了