Handler相关面试题

1、消息是如何添加到队列的

handler 发送一个message 时,会调用sendMessage等方法,这些方法中最终都会调用 Handler 中的 enqueueMessage 方法,我们看一下 enqueueMessage 方法做了什么:

//Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //...
    //这里执行MessageQueue的 enqueueMessage
    return queue.enqueueMessage(msg, uptimeMillis);
}

实际上就是调用了MessageQueue的enqueueMessage:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            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 收到消息以后,会根据时间进行排列。

2、Handler的消息队列在哪创建的

回到创建Handler的地方,它的构造方法:

//Handler
public Handler() {
    this(null, false);
}
//Handler
public Handler(Callback callback, boolean async) {
    //...
    //获取当前的looper
    mLooper = Looper.myLooper();
    //...
    //获取looper 的 MessageQueue
    mQueue = mLooper.mQueue;
    //...
}
//Looper
final MessageQueue mQueue;

private Looper(boolean quitAllowed) {
    //在这里创建了一个 MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //...
}

可以看到 Handler其实是拿着Looper 的MessageQueue当做自己的MessageQueue。

3、Loope有什么作用

消息被有序的添加到了消息队列中,而Looper就是负责将消息从消息队列中取出。当执行Looper的loop()方法,Looper会从消息队列中取出消息,然后交给handler的dispatchMessage去处理消息。

//Looper
public static void loop() {
    //...
    for (;;) {
        //从消息队列中获取消息
        Message msg = queue.next(); // might block
        //...
            try {
                //msg.traget 就是Handler 
                //使用 Handler 的  dispatchMessage() 处理消息
                msg.target.dispatchMessage(msg);
                //...
        } catch (Exception exception) {
            //...
        }
        //...
    }
}

4、一个线程有几个Looper

要想知道有几个Lopper,肯定要先知道Looper在哪里创建。Looper有一个prepare方法:

//Looper
public static void prepare() {
    prepare(true);
}

在这里会创建一个新的Looper 并且设置到了ThreadLocal:

//Looper
private static void prepare(boolean quitAllowed) {
    //通过 sThreadLocal get 检查是否已经有 looper  
    if (sThreadLocal.get() != null) {
        //如果已经有了 就抛出异常
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //没有的话 就设置一个新的Looper
    sThreadLocal.set(new Looper(quitAllowed));
}

在ThreadLocal可以看到是以map的形式去保存,保证了一个线程只有一个map,又将looper和ThreadLocal进行绑定

//ThreadLocal
public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取 ThreadLocalMap 
    ThreadLocalMap map = getMap(t);
    //有的话 就将当前的 ThreadLocal 和 Looper 绑定在一起,
    if (map != null)
        //set 以后 在上面  sThreadLocal.get() 就不会在为null了
        map.set(this, value);
    else
        //没有的话 创建一个 ThreadLocalMap 在绑定在一起
        createMap(t, value);
}

看到Looper中的 sThreadLocal:

//Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

他是一个静态的 final 保证了 一个Looper只有一个 sThreadLocal,最终保证了一个线程只有一个Looper。

5、主线程什么时候执行preapre

想要使用Looper,肯定需要先prepare 去创建一个Looper,那么主线程如何创建Looper的呢?我们知道 java 程序的入口是 main 方法, 对于Android来说,其实也有一个main 方法,他的位置在 ActivityThread:

//ActivityThread
public static void main(String[] args) {
    //...
    //可以看到在这里 程序启动以后,Android 系统帮我们将主线程的Looper prepare
    Looper.prepareMainLooper();

    //...
    //然后帮助我们启动了 loop
    Looper.loop();
    //...
}

6、Handler内存泄露

Handler为什么会有可能导致内存泄露? 我们知道 内部类会持有外部类的引用,当我们做一个延时任务,延时10S,然后在10S内退出Activity,在我们sendMessage的时候,handler对象被传递给msg 如👇所示,然后被存放在MessageQueue中。在这10S内,即使Activity销毁了,但是引用关系依然被保存在MessageQueue中,那么即使Activity销毁了,他的对象依然不会被GC销毁,因为他依然被引用。就导致内存未被回收。

/Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //这里 将 handler 本身的对象 传给 msg 的target
    msg.target = this;
   //...
}

复制代码那么如何处理Handler内存泄露呢

  1. 将Handler改成静态类。原因是因为静态类不会持有外部类的引用
  2. 继承Handler,将Activity作为弱引用使用
    private static class AppHandler extends Handler {
        //弱引用,在垃圾回收时,被回收
        WeakReference<Activity> activity;
    
        AppHandler(Activity activity){
            this.activity=new WeakReference<Activity>(activity);
        }
    
        public void handleMessage(Message message){
            switch (message.what){
                //todo
            }
        }
    }
    
  3. 在界面退出的时候,调用Handler的removeMessages方法
    // 清空消息队列,移除对外部类的引用
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    
    }
    
      
    //Handler源码中removeCallbacksAndMessages()注释含义
    /**
     * Remove any pending posts of callbacks and sent messages whose
     * <var>obj</var> is <var>token</var>.  If <var>token</var> is null,
     * all callbacks and messages will be removed.
     */
    public final void removeCallbacksAndMessages(Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }
    

7、消息队列没有消息时Handler如何挂起

Looper从MessageQueue中获取message,当获取不到message的时候,会将 nextPollTimeoutMillis置成-1,然后进入下次循环,当执行nativePollOnce方法时候,如果nextPollTimeoutMillis==-1那么就会执行Linux的epoll机制,让线程处于挂起状态,阻塞线程。

