美文网首页Android进阶之路
Handler另类难点三问

Handler另类难点三问

作者: 积木zz | 来源:发表于2020-11-20 01:29 被阅读0次

    之前有一章节介绍了Handler的常见面试题,今天就来说说另类的,可能你没关注的其他问题,一起看看吧。

    系统为什么提供Handler

    • 这点大家应该都知道一些,就是为了切换线程,主要就是为了解决在子线程无法访问UI的问题。

    那么为什么系统不允许在子线程中访问UI呢?

    • 因为Android的UI控件不是线程安全的,所以采用单线程模型来处理UI操作,通过Handler切换UI访问的线程即可。

    那么为什么不给UI控件加锁呢?

    • 因为加锁会让UI访问的逻辑变得复杂,而且会降低UI访问的效率,阻塞线程执行。

    Handler是怎么获取到当前线程的Looper的

    • 大家应该都知道Looper是绑定到线程上的,他的作用域就是线程,而且不同线程具有不同的Looper,也就是要从不同的线程取出线程中的Looper对象,这里用到的就是ThreadLocal

    假设我们不知道有这个类,如果要完成这样一个需求,从不同的线程获取线程中的Looper,是不是可以采用一个全局对象,比如hashmap,用来存储线程和对应的Looper?所以需要一个管理Looper的类,但是,线程中并不止这一个要存储和获取的数据,还有可能有其他的需求,也是跟线程所绑定的。所以,我们的系统就设计出了ThreadLocal这种工具类。

    ThreadLocal的工作流程是这样的:我们从不同的线程可以访问同一个ThreadLocal的get方法,然后ThreadLocal会从各自的线程中取出一个数组,然后再数组中通过ThreadLocal的索引找出对应的value值。具体逻辑呢,我们还是看看代码,分别是ThreadLocal的get方法和set方法:

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        } 
        
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }    
        
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }    
        
    

    首先看看set方法,获取到当前线程,然后取出线程中的threadLocals变量,是一个ThreadLocalMap类,然后将当前的ThreadLocal作为key,要设置的值作为value存到这个map中。

    get方法就同理了,还是获取到当前线程,然后取出线程中的ThreadLocalMap实例,然后从中取到当前ThreadLocal对应的值。

    其实可以看到,操作的对象都是线程中的ThreadLocalMap实例,也就是读写操作都只限制在线程内部,这也就是ThreadLocal故意设计的精妙之处了,他可以在不同的线程进行读写数据而且线程之间互不干扰。

    画个图方便理解记忆:

    ThreadLocal.PNG

    当MessageQueue 没有消息的时候,在干什么,会占用CPU资源吗。

    • MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 方法这里。具体就是会调用到nativePollOnce方法里,最终调用到epoll_wait()进行阻塞等待。

    这时,主线程会进行休眠状态,也就不会消耗CPU资源。当下个消息到达的时候,就会通过pipe管道写入数据然后唤醒主线程进行工作。

    这里涉及到阻塞和唤醒的机制叫做 epoll 机制

    先说说文件描述符和I/O多路复用

    在Linux操作系统中,可以将一切都看作是文件,而文件描述符简称fd,当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,可以理解为一个索引值。

    I/O多路复用是一种机制,让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作

    所以I/O多路复用其实就是一种监听读写的通知机制,而Linux提供的三种 IO 复用方式分别是:select、poll 和 epoll 。而这其中epoll是性能最好的多路I/O就绪通知方法。

    所以,这里用到的epoll其实就是一种I/O多路复用方式,用来监控多个文件描述符的I/O事件。通过epoll_wait方法等待I/O事件,如果当前没有可用的事件则阻塞调用线程。

    拜拜

    今天就说这么多了,感兴趣的朋友也可以继续深究下去,比如epoll为什么是性能最好的I/O多路复用方法?Handler在App启动流程中涉及到了哪些功能?等等。有机会再和大家聊聊~

    有一起学习的小伙伴可以关注下我的公众号——码上积木❤️❤️
    每日三问知识点/面试题,积少成多。

    相关文章

      网友评论

        本文标题:Handler另类难点三问

        本文链接:https://www.haomeiwen.com/subject/eebriktx.html