美文网首页Android开发Android开发Android技术知识
Android Handler的使用方式和注意事项

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

作者: donkingliang | 来源:发表于2017-03-19 14:11 被阅读839次

    今天给大家讲解的是在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类等,关于这些知识,大家可以查阅相关资料,这里就不做讲解了,因为他们的实现其实跟我们前面说的是一样的。

    文章已同步到我的CSDN博客http://blog.csdn.net/u010177022/article/details/63278070

    相关文章

      网友评论

        本文标题:Android Handler的使用方式和注意事项

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