今天给大家讲解的是在Android开发中如何使用Handler和使用Handler时要注意的一些细节问题。本篇文章是作为我上一篇文章《Android源码分析--Handler机制的实现与工作原理》的补充。虽然是补充,但是两篇文章所讲的内容不同:一个是原理的分析,一个是使用的讲解。如果你还没有看过上一篇文章,建议你先去看一看,只有了解了Handler的原理,才能更好的使用它。而且我们今天所讲的内容也是建立在上一篇文章的基础上的。
Handler的最大作用就是线程的切换,至于Handler切换线程的原理和实现,上一篇文章已有详细的讲解,这里就不多说了。下面我们看如何把一个消息从一个线程发送到另一个线程。
//在主线程创建一个Handler对象。 //重写Handler的handleMessage方法,这个就是接收并处理消息的方法。 Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { //msg就是子线程发送过来的消息。 } }; //开启一个子线程 new Thread(new Runnable() { @Override public void run() { //在子线程发送一个消息。 Message msg = new Message(); handler.sendMessage(msg); } }).start();
上面就是一个简单的Handler使用例子。我们在主线程创建一个Handler,他的handleMessage()方法是运行在主线程的,当我们在子线程发送一个消息的时候,handleMessage()会接收到消息,这样我们就把消息由子线程发送到了主线程。上面的代码中,Handler发送的消息是一个空消息,什么数据也没有,如果我们只是单纯的发送一个空消息,可以使用Handler自己的发送空消息的方法:
handler.sendEmptyMessage(what);
这两种发送空消息的效果是一样的。至于这里的what是什么我们后面会说到。
Handler也可以发送带有数据的消息。Message对象有一个Object类型的obj属性,就是用来携带消息数据的。我们只需要把要发送的数据赋值给obj就可以了,然后在处理消息的时候再把数据取出来。
Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { Log.i(TAG,"Handler 发送过来的消息是:" + msg.obj); } }; new Thread(new Runnable() { @Override public void run() { Message msg = new Message(); msg.obj = "我是消息数据"; handler.sendMessage(msg); } }).start();
上面的代码中,msg携带一个字符串数据:"我是消息数据",在handleMessage()方法中把这个数据取出来。除了obj以外,Message还有两个int类型的属性:arg1、arg2可以用来发送一些简单的数据。
现在我们看到的所有的消息都是在Handler的handleMessage()方法中处理,如果我要发送很多个消息,每个消息的数据都不一样,消息的处理逻辑也不一样,那么在handleMessage()方法中如何去判断哪个消息是哪个呢?这时Message的what属性就派上用场了,what属性就是上面发送空消息时我们看到的what,它是一int类型,它是用来标识消息的。
Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.i(TAG, "第一个消息是:" + msg.obj); break; case 2: Log.i(TAG, "第二个消息是:" + msg.obj); break; } } }; new Thread(new Runnable() { @Override public void run() { Message msg1 = new Message(); msg1.obj = "第一个消息"; msg1.what = 1; handler.sendMessage(msg1); Message msg2 = new Message(); msg2.obj = "第二个消息"; msg2.what = 2; handler.sendMessage(msg2); } }).start();
上面我们在子线程中发送了两个消息,并且给两个消息设置了它的what,在处理消息的时候就可以通过msg的what来判断是哪个消息了。
除了用sendMessage发送Message消息以外,Handler还可以post一个Runnable。
new Thread(new Runnable() { @Override public void run() { //在子线程post一个Runnable对象 handler.post(new Runnable() { @Override public void run() { //这里是消息处理的方法 //这里运行在主线程。 } }); } }).start();
其实post()方法和sendMessage()方法的逻辑是一样的,post()方法中的Runnable会被封装成Message,然后发送出去。下面看它的源码:
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; }
从源码中我们看到,Runnable会被封装成Message,然后还是用sendMessage的方式发送出去。而且封装的逻辑也很简单,直接把Runnable赋值给Message的callback就可以了。在上一篇文章中我介绍了消息处理的三个方法,Message自己的callback优先级是最高的,所以这个消息是由自己的callback也就是Runnable的run()方法处理的,而不再是Handler的handleMessage()方法。
前面我们所举的例子中,都是消息的处理是在主线程的。其实不然,消息的处理事实上是运行在负责管理消息队列(MessageQueue)的Looper所在的线程的,而不一定是主线程。这一点我在上一篇文章也反复的提到了,如果还不了解这一点的请认真阅读一下我的上一篇文章。
如果我们创建一个Handler对象而没有给它指定它的Looper,那么它默认会使用当前线程的Looper。前面我们所举的例子都是在主线程直接创建Handler对象的,所以它的Looper就是主线程的Looper,它的消息自然也就是在主线程处理了。那么我们也可以在子线程使用Handler,下面可一个例子:
//声明Handler; Handler handler; new Thread(new Runnable() { @Override public void run() { //创建当前线程的Looper Looper.prepare(); //在子线程创建handler对象 handler = new Handler() { @Override public void handleMessage(Message msg) { //这里是消息处理,它是运行在子线程的 } }; //开启Looper的消息轮询 Looper.loop(); } }).start(); mBanner.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在主线程发送一个消息到子线程 Message msg = new Message(); handler.sendMessage(msg); } });
在上面的例子中,我们在子线程创建handler对象,handler的Looper就是子线程的Looper,所以消息的处理也就是在子线程处理的。这就是在子线程使用Handler的方式。在这里有几个需要注意的点:(很重要)
1、在子线程使用Handler前一定要先为子线程创建Looper,创建的方式是直接调用Looper.prepare()方法。前面我们说过创建Handler对象时如果没有给它指定Looper,那么它默认会使用当前线程的Looper,而线程默认是没有Looper的,所以使用前一定要先创建Looper。
2、在同一个线程里,Looper.prepare()方法不能被调用两次。因为同一个线程里,最多只能有一个Looper对象。
3、只有调用了Looper.loop()方法,Handler机制才能正常工作。 Looper负责管理MessageQueue,它的loop()方法负责从MessageQueue里取出消息并交给Handler处理,所以如果没有调用Looper.loop()方法,消息就不会被取出和处理。
4、Looper.loop()方法一定要在调用了Looper.prepare()方法之后调用。那是因为如果当前线程还没有Looper,是不能调用Looper.loop()方法开启消息轮询的,否则会报错。
5、不要在主线程调用Looper.prepare()方法。这是因为在Android系统创建主线程的时候就已经调用了Looper.prepare()方法和Looper.loop()方法,这也是为什么我们在主线程使用Handler时不需要调用这两个方法的原因。
6、当我们在子线程使用Handler时,如果Handler不再需要发送和处理消息,那么一定要退出子线程的消息轮询。因为Looper.loop()方法里是一个死循环,如果我们不主动结束它,那么它就会一直运行,子线程也会一直运行而不会结束。退出消息轮询的方法是:
Looper.myLooper().quit(); Looper.myLooper().quitSafely();
上面的例子都是用线程自己的Looper来创建Handler,我们也可以用指定的Looper来创建Handler。
new Thread(new Runnable() { @Override public void run() { //获取主线程的Looper Looper looper = Looper.getMainLooper(); //用主线程的Looper创建Handler handler = new Handler(looper) { @Override public void handleMessage(Message msg) { //这里是运行在主线程的 } }; } }).start();
上面的例子中,我们虽然是在子线创建Handler,但因为用的是主线程的Looper,所以消息的处理是在主线程的,这跟在主线程创建Handler是一样的。因为这里并没有使用到子线程的Looper,所以不要调用Looper.prepare()和Looper.loop()方法。
以上我们所说的是Handler切换线程的使用。Handler除了提供post()方法和sendMessage()方法以外,还提供了一系列的发送消息的方法。比如延时发送消息和定时发送消息:
//延时发送消息 public final boolean sendMessageDelayed(Message msg, long delayMillis) public final boolean postDelayed(Runnable r, long delayMillis); //定时发送消息 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { public final boolean postAtTime(Runnable r, long uptimeMillis); public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);
通过这些方法,可以实现延时执行方法和定时执行方法的功能。如下面的例子:
Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { Log.i(TAG, "延时1000毫秒打印"); } },1000);
上面的例子并没有涉及到线程的切换,只是利用了Handler延时发送消息的功能达到延时打印。所以Handler的使用不仅仅是切换线程。更多的方法使用就不一一举例了。
小知识点:
1、使用Message.obtain()来获取一个消息。前面我们的例子中获取一个消息都是用new的方式直接创建,我这样做只是为了方便大家的理解而已。在使用中不推荐用这种方式来获取一个消息,而是使用Message.obtain()方法。
Message msg = Message.obtain();
Android会为Message提供一个缓存池,把使用过的消息缓存起来,方便下次使用。我们用Message.obtain()方法获取一个消息时,会先从缓存池获取,如果缓存池没有消息,才会去创建消息。这样做可以优化内存。
2、同一个Message不要发送两次。如下面的代码是有问题的:
//同一个Message发送了两次 Message msg = Message.obtain(); handler.sendMessage(msg); handler.sendMessage(msg);
这是因为所以的消息都是发送到MessageQueue存放,然后等待处理的。如果一个Message对象在MessageQueue里,再次把它存到MessageQueue时就会报错。
3、Android已经提供了很多实现了Handler的类和方法,方便我们使用。如Activity类的runOnUiThread()方法,View的post()方法,HandlerThread类等,关于这些知识,大家可以查阅相关资料,这里就不做讲解了,因为他们的实现其实跟我们前面说的是一样的。
作者:donkingliang
链接:https://www.jianshu.com/p/e02d68da29b6