美文网首页
Android线程通信之Handler

Android线程通信之Handler

作者: imkobedroid | 来源:发表于2019-05-19 16:29 被阅读0次

    handler是安卓中通信的常用东西,虽然常用明白其中的原理相当重要,本文记录方便后面自己巩固!

    基本使用

       /**
         * 防止内存泄漏
         */
        open class ChildHandler : Handler()
    
      /**
             * 一般的消息发送
             */
            messageSend.setOnClickListener {
                val handler = @SuppressLint("HandlerLeak")
                object : ChildHandler() {
                    override fun handleMessage(msg: Message?) {
                        super.handleMessage(msg)
                        val par1 = msg?.what
                        val par2 = msg?.arg1
                        val par3 = msg?.arg2
                        val par4 = msg?.data?.getString("key1")
                        val par5 = (msg?.obj as HandlerData).info
                        Log.e("消息结果:", "$par1  $par2  $par3  $par4  $par5")
                    }
                }
                Thread(Runnable {
                    handler.sendMessage(getMessage())
                    SystemClock.sleep(2000)
                    handler.postDelayed({ handler.sendMessage(getMessage()) }, 0)
                    SystemClock.sleep(2000)
                    handler.post { toast("我是子线程中的post") }
                }).start()
            }
        }
    

    handler的使用其实很简单,可以发送的类别基本如下:

      fun getMessage(): Message {
                val msg = Message()
                msg.what = 1
                msg.arg1 = 2
                msg.arg2 = 3
                val bundle = Bundle()
                bundle.putString("key1", "4")
                msg.data = bundle
                msg.obj = HandlerData("5")
                return msg
            }
    

    获取Message载体也可以使用下面这种方法:

     fun getMessageInfo(handler: Handler): Message {
                return handler.obtainMessage()
            }
    

    与new Message的区别就是:
    obtainmessage()是从消息池中拿来一个msg 不需要另开辟空间new
    new需要重新申请,效率低,obtianmessage可以循环利用

    上面在一个子线程中有这样一句代码:

    handler.post { toast("我是子线程中的post") }
                }).start()
    

    为什么可以在子线程更新UI呢?看起代码似乎是在子线程,其实不然,整个更新UI的操作还是在主线程,有兴趣的可以参考这篇文章,总结的很好:
    handler.post方法的终极最直观的理解与解释

    在主线程中使用handler

    获取handler对象

     /**
         * handler接受消息
         */
        private fun initData() {
            val handler = @SuppressLint("HandlerLeak")
            object : Handler() {
                override fun handleMessage(msg: Message?) {
                    super.handleMessage(msg)
                    msg?.what?.let { Toast.makeText(this@MainActivity, "主线程接受到消息----$it", Toast.LENGTH_SHORT).show() }
                }
            }
        }
    

    这个handler是new在主线程中的属于主线程的handler

    发送消息

            /**
             * 主线程通信  handler属于主线程
             */
            findViewById<AppCompatButton>(R.id.sendMessage).setOnClickListener {
                Thread(Runnable {
                    SystemClock.sleep(5000)
                    handler!!.sendEmptyMessage(0)
                }).start()
            }
    

    这里是点击按钮后开启一个子线程,并且在子线程沉睡5秒后发送一个消息

    结果

    按照Toast弹出了正确的显示框

    在子线程中使用handler

    findViewById<Button>(R.id.childThread).setOnClickListener { view ->
                Thread(Runnable {
                    // SystemClock.sleep(5000)
                    Looper.prepare()
                    mHandlerThread = @SuppressLint("HandlerLeak")
                    object : Handler() {
                        override fun handleMessage(msg: Message?) {
                            super.handleMessage(msg)
                            msg?.what?.let {
                                //(view as Button).text="子线程接受到消息----$it"
                                Toast.makeText(this@MainActivity, "子线程接受到消息----$it", Toast.LENGTH_SHORT).show()
                            }
                        }
                    }
                    Looper.loop()
                }).start()
                SystemClock.sleep(5000)
                mHandlerThread!!.sendEmptyMessage(0)
    
            }
    

    同样是点击一个按钮后开启一个线程并且获取handler对象,在点击按钮后主线程沉睡5秒后给子线程发送一个消息,并且显示出消息

    结果

    正确的显示出消息

    注意其中有句代码

     //(view as Button).text="子线程接受到消息----$it"
    

    这是来检测是否在子线程中,所以更新ui失败证明了是在子线程中。

    源码解读

    主线程中使用handler是没有什么问题的,但是在子线程中使用handler多了两句代码分别是:

     Looper.prepare()
     Looper.loop()
    
    Looper.prepare()

    先来分析Looper.prepare(),进入源码可以看到:

     /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          */
        public static void prepare() {
            prepare(true);
        }
    

    再往下

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

    这里我们看到了一个sThreadLocal,他的定义在里面是这样的:

     static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据,这里储存的就是一个Looper。
    我们继续分析looper,当上面的sThreadLocal.set(new Looper(quitAllowed))执行时候,他会走到这里:

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

    这里获取一个looper对象的时候需要一个MessageQueue对象,这个MessageQueue就是来获取消息的对象和一个线程对象,再看looper类的定义

    public final class Looper {
        //省略代码...
        final MessageQueue mQueue;
        final Thread mThread;
        }
    

    到这里基本上就可以明白

    使用Looper.prepare()这个方法是在当前的线程中获取到是否有looper对象,如果没有的话就重新设置一个looper到当前的对象,并将这个对象与当前线程进行绑定,其中looper对象里面包含了一个MessageQueue(消息队列)

    Looper.loop()

    我们继续分析looper在线程中的作用,进入Looper.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;
            
            ...省略代码...
            
            for (;;) { ...省略代码...}
            }
    

    其中的myLooper()方法是:

      /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
    

    这里可以恍然大悟,这里获取的looper就是前面我们调用Looper.prepare()设置的looper,并且将当前的这个looper中的MessageQueue(消息队列)对象拿了出来,再往下走是一个死循环代码:

     for (;;) { ...省略代码...}
    

    这个循环里做的事其实就是用当前looper中去循环的取出MessageQueue中的Message,并将这个message交给相应的handler进行处理,其他线程传过来的消息是放在message中的,这里再对相应的几个对象做下说明

    Handler:线程间通信的方式,主要用来发送消息及处理消息 ,消息的发送是不区分线程的,但是消息的接受是要区分线程的 
    Looper:为线程运行消息循环的类,循环取出MessageQueue中的Message;消息派发,将取出的Message交付给相应的Handler。 
    MessageQueue:存放通过Handler发过来的消息,遵循先进先出原则。 
    Message:消息,线程间通信通讯携带的数据。
    

    结论:

    一个线程需要接受其他线程传递过来的消息,必须其中有一个与当前线程进行绑定的looper消息循环器和一个处理消息的handler,这个looper中包含了一个MessageQueue,这个MessageQueue中包含的就是其他线程传递过来的Message消息对象,这个looper是一直在循环的取出消息队列中的消息,并将这个消息信息传递给当前线程中的handler对象进行处理,handler消息的发送是不区分线程的,但是消息的接受是要区分线程的,当前handler在哪个线程中就在哪个线程中处理消息!

    主线程中使用handler为什么代码中不用写Looper.prepare(),Looper.loop()?

    我们找到整个项目的函数入口,代码如下:

     public static void main(String[] args) {
           ...省略代码...
            Looper.prepareMainLooper();
            ActivityThread thread = new ActivityThread();
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    进入到Looper.prepareMainLooper():

     public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    

    进入 prepare(false)

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

    这里就很清楚了,这里其实就已经准备好了looper,再进入上边的myLooper():

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

    其实这里返回的就是上边准备的looper,主函数入口处并且有了Looper.loop(),这里大家就清楚了为什么主线程不需要操作looper了吧,因为主线程已经准备好了looper,并且准备的方式跟我们上面再子线程中准备的方式是一样的!

    相关文章

      网友评论

          本文标题:Android线程通信之Handler

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