一 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,我的第一篇博客写完了,祝你幸福