//MessageQueue
Message next() {
    for (;;) {
        //step3: nextPollTimeoutMillis == -1 执行native 函数,
        //执行 linux epoll 机制,线程处于等待状态,线程挂起
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            //...
            if (msg != null) {
                
            } else {
                // step1:如果没有消息  nextPollTimeoutMillis 变成-1
                nextPollTimeoutMillis = -1;
            }
        }
    }
}
//Looper
public static void loop() {
    for (;;) {
        //step4:这里也就挂起了
        Message msg = queue.next(); // might block
    }
}

8、Handler如何退出

使用looper去执行quit方法退出

handler.looper.quit()
//Looper
public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}
//MessageQueue
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        //step1:将mQuitting 变量变成true
        mQuitting = true;
        //step2:删除所有的消息
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        //step3:唤醒线程
        nativeWake(mPtr);
    }
    }
   
//MessageQueue
Message next() {
    for (;;) {
        //step4:线程被唤醒。继续执行
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        //step5:检查到状态是 true 返回null 出去
        if (mQuitting) {
            dispose();
            return null;
        }
    }
}
//Looper
public static void loop() {
    for (;;) {
        //step6:这里也被唤醒获取到message == null
        Message msg = queue.next(); // might block
        //step7:最终在这里🔚循环
        if (msg == null) {
            return;
        }
    }
}

Looper会先将消息队列中的消息全部清空,然后使用nativeWake的native方法唤醒线程,在上面我们介绍了,当消息队列中没有消息的时候,线程会挂起,处于等待状态,当我们唤醒以后,Looper的loop方法会继续执行下去,然后从MessageQueue中获取到一个null的Message,最终将Looper的loop()方法退出。

9、主线程能够Quit么?

我们知道了 主线程是在ActivityThread的main方法中执行了Looper.prepareMainLooper()创建的Looper:

//Looper
@Deprecated
public static void prepareMainLooper() {
    //step1: 注意看这里是一个false
    prepare(false);
}
//Looper
private static void prepare(boolean quitAllowed) {
    //step2:new的Looper传入的是false
    sThreadLocal.set(new Looper(quitAllowed));
}
//Looper
private Looper(boolean quitAllowed) {
    //step3:创建的MessageQueue 传入的也是false
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
//MessageQueue
MessageQueue(boolean quitAllowed) {
    //step4:将mQuitAllowed 变量变成了false
    mQuitAllowed = quitAllowed;
}
//MessageQueue
void quit(boolean safe) {
    //step5:如果是false 就是主线程 会直接抛出错误
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
}

回头在看一下 Looper的prepare方法,只有主线程可以创建一个不可以quit的MessageQueue,其他线程创建的都是可以quit的

//Looper
//公开方法 prepare 传入的是true
public static void prepare() {
    prepare(true);
}
//私有方法
private static void prepare(boolean quitAllowed) 

//主线程 传入的是false
public static void prepareMainLooper() {
    prepare(false);
}

10、为什么设计主线程不能被quit

在ActivityThread中,定义了一个H的类,继承了Handler,这个H的handler执行了Android所有的主要事件,比如广播,service,Activity生命周期等都是在这里进行处理,所以不能把主线程quit:

//ActivityThread
class H extends Handler {

}

11、消息如何知道是由哪个Handler发送的?

一个线程可以有多个Handler,想new几个都可以,在我们往MessageQueue中添加消息的时候,会加入一个target标记是哪个handler发送的:

//Handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //step1:在这里 就标记了是哪一个handler 发送的 
    msg.target = this;
    //...
}
//Looper
public static void loop() {
    //...
    for (;;) {
        //...
            try {
                //step2:这里就对应起来是哪一个handler 发送的message 
                msg.target.dispatchMessage(msg);
                //...
        } catch (Exception exception) {
            //...
        }
        //...
}

12、Handler如何确保线程安全的

//MessageQueue
boolean enqueueMessage(Message msg, long when) {
    //step1:通过加锁的方式,保证了添加消息到消息队列的安全
    synchronized (this) {
    }
}
//MessageQueue
Message next() {
    for (;;) {
        //step2:通过锁的方式保证了读取消息的安全
        synchronized (this) {
        }
    }
}

13、Message如何复用的

看一下我们quit的时候,是怎么从消息队列中清空消息的

//MessageQueue
void quit(boolean safe) {
    synchronized (this) {
        //step1: 清除所有的消息
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
    }
}
//MessageQueue
private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        //step2:执行message的方法
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
//Message
void recycleUnchecked() {
    //step3:将所有的变量全部清空
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        //默认50个Message
        if (sPoolSize < MAX_POOL_SIZE) {
            //step4:将已经清空状态的Message 放到一个新的链表中
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

使用obtain方法会从之前清空状态的链表中取出一个Message去使用,减少创建Message带来的内存消耗。

//Message
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            //step5:从已经清空状态的链表中取出一个Message使用
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

这种设计模式称为享元设计模式

14、为什么主线程loop不会导致ANR

首先要知道ANR是怎么出现的,ANR出现的条件有两个

  • 5秒内没有响应输入的事件,触摸反馈等
  • 广播10秒内没有执行完毕

在上面我们分析知道,所有的事件都是由Handler进行分发,在主线程上,发送一个事件,这个事件耗时,将主线程的loop()给卡主,让他只能执行当前任务,不能去处理其他事件就出现了ANR。

ANR的本质是由于不能及时处理消息导致的,和他的loop是没有任何关系的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值