手记

Android Handler的使用方式和注意事项

今天给大家讲解的是在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


0人推荐
随时随地看视频
慕课网APP