美文网首页
Handler学习笔记

Handler学习笔记

作者: kirito0424 | 来源:发表于2018-05-14 15:55 被阅读33次

    目录

    学习目录

    1. Handler的作用

    • 简单说Handler用于同一个进程的线程之间通信。(可以是不同线程,也可以是同一个线程,比如像做延时操作)

    • 基本原理是,Handler发送Message,并放入对应线程的MessageQueue中,Looper让对应线程无限循环地从自己的MessageQueue拿出消息处理。(Handler和Looper持有的是同一个MessageQueue)

    • 使用的最多的场景就是,我们在UI线程创建好Handler实例,然后在子线程做完耗时操作后,想要更新UI内容时,通过mHander sendMessage通知UI更新。而正如1所说,理论上我们也完全可以由UI线程发送Message,由子线程接收并处理,只是比较少见。另一个多见的场景是延时任务,往往是UI线程自己跟自己通信。

    2. 为什么需要Handler?

    • 一般来说,我们只要在子线程把信息放进主线程的MessageQueue里就可以了。因为,在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到主线程的MessageQueue 的实例,就可以放入消息,主线程的Looper在轮询MessageQueue时,就可以取出该消息并处理。

    • 主线程的MessageQueue的实例是可以拿到的(在主线程下 Looper.myLooper().mQueue),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类.只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用,Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。

    • Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

    // Looper.loop轮询方法
    public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            // 获取MessageQueue
            final MessageQueue queue = me.mQueue;
            // 省略
            // 具体的轮询逻辑,无限for循环
            for (;;) {
            // 取出Message
                Message msg = queue.next(); // might block
                // 省略
                try {
                // target为发送message的Handler实例
                // Handler处理
                    msg.target.dispatchMessage(msg);
                } 
                // 省略
            }
        }
    

    所以说,引入Handler只是为了大家使用方便以及代码的清晰简洁。并没有大家想的那么高深。


    3. 具体的使用

    3.1 主线程使用Handler刷新UI

    Handler handler = new Handler()
    

    实际会调用

     public Handler(Callback callback, boolean async) {
            // 省略
            // 这里也验证了,Handler在哪个线程创建,他就会持有哪个线程的Looper
            // 我们一般在UI线程初始化,Handler就会持有UI线程的Looper和MessageQueue
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            // 这里也验证了,Handler在哪个线程创建,他就会持有哪个线程的MessageQueue
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    在其他线程创建想在主线程处理事情的Handler,可以用以下代码可以达到相应效果

    // 传入UI线程的Looper
    Handler handler = new Handler(Looper.getMainLooper);
    

    3.2 LooperThread子线程使用(官网的文档)

    class LooperThread extends Thread {
           //其他线程可以通过mHandler这个引用给该线程的消息队列添加消息
           public Handler mHandler;
           public void run() {
                Looper.prepare();
                //需要在线程进入死循环之前,创建一个Handler实例供外界线程给自己发消息
                mHandler = new Handler() {
                    public void handleMessage(Message msg) {
                        //Handler 对象在这个线程构建,那么handleMessage的方法就在这个线程执行
                    }
                };
                // loop方法里会用到Handler实例
                // 所以必须先初始化Handler
                // 如果在loop方法之后初始化Handler,那么loop方法执行中会报错
                Looper.loop();
                // loop之后才初始化Handler,代码是无效的,loop是死循环,正常情况下这行代码就不会执行了
                // mHandler = new Handler()......
            }
        }
    

    需要说明的是,上面写到的Looper.prepare,创建Handler和Looper.loop方法的顺序并不一定不能改。如果你想的话,也完全可以loop执行之后创建Handler,只是创建的流程不能写在loop后面。因为loop里的死循环会导致你的代码不执行,你可以在主线程通过LooperThread.mHander这样的引用,来创建实例,效果也是一样的。

    注意,其实UI线程也有类似的代码,如下:

    public final class ActivityThread {
        public static final void main(String[] args) {
            ......
            Looper.prepareMainLooper();
            ......
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {    
                sMainThreadHandler = thread.getHandler();
            }
            ......
            Looper.loop();
            ......
        }
    }
    

    与上面的例子类似,系统在这里也为我们初始化了一个Handler。我们每次使用Handler mHandler = new Handler();都是额外创建了一个Handler,与原有的不冲突。msg.target.dispatchMessage(msg)这句代码会判断target。


    4.Handler发送消息的两种方式

    Handler,它直接继承自Object,一个Handler允许发送和处理Runnable或者Message对象,并且会关联到主线程的MessageQueue中。所以Handler把消息压入MessageQueue也有两种方式,post(new Runnable)和sendMessage(Message msg)。

    4.1 post

    post允许把一个Runnable对象入队到消息队列中。它的方法有:

    • post(Runnable)
    • postAtTime(Runnable,long)
    • postDelayed(Runnable,long)。

    4.2 sendMessage

    sendMessage允许把一个包含消息数据的Message对象压入到消息队列中。它的方法有:

    • sendEmptyMessage(int)
    • sendMessage(Message)
    • sendMessageAtTime(Message,long)
    • sendMessageDelayed(Message,long)。

    从上面的各种方法可以看出,不管是post还是sendMessage都具有多种方法,它们可以设定Runnable对象和Message对象被入队到消息队列中,是立即执行还是延迟执行。

    4.3 post和sendMessage方法的联系和区别

    先看源码

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

    代码很好理解,post方法其实还是把Runnable对象转化成了Message,区别在于普通的sendMessage不会使用callBack参数,它具体的处理逻辑在Handler的handleMessage里。而post会使用Message的callback,callback就是Runnable对象,所以使用post方法的话,无需再去写具体的handleMessage逻辑。源码如下:

    // dispatchMessage方法是在Looper.loop开启循环,开始处理MessageQueue里的每个Message时调用的,可以发现,默认先调用callback,没有callback才会使用handleMessage
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {                        
                return;
                }
            }
            handleMessage(msg); 
        }
    }
    

    5. 了解Message

    Message是一个final类,所以不可被继承。Message封装了线程中传递的消息,如果对于一般的数据,Message提供了getData()和setData()方法来获取与设置数据,其中操作的数据是一个Bundle对象,这个Bundle对象提供一系列的getXxx()和setXxx()方法用于传递基本数据类型的键值对,对于基本数据类型,使用起来很简单,这里不再详细讲解。而对于复杂的数据类型,如一个对象的传递就要相对复杂一些。在Bundle中提供了两个方法,专门用来传递对象的,但是这两个方法也有相应的限制,需要实现特定的接口,当然,一些Android自带的类,其实已经实现了这两个接口中的某一个,可以直接使用。方法如下:

    putParcelable(String key,Parcelable value):需要传递的对象类实现Parcelable接口。

    pubSerializable(String key,Serializable value):需要传递的对象类实现Serializable接口。

    还有另外一种方式在Message中传递对象,那就是使用Message自带的obj属性传值,它是一个Object类型,所以可以传递任意类型的对象,Message自带的有如下几个属性:

    int arg1:参数一,用于传递不复杂的数据,复杂数据使用setData()传递。

    int arg2:参数二,用于传递不复杂的数据,复杂数据使用setData()传递。

    Object obj:传递一个任意的对象。

    int what:定义的消息码,一般用于设定消息的标志。

    注意

    对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。Handler.obtainMessage()具有多个重载方法,如果查看源码,会发现其实Handler.obtainMessage()在内部也是调用的Message.obtain()。

    相关文章

      网友评论

          本文标题:Handler学习笔记

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