zoukankan      html  css  js  c++  java
  • Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧。。

    学习内容:

    1.MessageQueue,Looper,MessageQueue的作用.

    2.子线程向主线程中发送消息

    3.主线程向子线程中发送消息

     

      异步消息处理机制是Android提供给我们异步更新UI的一种很好的方式,线程之间以Handler作为桥梁,使得Message可以在线程间进行传递,从而实现异步的一种方式。

    1.MessageQueue

      MessageQueue顾名思义,指的就是消息队列,说这个之前我们首先需要知道什么是Message,比如说我们在UI界面时点击一个按钮,或者是接收到了一条广播,其实都算是一条Message,这些事件都被封装成一条Message被添加到了MessageQueue队列当中,因为我们知道一个线程在一段时间只能对一种操作进行相关的处理,因此这些消息的处理就要有先后顺序,因此采用MessageQueue来管理,也就是消息队列。消息队列其实就是一堆需要处理的Message而形成的传送带。一旦有消息发送进来,那么直接执行enqueueMessage()方法。也就是将消息压入到队列当中,一旦线程空闲下来,那么直接从MessageQueue中取出消息,使得消息出队。

    2.Looper

      Looper的主要作用是与当前线程形成一种绑定的关系,同时创建一个MessageQueue,这样保证一个线程只能持有一个Looper和MessageQueue同时Looper使得MessageQueue循环起来,就好比水车和水一样,MessageQueue好比水车,当他有了Looper的时候,那么水车会随着水去转动也就是说Looper为MessageQueue提供了活力,使其循环起来,循环的动力往往就少不了Thread。一般而言子线程一般是没有MessageQueue,因此为了使线程和消息队列能够关联那么就需要有Looper来完成了,因此我们可以这样去实现

       class ChildRunnable implements Runnable{
    
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Log.e("TAG",msg.obj+"");
                    }
                };
                Log.e("TAG_2",Looper.myLooper().toString());
                Looper.loop();
    
            }
        }

      这里调用了Looper的prepare()和loop()方法。

    public static final void prepare() {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(true));
    }

      方法比较的简单,我们可以看到prepare()方法首先判断了sThreadLocal中持有的线程引用是否为空,如果不为空,那么直接就会抛异常,这也就说明了Looper.prepare()在一个线程中只允许调用一次,这样也同样为一个线程对应一个Looper做了保障。当Looper.prepare()执行完毕之后Looper才可以执行loop()方法。

    public static void loop() {
            
            /**
             * 获取当前线程绑定的Looper
             */
            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;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
            
            /**
             * 死循环,循环从MessageQueue取出消息.
             */
            for (;;) {
                /**
                 * 从Queue中取出一条消息
                 */
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
                /**
                 * 将消息分发出去
                 */
                msg.target.dispatchMessage(msg);
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
                /**
                  * 将消息回收
                 */
                msg.recycle();
            }
    }

      这里我们可以看到loop()是通过几个流程使得MessageQueue循环起来的,首先通过静态方法获取当前线程对应的Looper,然后获取由Looper创建的MessageQueue,然后进入死循环,阻塞式的从MessageQueue中取出消息,获取到消息之后将消息分发出去,否则进入等待状态,等待新的消息到来。这就是Looper使MessageQueue循环起来的方式。

      那么我们现在MessageQueue消息队列已经存在了,Looper以线程为动力使得MessageQueue循环了起来,那么消息的发送和处理由谁来完成呢显而易见这就是Handler的作用了.

    3.Handler

      Handler的出现使得消息可以被发送和接收,Handler的构造方法有多个。

    public Handler() {
           
    }
    
    public Handler(Handler.Callback callback) {
           
    }
    
    public Handler(Looper looper) {
          
    }
    
    public Handler(Looper looper, Handler.Callback callback) {
            
    }

      这是Handler的四个构造方法,1和2都没有传递Looper,那么Handler通过调用Looper.myLooper()方法将Handler与Looper和MessageQueue形成绑定关系,而3和4直接传递Looper,那么Handler将直接将传递进来的Looper对象进行保存,直接和传递的Looper以及相关的MessageQueue形成绑定关系。同时这四个构造方法还有不同点,就是是否传递Callback回调接口对应的参数。

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

      实现Callback接口是实现处理Message的一种方式,而另一种方式则不传递Callback回调接口,而是直接实现Handler中handleMessage方法,也就是说我们可以通过这两种方式实现Handler对消息的处理。这样三者就形成了绑定关系,然后我们来看看Handler的sendMessage等方法.其实无论是调用了哪种方法,sendMessage(),sendMessageDelayed()等等,最后都会调用sendMessageAtTime()方法。这里需要先说一下post方法和sendMessage方法。一般我们使用Handler发送消息的时候也会这样去写。

     handler.post(new Runnable() {
        @Override
        public void run() {
            /**
               * 相关操作
               * */
        }
    });

      这样发送消息也是可以的,post执行过程是这样的。

    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则作为callback参数作为回调,用于后续处理消息。这里也调用了sendMessageDelayed()方法,最后还是会调用sendMessageAtTime()方法,因此可以看出,无论是send还是post,最后都会调用sendMessageAtTime()方法。

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {  
       MessageQueue queue = mQueue;  
       return enqueueMessage(queue, msg, uptimeMillis);  
    }  

      sendMessageAtTime()是最终调用的方法,他通过调用enqueueMessage将消息加入到消息队列当中。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            //注意下面这行代码
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //注意下面这行代码
            return queue.enqueueMessage(msg, uptimeMillis);
    }

      enqueueMessage方法比较的简单,这里讲msg.target = this,将msg的target赋值为当前的handler,然后调用queue.enqueueMessage()方法将消息加入到消息队列当中。那么SendMessage之后,Looper就通过loop()方法不断的从MessageQueue取出消息,然后通过dispatchMessage()方法将消息分发给Handler,交给Handler去处理相关的Message.

    public void dispatchMessage(Message msg) {  
            if (msg.callback != null) {  
                handleCallback(msg);  
            } else {  
                if (mCallback != null) {  
                    if (mCallback.handleMessage(msg)) {  
                        return;  
                    }  
                }  
                handleMessage(msg);  
            }  
    }  

      这样我们就可以直接去处理相关的消息了,这个方法做了一些判断,也就是判断是否传递了callback属性,如果msg.callback不为空,那么则表示是post发送过来的消息,在处理的时候就需要调用handleCallback(msg)方法,然后我们会继续判断Handler的Callback是否为空,也就是我们在new Handler(Handler.Callback)方法去创建Handler的时候的判断,如果不为空,那么执行我们Callback中实现的handleMessage()方法,如果为空,那么就直接执行Handler中的handleMessage()方法。总体的流程就是这样。在其他的牛人博客中,看到了一幅特别形象的流程图。在这里贴出来。

      在现实生活的生产生活中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另一端进行收集处理。

      我们可以把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的消息队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,我们可以把发送机滚轮看做是Looper,而发动机的转动是需要电源的,我们可以把电源看做是线程Thread,所有的消息循环的一切操作都是基于某个线程的。一切准备就绪,我们只需要按下电源开关发动机就会转动起来,这个开关就是Looper的loop方法,当我们按下开关的时候,我们就相当于执行了Looper的loop方法,此时Looper就会驱动着消息队列循环起来。

      那Hanlder在传送带模型中相当于什么呢?我们可以将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相当于我们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX方法,这就把Message对象放入到了消息队列MessageQueue中了。当货物从传送带的另一端顺着管道划出时,我们就相当于调用了Hanlder的dispatchMessage方法,在该方法中我们完成对Message的处理。

    2.子线程向主线程发送消息

      子线程向主线程发送消息,平时还是很常用的,比如说子线程完成了一个耗时的操作,需要主线程去更新UI,那么这个时候就需要子线程向主线程中发送相关的消息。用起来也比较的简单。

    private Handler handler = new Handler(){
             //主线程做处理.
            @Override
            public void handleMessage(Message msg) {
                String data = (String) msg.obj;
                textView.setText("从子线程发送过来的消息"+data);
                childRunnable = null;
            }
    };
    class ChildRunnable implements Runnable {
            //子线程发送相关消息
            @Override
            public void run() {
                Message message = Message.obtain();
                message.obj = "ChildToMain";
                handler.sendMessage(message);
            }
    }

     3.主线程将消息发送给子线程

      我们经常将消息从子线程发送给主线程,其实主线程也是可以发消息给子线程的。在默认情况下子线程是没有Looper和MessageQueue的,因此我们需要为子线程创建一个Looper然后与子线程的Handler形成绑定关系。

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler_main_to_child);
            Log.e("TAG_0",Looper.getMainLooper().toString());
            button = (Button) findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Message message = Message.obtain();
                    message.obj = "MainToChild";
                    handler.sendMessage(message);
                }
            });
            new Thread(new ChildRunnable()).start();
    
        }
    
        class ChildRunnable implements Runnable{
    
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        Log.e("TAG",msg.obj+"");
                    }
                };
                Log.e("TAG_2",Looper.myLooper().toString());
                Looper.loop();
    
            }
        }

      这里设置了一个按钮的监听事件来控制主线程向子线程发送消息,或者我们先让主线程休眠,先让子线程初始化完毕之后,主线程再向子线程发送消息,如果直接发送的话,很有可能在Handler还没有初始化完毕后就导致消息发送,这样就会出现Handler为null从而导致NullPointerExpection发生。因此在这里进行了简单的控制,这里我们可以看到,如果子线程想拥有自己的Looper和MessageQueue首先需要执行Looper.prepare()和Looper.loop()方法。才能为子线程的Handler绑定上Looper和MessageQueue。有人可能会问道,为什么主线程直接发送消息就可以,而不需要调用这两个方法,这是因为Activity在onCreate的时候已经执行了这些方法,因此我们可以直接发消息给主线程。

     因此当我们在使用Handler Message Looper实现异步消息机制的时候,如果想实现多级通信,那么就需要弄明白当前的Looper MessageQueue绑定的是哪个Handler。Looper,MessageQueue在一个线程中只有唯一一个,但是Handler可以是多个的,我们只需要控制这个绑定关系是实现多级通信的关键。

     最后贴一个简单的源代码:Demo下载

      

  • 相关阅读:
    B树与B+详解
    处理器拦截器(HandlerInterceptor)详解(转)
    过滤器(Filter)与拦截器(Interceptor )区别
    Redis和MemCache静态Map做缓存区别
    Ubuntu16.10下mysql5.7的安装及远程访问配置
    windows中mysql5.7保存emoji表情
    基于Quartz.NET 实现可中断的任务(转)
    Ubuntu16.10下使用VSCode开发.netcore
    ubuntu16.10 安装ibus中文输入法
    ubuntu 中安装mysql 使用默认用户密码登录
  • 原文地址:https://www.cnblogs.com/RGogoing/p/6155263.html
Copyright © 2011-2022 走看看