写点什么

Android 面试必备知识点:Android 中 Handler 八大问题汇总

用户头像
Android架构
关注
发布于: 刚刚

private static Message getPostMessage(Runnable r) {


Message m = Message.obtain();


m.callback = r;


return m;


}


可以看到getPostMessage中会先生成一个Messgae,并且把runnable赋值给messagecallback.消息都放到MessageQueue中后,看下Looper是如何处理的。


for (;;) {


Message msg = queue.next(); // might block


if (msg == null) {


return;


}


msg.target.dispatchMessage(msg);


}


Looper中会遍历message列表,当message不为null时调用msg.target.dispatchMessage(msg)方法。看下message结构:



也就是说msg.target.dispatchMessage方法其实就是调用的Handler中的dispatchMessage方法,下面看下dispatchMessage方法的源码:


public void dispatchMessage(@NonNull Message msg) {


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();


}


因为调用post方法时生成的message.callback=runnable,所以dispatchMessage方法中会直接调用?message.callback.run();也就是说直接执行post中的runnable方法。 而sendMessage中如果mCallback不为null就会调用mCallback.handleMessage(msg)方法,否则会直接调用handleMessage方法。


总结?post方法和handleMessage方法的不同在于,postrunnable会直接在callback中调用run方法执行,而sendMessage方法要用户主动重写mCallback或者handleMessage方法来处理。


3、Looper 会一直消耗系统资源吗?




首先给出结论,Looper不会一直消耗系统资源,当LooperMessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程就会进入阻塞状态。



下面看下looper所在的线程是如何进入阻塞状态的。looper阻塞肯定跟消息出队有关,因此看下消息出队的代码。


消息出队


Message next() {


// Return here if the message loop has already quit and been disposed.


// This can happen if the application tries to restart a looper after quit


// which is not supported.


final long ptr = mPtr;


if (ptr == 0) {


return null;


}


int nextPollTimeoutMillis = 0;


for (;;) {


if (nextPollTimeoutMillis != 0) {


Binder.flushPendingCommands();


}


nativePollOnce(ptr, nextPollTimeoutMillis);


// While calling an idle handler, a new message could have been delivered


// so go back and look again for a pending message without waiting.


if(hasNoMessage)


{


nextPollTimeoutMillis =-1;


}


}


}


上面的消息出队方法被简写了,主要看下面这段,没有消息的时候nextPollTimeoutMillis=-1


if(hasNoMessage)


{


nextPollTimeoutMillis =-1;


}


看 for 循环里面这个字段所其的作用:


if (nextPollTimeoutMillis != 0) {


Binder.flushPendingCommands();


}


nativePollOnce(ptr, nextPollTimeoutMillis);


Binder.flushPendingCommands();这个方法的作用可以看源码里面给出的解释:


/**


  • Flush any Binder commands pending in the current thread to the kernel


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


driver. This can be


  • useful to call before performing an operation that may block for a long

  • time, to ensure that any pending object references have been released

  • in order to prevent the process from holding on to objects longer than

  • it needs to.


*/


也就是说在用户线程要进入阻塞之前跟内核线程发送消息,防止用户线程长时间的持有某个对象。再看看下面这个方法:nativePollOnce(ptr, nextPollTimeoutMillis);nextPollingTimeOutMillis=-1时,这个native方法会阻塞当前线程,线程阻塞后,等下次有消息入队才会重新进入可运行状态,所以Looper并不会一直死循环消耗运行内存,对队列中的颜色消息还没到时间时也会阻塞当前线程,但是会有一个阻塞时间也就是nextPollingTimeOutMillis>0的时间。


当消息队列中没有消息的时候 looper 肯定是被消息入队唤醒的。


消息入队


boolean enqueueMessage(Message msg, long when) {


if (msg.target == null) {


throw new IllegalArgumentException("Message must have a target.");


}


if (msg.isInUse()) {


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;


}


上面可以看到消息入队之后会有一个


if (needWake) {


nativeWake(mPtr);


}


方法,调用这个方法就可以唤醒线程了。另外消息入队的时候是根据消息的delay时间来在链表中排序的,delay时间长的排在后面,时间短的排在前面。如果时间相同那么按插入时间先后来排,插入时间早的在前面,插入时间晚的在后面。


4、android 的 Handle 机制,Looper 关系,主线程的 Handler 是怎么判断收到的消息是哪个 Handler 传来的?




Looper是如何判断Message是从哪个handler传来的呢?其实很简单,在1中分析过,handlersendMessage的时候会构建一个Message对象,并且把自己放在Messagetarget里面,这样的话Looper就可以根据Message中的target来判断当前的消息是哪个handler传来的。


5、Handler 机制流程、Looper 中延迟消息谁来唤醒 Looper?




从 3 中知道在消息出队的for循环队列中会调用到下面的方法。


nativePollOnce(ptr, nextPollTimeoutMillis);


如果是延时消息,会在被阻塞nextPollTimeoutMillis时间后被叫醒,nextPollTimeoutMillis就是消息要执行的时间和当前的时间差。


6、Handler 是如何引起内存泄漏的?如何解决?




在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper


Looper.myLooper().quit()


那么,如果在HandlerhandleMessage方法中(或者是 run 方法)处理消息,如果这个是一个延时消息,会一直保存在主线程的消息队列里,并且会影响系统对Activity的回收,造成内存泄露。


具体可以参考Handler内存泄漏分析及解决


总结一下,解决Handler内存泄露主要 2 点


1 、有延时消息,要在Activity销毁的时候移除Messages


2、 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。


7、handler 机制中如何确保 Looper 的唯一性?




Looper是保存在线程的ThreadLocal里面的,使用Handler的时候要调用Looper.prepare()来创建一个Looper并放在当前的线程的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));


}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android面试必备知识点:Android中Handler八大问题汇总