Android线程通信之Handler

2019-05-19  本文已影响0人  imkobedroid

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,并且准备的方式跟我们上面再子线程中准备的方式是一样的!

上一篇下一篇

猜你喜欢

热点阅读