Handler学习笔记

目录

Handler学习笔记_第1张图片
学习目录

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学习笔记)