• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Android-Handler面试总结

android 搞代码 3年前 (2022-03-30) 15次浏览 已收录 0个评论

在Android面试中,无关Handler的面试是一个离不开的话题,上面咱们就无关Handler的面试进行一个总结。

1,Handler、Looper、MessageQueue、线程的关系

  • 一个线程只会有一个Looper对象,所以线程和Looper是一一对应的。
  • MessageQueue对象是在new Looper的时候创立的,所以Looper和MessageQueue是一一对应的。
  • Handler的作用只是将音讯加到MessageQueue中,并后续取出音讯后,依据音讯的target字段分发给当初的那个handler,所以Handler对于Looper是能够多对一的,也就是多个Hanlder对象都能够用同一个线程、同一个Looper、同一个MessageQueue。

综上,Looper、MessageQueue、线程是一一对应关系,而他们与Handler是能够一对多的。

2,主线程为什么不必初始化Looper

因为利用在启动的过程中就曾经初始化了一个主线程Looper。每个java应用程序都是有一个main办法入口,Android是基于Java的程序也不例外,Android程序的入口在ActivityThread的main办法中,代码如下:

// 初始化主线程Looper
 Looper.prepareMainLooper();
 ...
 // 新建一个ActivityThread对象
 ActivityThread thread = new ActivityThread();
 thread.attach(false, startSeq);
 // 获取ActivityThread的Handler,也是他的外部类H
 if (sMainThreadHandler == null) {
 sMainThreadHandler = thread.getHandler();
 }
 ...
 Looper.loop();
 // 如果loop办法完结则抛出异样,程序完结
 throw new RuntimeException("Main thread loop unexpectedly exited");
} 

能够看到,main办法中会先初始化主线程Looper,新建ActivityThread对象,而后再启动Looper,这样主线程的Looper在程序启动的时候就跑起来了。并且,咱们通常认为 ActivityThread 就是主线程,事实上它并不是一个线程,而是主线程操作的管理者。

3,为什么主线程的Looper是一个死循环,然而却不会ANR

因为当Looper解决完所有音讯的时候会进入阻塞状态,当有新的Message进来的时候会突破阻塞继续执行。

首先,咱们看一下什么是ANR,ANR,全名Application Not Responding。当我发送一个绘制UI 的音讯到主线程Handler之后,通过肯定的工夫没有被执行,则抛出ANR异样。上面再来答复一下,主线程的Looper为什么是一个死循环,却不会ANR?Looper的死循环,是循环执行各种事务,包含UI绘制事务。Looper死循环阐明线程没有死亡,如果Looper进行循环,线程则完结退出了,Looper的死循环自身就是保障UI绘制工作能够被执行的起因之一。

对于这个问题,咱们还能够失去如下的一些论断:

  • 真正会卡死的操作是在某个音讯解决的时候操作工夫过长,导致掉帧、ANR,而不是loop办法自身。
  • 在主线程以外,会有其余的线程来解决承受其余过程的事件,比方Binder线程(ApplicationThread),会承受AMS发送来的事件
  • 在收到跨过程音讯后,会交给主线程的Hanlder再进行音讯散发。所以Activity的生命周期都是依附主线程的Looper.loop,当收到不同Message时则采纳相应措施,比方收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()办法,最终执行到onCreate办法。
  • 当没有音讯的时候,会阻塞在loop的queue.next()中的nativePollOnce()办法里,此时主线程会开释CPU资源进入休眠状态,直到下个音讯达到或者有事务产生,所以死循环也不会特地耗费CPU资源。

4,Message是怎么找到它所属的Handler而后进行散发的

在loop办法中,找到要解决的Message须要调用上面的一段代码来解决音讯:

msg.target.dispatchMessage(msg);

所以是将音讯交给了msg.target来解决,那么这个target是什么呢,通常查看target的源头能够发现:

private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;

        return queue.enqueueMessage(msg, uptimeMillis);
    }

在应用Hanlder发送音讯的时候,会设置msg.target = this,所以target就是当初把音讯加到音讯队列的那个Handler。

5,Handler是如何切换线程的

应用不同线程的Looper解决音讯。咱们晓得,代码的执行线程,并不是代码自身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个Looper都运行在对应的线程,所以不同的Looper调用的dispatchMessage办法就运行在其所在的线程了。

6,post(Runnable) 与 sendMessage 有什么区别

咱们晓得,Hanlder中发送音讯能够分为两种:post(Runnable)和sendMessage。首先,咱们来看一下源码:

public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
   
public final boolean sendMessage(@NonNull Message msg) {
     return sendMessageDelayed(msg, 0);
   }

能够看到,post和sendMessage的区别就在于,post办法给Message设置了一个callback回调。那么,那么这个callback有什么用呢?咱们再转到音讯解决的办法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();
    }

能够看到,如果msg.callback不为空,也就是通过post办法发送音讯的时候,会把音讯交给这个msg.callback进行解决;如果msg.callback为空,也就是通过sendMessage发送音讯的时候,会判断Handler以后的mCallback是否为空,如果不为空就交给Handler.Callback.handleMessage解决。

所以post(Runnable) 与 sendMessage的区别就在于后续音讯的解决形式,是交给msg.callback还是 Handler.Callback或者Handler.handleMessage。

7,Handler如何保障MessageQueue并发拜访平安的

