美文网首页Android开发Android技术知识Android开发经验谈
【学习笔记】Looper 与 Handler 的关系

【学习笔记】Looper 与 Handler 的关系

作者: 我说的这句话是谎话 | 来源:发表于2018-03-31 14:55 被阅读64次
    • 什么是异步消息处理线程
      异步消息处理线程启动会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中会取出一个消息,然后回调给相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
    • 这个消息处理过程有哪些角色
      1. 无限的循环体
      2. 内部的消息队列
      3. 相应的消息处理函数

    Looper

    对于 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));    
    } 
    

    sThreadLocal 是一个 ThreadLocal对象,可以在一个线程中存储变量。在最后面进行了 Looper 实例的保存。

    在 prepare() 方法中,首先进行了 Looper 对象的判空处理,如果不为空(之前已经有了一个 Looper 实例),就会抛出异常,说明了 prepare 不能被调用两次,同时也保证了 一个线程里面只能有一个 Looper 实例。

    那 new Looper(true)里面做了什么呢?

    private Looper(boolean quitAllowed) {    
            mQueue = new MessageQueue(quitAllowed);    
            mRun = true;    
            mThread = Thread.currentThread();    
    }    
    

    在 Looper 的构造方法中,有新增一个 MessageQuene 实例,这个是一个消息队列。

    然后看看第二个主要方法 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.");    
            }    
            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();    
        
            for (;;) {    
                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();    
            }    
    }    
    

    还有一个 myLooper() 方法

    public static Looper myLooper() {
        return sThreadLocal.get();
    }
    

    从 threadLocal 里面去的 looper 实例,如果为空则抛出异常,所以 loop() 方法的调用一定要在 prepare() 之后。

    后续的大概意思就是 异步消息处理线程的流程:进入一个无限的循环体for(;;)之中,每循环一次,从其内部的消息队列中final MessageQueue queue = me.mQueue;会取出一个消息Message msg = queue.next();,然后回调给相应的消息处理函数msg.target.dispatchMessage(msg);,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。

    Looper的主要作用就是:

    • prepar() 与当前线程绑定,保证一个线程只有一个 Looper 实例,同时一个 Looper实例也只有一个 MessageQuene
    • loop() 消息循环处理机制

    理清一下异步消息处理流程的角色 出场了哪些?

    1. 无限的循环体
    2. 内部的消息队列
    3. 相应的消息处理函数

    前两个都清楚来路,还剩一个 msg.target 从哪里来?要到哪里去?贫僧从东土大唐而来,去往西天拜佛求经

    于是:Handler 登场。

    一般 Activity 中使用 Hander 有两种方式

    先说第一种handler方式:

     public Handler() {    
                this(null, false);    
        }
    
    
        private Handler mHandler = new Handler()    
            {    
                public void handleMessage(android.os.Message msg)    
                {    
                    switch (msg.what)    
                    {    
                    case value:    
                            
                        break;    
            
                    default:    
                        break;    
                    }    
                };    
            };    
    

    需要看两个方法:Handler() 的构造函数干了什么?handleMessage 是谁通过什么方式调用的?

    • Handler 是怎么与 消息队列(MessageQueue)联系上的?他在子线程发送的消息是怎么发送到 消息队列(MessageQueue)的?
    public Handler(Callback callback, boolean async) {    
            if (FIND_POTENTIAL_LEAKS) {    
                final Class<? extends Handler> klass = getClass();    
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&    
                        (klass.getModifiers() & Modifier.STATIC) == 0) {    
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +    
                        klass.getCanonicalName());    
                }    
            }    
        
            mLooper = Looper.myLooper();    
            if (mLooper == null) {    
                throw new RuntimeException(    
                    "Can't create handler inside thread that has not called Looper.prepare()");    
            }    
            mQueue = mLooper.mQueue;    
            mCallback = callback;    
            mAsynchronous = async;    
        }    
    

    先通过 Looper.myLooper 获得 当前线程的 Looper 实例如果没有,则无法创建 Handler

    在 非UI线程 创建 Handler 的时候 通常都会 抛出异常:Can't create handler inside thread that has not called Looper.prepare()。

    这里有两个知识点:

    1. UI 线程 (Activity)并没有显式地调用 Looper.prepare() 和 Looper.loop() 方法,是因为在 启动代码中,已经在当前UI线程中调用的 这两个方法,所以直接在 UI线程创建 Handler 时 Looper.myLooper()是会返回当前线程(UI线程)的唯一 Looper实例的。
    2. 在 非UI 线程中创建 Handler 时,应该要进行 Looper方法的显式调用

    通过得到的 Looper 实例,进一步获取 Looper 的 MessageQueue(消息队列),这样Handler 就得到了 MessageQueue 的联系方式。接下来就是 联系MessageQueue 的具体做法了打电话还是wechat?

    public final boolean sendMessage(Message msg)    
     {    
         return sendMessageDelayed(msg, 0);    
     } 
    
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {    
         Message msg = Message.obtain();    
         msg.what = what;    
         return sendMessageDelayed(msg, delayMillis);    
     }   
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis)    
       {    
           if (delayMillis < 0) {    
               delayMillis = 0;    
           }    
           return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    
       }    
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {    
           MessageQueue queue = mQueue;    
           if (queue == null) {    
               RuntimeException e = new RuntimeException(    
                       this + " sendMessageAtTime() called with no mQueue");    
               Log.w("Looper", e.getMessage(), e);    
               return false;    
           }    
           return enqueueMessage(queue, msg, uptimeMillis);    
       }   
    

    终于走到了能看到MessageQueue的地方 sendMessageAtTime()

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {    
           msg.target = this;    
           if (mAsynchronous) {    
               msg.setAsynchronous(true);    
           }    
           return queue.enqueueMessage(msg, uptimeMillis);    
       }
    

    msg.target 很眼熟,不玩初心才行,这上面一大串转接就是 为了找到 msg.target 的来路。

    终于找到了,this!!!是谁?~~这个属于java的基础知识

    public static void loop() {    
            ...  
            for (;;) {    
                ...
                msg.target.dispatchMessage(msg);    
                ...  
            }    
    }    
    

    这句就是在做 消息处理 这件事情,至此,案情水落石出,真相只有一个犯人target 就是 handler

    wait a second. 还有一个疑点。 handler处理的消息是从哪里来的?

    queue.enqueueMessage(msg, uptimeMillis); 
    

    这么明显,handler 发送的消息被送到 MessageQueue 消息队列里面的,然后交给 target 的 dispatchMessage 去处理了。看一看 dispatchMessage

    public void dispatchMessage(Message msg) {    
            if (msg.callback != null) {    
                handleCallback(msg);    
            } else {    
                if (mCallback != null) {    
                    if (mCallback.handleMessage(msg)) {    
                        return;    
                    }    
                }    
                handleMessage(msg);    
            }    
        }    
    
    /**  
       * Subclasses must implement this to receive messages.  
       */    
      public void handleMessage(Message msg) {   
        
      }   
    

    这是个空方法。

    msg.callback 是什么?我发送的消息只有 msg 跟 what ,并没有callback啊,所以一般都是 null 的,else 必然会调用的方法是 handleMessage(msg),这个也很眼熟,不就是 new Handler() 之后需要重写的 消息处理函数吗?

    到这里就比较明白了。

    总结陈词:

    1. Looper 会在 当前线程中 保存唯一一个 Looper 实例,然后唯一的Looper 实例会在 Looper.prepare() 方法新建一个 同样在一个线程里唯一的 MessageQueue消息队列。
    2. Looper.loop() 会让当前线程进入一个无限循环,然后不断地从MessageQueue 的实例中读取消息,然后回调 msg.target.dispatchMessage(msg)方法。
    3. Handler 构造方法中通过当前线程的 Looper 实例,从而得到 MessageQueue消息队列的联系方式
    4. Handler 通过 sendMessage 方法 发送消息 Message 给 MessageQueue,并且在 Message 中标明自己的身份 msg.target = this, 从而在联系MessageQueue 的过程中暴露自己的身份,完成后续的消息处理。
    5. 构造 Handler 实例时,重写 handleMessage 方法,也就是 msg.targte.dispatchMessage 最终调用的方法。
    6. 至此,整个 Looper 与 handler 的 联系过程完成。

    再说第二种handler方式

    mHandler.post(new Runnable()    
            {    
                @Override    
                public void run()    
                {    
                    Log.e("TAG", Thread.currentThread().getName());    
                    tv_text.setText("message received.");    
                }    
            });    
    

    需要看两个方法 handler.post 干了什么? 谁调用了 run 方法?

    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;    
      }   
    

    在上面方法中,看到了 message 的 callback 被赋值了,之前在 msg.target.dispatchMessage() 方法中有见到。

    Tips: 产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存

    public final boolean sendMessageDelayed(Message msg, long delayMillis)    
       {    
           if (delayMillis < 0) {    
               delayMillis = 0;    
           }    
           return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    
       }    
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {    
           MessageQueue queue = mQueue;    
           if (queue == null) {    
               RuntimeException e = new RuntimeException(    
                       this + " sendMessageAtTime() called with no mQueue");    
               Log.w("Looper", e.getMessage(), e);    
               return false;    
           }    
           return enqueueMessage(queue, msg, uptimeMillis);    
       }    
    

    又回到了这里 sendMessageAtTime() 跟 handler.sendMessage 殊途同归。

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

    不过,这次的 callback 可不为null 了,所以会走 handlerCallbace(msg) 分支

        private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    也就是调用 Runnable 的 run 方法。

    关于 Looper , Handler, Message 的关系已经很清楚了。

    Handler 不仅可以更新UI, 我们完全可以在一个子线程中去创建一个 Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在创建 Handler 实例的线程中执行。

    想到一个问题

    • 一个 Activity 可以有多个 handler吗

    已发布至 Jimbray

    相关文章

      网友评论

        本文标题:【学习笔记】Looper 与 Handler 的关系

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