Handler

作者: 小慧sir | 来源:发表于2019-08-06 16:03 被阅读0次

    1、定义

    一套 Android 消息传递机制

    2、作用

    在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

    3、意义

    • 问:为什么要用 Handler消息传递机制
    • 答:多个线程并发更新UI的同时 保证线程安全

    4、 使用方式

    • Handler的使用方式 因发送消息到消息队列的方式不同而不同
    • 共分为2种:使用Handler.sendMessage()、使用Handler.post()

    5、 相关概念

    名称 定义 作用
    UI线程 主线程(Main),用于UI更新 当数据变化,需要展示界面时,就要更新到UI线程
    子线程 又叫工作线程,主要处理耗时操作 因为UI线程逻辑处理时间有限,耗时操作要放到子线程,避免ANR
    Handler 线程通信的对象,线程之间任务的处理者 添加到消息队列里,接受Looper派发过来的对象
    Message 消息体对象 封装要传送的对象,类似一个载体
    Looper 轮训机制的对象 消息获取:不断从消息队列里拿出数据,发送给handler处理,每一个线程只能有一个Looper对象
    MessageQueue 消息队列,一个数据结构(先进先出) 维护和保存发送过来的消息

    那么和Handler 、 Looper 、Message、MessageQueue有啥关系?其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。
    主线程创建一个handler对象后维护一个Looper,Looper创建一个MessageQueue,通过死循环一直检测MQ是否有消息进来,如果有通知handler处理消息,并且handler就是负责往MQ发消息的对象

    6、 图解

    image

    7、 源码分析

    1. 新建一个handler获取到Looper对象和消息队列
    new Handler()
            --->this(null, false); 
            --->Looper mLooper = Looper.myLooper();
            --->MessageQueue mQueue =  mLooper.mQueue
    
    

    通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。

    1. 发送消息
    handler.sendMessage(message);
            --->sendMessageAtTime()
            --->enqueueMessage(queue, msg, uptimeMillis)
            --->msg.target = this;  ---  Handler对象
            --->queue.enqueueMessage(msg, uptimeMillis)  --  消息存到消息队列
    
    
    1. Looper:对于Looper主要是prepare()和loop()两个方法。

    首先看prepare()方法

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

    sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。将一个Looper的实例放入了ThreadLocal,并且判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例
    注意:一个线程中只能有一个Looper对象,只能有一个消息队列MessageQueue

    下面看Looper的构造方法:

    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
    }
    
    

    在构造方法中,创建了一个MessageQueue(消息队列)。

    loop()方法:

    final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
    
    

    方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。

            //拿到该looper实例中的mQueue(消息队列)
            final MessageQueue queue = me.mQueue;
            // 进入了我们所说的无限循环。
            for (; ; ) {
                // 取出一条消息,如果没有消息则阻塞。
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                。。。。
    
                msg.target.dispatchMessage(msg);
                ---handler
    
            }
    
    

    Looper主要作用:
    1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
    2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage()去处理。

    1. handler.dispatchMessage():
            if (msg.callback != null) {
                handleCallback(msg); --- >回调自身Runnable中run方法,使用post方法的时候
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg); --- >子类必须重写
            }
    
            回到最后处理的方法:
                private static void handleCallback(Message message) {
                    message.callback.run();
                }
    
                /**
                 * Subclasses must implement this to receive messages.
                 */
                public void handleMessage(Message msg) {
                }
    
    

    使用调用 msg.target.dispatchMessage(msg);把消息交给msg的target的dispatchMessage方法去处理。

    4、子线程创建handler

    其实Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行(HandlerThread)。

    new Thread() {
                private Handler handler;
    
                public void run() {
    
                    Looper.prepare();
    
                    handler = new Handler() {
                        public void handleMessage(android.os.Message msg) {
                            Log.e("TAG", Thread.currentThread().getName());
                        };
                    };
                    Looper.loop();
                }
            }
    
    

    5、 总结

    1. Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。

    2. Handler的sendMessage方法,会给msg的target赋值为handler自身,然后将Message加入MessageQueue中。

    3. Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

    4. Looper.loop()会让当前线程进入一个无限循环,从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

    5. handler.dispatchMessage(msg)分发消息,如果是sendMessage(),会回调重写的handleMessage方法;如果是post(),会最后会回调 message.callback.run(),当前的run()方法。

    6. 在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

    7. 疑问:主线程死循环为什么不会卡死App?
      真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

    相关文章

      网友评论

          本文标题:Handler

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