循环加锁,配合阻塞唤醒机制。咱们发现,MessageQueue其实是【生产者-消费者】模型,Handler一直地放入音讯,Looper一直地取出,这就波及到死锁问题。如果Looper拿到锁,然而队列中没有音讯,就会始终期待,而Handler须要把音讯放进去,锁却被Looper拿着无奈入队,这就造成了死锁,Handler机制的解决办法是循环加锁,代码在MessageQueue的next办法中:

Message next() {
 ...
 for (;;) {
 ...
 nativePollOnce(ptr, nextPollTimeoutMillis);
 synchronized (this) {
 ...
 }
 }
} 

咱们能够看到他的期待是在锁外的,当队列中没有音讯的时候,他会先开释锁,再进行期待,直到被唤醒。这样就不会造成死锁问题了。

8,Handler的阻塞唤醒机制是怎么实现的

Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。这个机制也是相似于handler机制的模式。在本地创立一个文件描述符,而后须要期待的一方则监听这个文件描述符,唤醒的一方只须要批改这个文件,那么期待的一方就会收到文件从而突破唤醒。

参考:Linux的阻塞唤醒机制

9,什么是Handler的同步屏障

所谓同步屏障,其实就是一个Message,只不过它是插入在MessageQueue的链表头,且其target==null。 而Message加急音讯就是应用同步屏障实现的。同步屏障用到了postSyncBarrier()办法。

public int postSyncBarrier() {
 return postSyncBarrier(SystemClock.uptimeMillis());
}
 private int postSyncBarrier(long when) {
 synchronized (this) {
 final int token = mNextBarrierToken++;
 final Message msg = Message.obtain();
 msg.markInUse();
 msg.when = when;
 msg.arg1 = token;
 Message prev = null;
 Message p = mMessages;
 // 把以后须要执行的Message全副执行
 if (when != 0) {
 while (p != null && p.when <= when) {
 prev = p;
 p = p.next;
 }
 }
 // 插入同步屏障
 if (prev != null) { // invariant: p == prev.next
 msg.next = p;
 prev.next = msg;
 } else {
 msg.next = p;
 mMessages = msg;
 }
 return token;
 }
} 

能够看到,同步屏障就是一个非凡的target,即target==null,咱们能够看到他并没有给target属性赋值,那这个target有什么用呢?

Message next() {
 ...
 // 阻塞工夫
 int nextPollTimeoutMillis = 0;
 for (;;) {
 ...
 // 阻塞对应工夫 
 nativePollOnce(ptr, nextPollTimeoutMillis);
 // 对MessageQueue进行加锁,保障线程平安
 synchronized (this) {
 final long now = SystemClock.uptimeMillis();
 Message prevMsg = null;
 Message msg = mMessages;
 /**
 *  1
 */
 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 {
 // 取得音讯且当初要执行,标记MessageQueue为非阻塞
 mBlocked = false;
 /**
 *  2
 */
 // 个别只有异步音讯才会从两头拿走音讯,同步音讯都是从链表头获取
 if (prevMsg != null) {
 prevMsg.next = msg.next;
 } else {
 mMessages = msg.next;
 }
 msg.next = null;
 msg.markInUse();
 return msg;
 }
 } else {
 // 没有音讯,进入阻塞状态
 nextPollTimeoutMillis = -1;
 }
 // 当调用Looper.quitSafely()时候执行完所有的音讯后就会退出
 if (mQuitting) {
 dispose();
 return null;
 }
 ...
 }
 ...
 }
} 

咱们重点看一下对于同步屏障的局部代码。

if (msg != null && msg.target == null) {
 // 同步屏障,找到下一个异步音讯
 do {
 prevMsg = msg;
 msg = msg.next;
 } while (msg != null && !msg.isAsynchronous());
} 

如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步音讯的Message,即isAsynchronous返回true,其余的音讯会间接漠视,那么这样异步音讯,就会提前被执行了。同时,,同步屏障不会主动移除,应用实现之后须要手动进行移除,不然会造成同步音讯无奈被解决。

10,IdleHandler的应用场景

后面说过,当MessageQueue没有音讯的时候,就会阻塞在next办法中,其实在阻塞之前,MessageQueue还会做一件事,就是查看是否存在IdleHandler,如果有,就会去执行它的queueIdle办法。

IdleHandler看起来如同是个Handler,但他其实只是一个有单办法的接口,也称为函数型接口。

public static interface IdleHandler {
 boolean queueIdle();
} 

事实上,在MessageQueue中有一个List存储了IdleHandler对象,当MessageQueue没有须要被执行的Message时就会遍历回调所有的IdleHandler。所以IdleHandler次要用于在音讯队列闲暇的时候解决一些轻量级的工作。

因而,IdleHandler能够用来进行启动优化,比方将一些事件(比方界面view的绘制、赋值)放到onCreate办法或者onResume办法中。然而这两个办法其实都是在界面绘制之前调用的,也就是说肯定水平上这两个办法的耗时会影响到启动工夫,所以咱们能够把一些操作放到IdleHandler中,也就是界面绘制实现之后才去调用,这样就能缩小启动工夫了。

11,HandlerThread应用场景

首先,咱们来看一下HandlerThread的源码:

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }

能够看到,HandlerThread是一个封装了Looper的Thread类,就是为了让咱们在子线程外面更不便的应用Handler。这里的加锁就是为了保障线程平安,获取以后线程的Looper对象,获取胜利之后再通过notifyAll办法唤醒其余线程,那哪里调用了wait办法呢?答案是getLooper办法。

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android-Handler面试总结

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址