Handler系列一: 简单使用入门

226 阅读4分钟

一 Handler是什么

官网的介绍: 处理程序允许您发送和处理Message与线程的相关联的Runnable对象MessageQueue。每个Handler实例都与一个线程和该线程的消息队列关联。当您创建新的处理程序时,它会绑定到Looper。它将消息和可运行对象传递到该Looper的消息队列,并在该Looper的线程上执行它们。

可以简单理解为,Handler用来处理我们的Message,它会绑定一个Loop,并运行在绑定的Loop所在的线程。

接下来,康康代码

binding.text.setOnClickListener {
            Handler(Looper.getMainLooper()).post {
                Toast.makeText(this@MainActivity,"红红火火恍恍惚惚",Toast.LENGTH_LONG).show()
            }
        }

一个点击事件,点击之后会弹出一个Toast,内容是"红红火火恍恍惚惚",嗯,很奈斯,那你隔着说半天,和我直接创建一个Toast有啥区别,好像,确实没区别,别急,接着看,如果是这样呢

 binding.text.setOnClickListener {
            Thread{
                Toast.makeText(this@MainActivity,"红红火火恍恍惚惚",Toast.LENGTH_LONG).show()
            }.start()
            /*Handler(Looper.getMainLooper()).post {
                Toast.makeText(this@MainActivity,"红红火火恍恍惚惚",Toast.LENGTH_LONG).show()
            }*/
        }

运行了一下,程序不出意料的崩溃了,芜湖,我猜大家肯定都知道不能在子线程更新ui,接着,再这样呢

binding.text.setOnClickListener {
            Thread {
                Handler(Looper.getMainLooper()).post {
                    Toast.makeText(this@MainActivity, "红红火火恍恍惚惚", Toast.LENGTH_LONG).show()
                }
            }.start()
        }

运行,又正常了,我丢,这么神奇吗,通常,我们都知道,在子线程要更新ui,可以使用runOnUiThread,来切换到主线程,而事实上

public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

如果当前线程不是主线程,runOnUiThread所作的,也无非是mHandler.post(action),啊,啊这,这么神奇吗?网上解析Handler的文章实在不要太多,相信上文已经勾起了你对handler的兴趣,快去学习吧! 什么,没有勾起?那就继续看下面的Handler的应用吧。

二 Handler的使用

通常,我们是在主线程中实现一个Handler,然后在子线程中使用它。便于处于子线程时,却有代码需要运行在主线程,比如计算一段较为复杂的数据,接着要更新计算结果到ui,那么就可以使用Handler

class MainActivity : BaseActivity<ActivityMainBinding>() {
    private lateinit var myHandler: MyHandler
    override fun initViews() {
        myHandler = MyHandler(Looper.getMainLooper())
        binding.text.setOnClickListener {
            Thread{
                val message = Message.obtain().apply {
                    what = myHandler.showToast
                    obj = "红红火火恍恍惚惚"
                }
                myHandler.sendMessage(message)
            }.start()
        }
         binding.btn.setOnClickListener {
            myHandler.sendEmptyMessage(myHandler.logOut)
        }
    }
   inner class MyHandler(looper: Looper): Handler(looper){
        val showToast = 1
        val logOut = 2

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when(msg.what){
                showToast ->{
                    val obj = msg.obj
                    Toast.makeText(this@MainActivity,obj.toString(),Toast.LENGTH_LONG).show()
                }
                logOut ->{
                     //登出逻辑....
                }
            }

        }
    }
}

这是Handler最简易的使用了,通常会有很多showToast 这样的变量定义多个不同的行为,text的点击,执行弹出吐司,而btn的点击,执行登出的逻辑,可以在任意的地方发送消息,最后都会在handleMessage接收到消息,要注意的是内存泄漏问题,这个就不多啰嗦,网上也有很多

三 Handler进阶使用

事实上,我们经常遇到,某些要延迟执行的代码,(历史上的真实事件)老板:这个按钮的点击,要延迟0.1秒才作出反应,给用户一种特殊的感觉,我******** 。事实上,Handler就可以轻易做到这一切

binding.text.setOnClickListener {
            Thread{
                val message = Message.obtain().apply {
                    what = myHandler.showToast
                    obj = "红红火火恍恍惚惚"
                }
                //myHandler.sendMessage(message)
                myHandler.sendMessageDelayed(message,100)
            }.start()
        }

可以了,就这样,消息会在100毫秒后才发出,不信的话自己去实验吧

还有就是倒计时的显示,比如验证码倒计时,各种千奇百怪的倒计时需求,使用handler都可以轻松做到

class MainActivity : BaseActivity<ActivityMainBinding>() {
    private lateinit var myHandler: MyHandler
    var countDown = 60
    override fun initViews() {
        myHandler = MyHandler(Looper.getMainLooper())
        binding.text.setOnClickListener {
            //点击开始倒计时
            myHandler.sendEmptyMessage(myHandler.showTime)
        }
        binding.btn.setOnClickListener {
            myHandler.sendEmptyMessage(myHandler.logOut)
        }
    }

    inner class MyHandler(looper: Looper) : Handler(looper) {
        val showToast = 1
        val logOut = 2
        val showTime = 3

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            when (msg.what) {
                showToast -> {
                    val obj = msg.obj
                    Toast.makeText(this@MainActivity, obj.toString(), Toast.LENGTH_LONG).show()
                }
                logOut -> {

                }
                showTime -> {
                    Log.d("MyHandler","倒计时进行中:${countDown}")
                    binding.btn.text = "倒计时${countDown}秒"
                    countDown--
                    if(countDown >= 0){
                        myHandler.sendEmptyMessageDelayed(showTime,1000)
                    }else{
                        countDown = 60
                    }
                }
            }
        }
    }
}

需要注意,当退出页面的时候,如果倒计时还在继续,会发生内存泄漏,在退出页面之后Log.d("MyHandler","倒计时进行中:${countDown}")依然会继续打印,说明倒计时还在继续

有三种方法解决这个问题, 我最喜欢的是

 override fun onDestroy() {
        super.onDestroy()
        //countDown = -1
        myHandler.removeCallbacksAndMessages(null)
    }

直接调用removeCallbacksAndMessages,这里的参数要传null,表示移除所有callbacks和messages 其他的两个方法是

1.使用静态内部类:适用于不用更新界面等情况,因为静态内部类是无法使用外部类的非静态方法和变量的

2.使用静态内部类+弱引用:适用于要更新界面等情况

ok,我的第一篇博客写完了,祝你幸福