Android - 初探 Handler
Android Handler - 初探
- 什么是Handler?
- 它的作用是什么?
- 什么场景下能用到它?
- 深入探一探
- 一个线程可以有几个Handler?
- 线程间通信的原理是什么样的?
- Looper是什么,作用是什么?
本篇文章你所能斩获的内容:
- Handler基础定义、核心作用以及实际开发中的典型应用场景
- 初步理解Handler机制的关键细节(线程与Handler数量关系、线程通信原理、Looper角色)
- Handler使用过程中常见的问题 - 内存泄漏
- 子线程中使用Handler的正确姿势
一. 什么是Handler,其作用是什么?
Handler是一套用于线程间通信、任务调度的核心组件,核心作用是解决同一个APP中不同线程间的数据传递和任务执行。
翻译一下这句话的意思是,在一个APP里面,通过Handler可以实现主线程和子线程之间的通信、子线程和子线程间的通信、子线程和主线程间的通信。
Handler的出现规避了什么问题呢?
- 在Android开发中,更新UI只能在UI线程中进行更新,而不能通过其他线程进行UI的更新,但是如果在UI线程中处理耗时操作,一旦时间超过规定时间(5s)就会引发程序UI线程阻塞崩溃(ANR)异常。而Handler作为线程间的通信桥梁,我们就能实现在非UI线程中去处理耗时操作,在非UI线程中通过Handler向UI线程发送消息/任务,由UI线程在合适的时机处理,刷新UI。规避了UI线程阻塞引起的ANR异常。
二. Handler的使用场景是什么?
理解了Handler是个什么东西,它的出现是为了什么,能达到什么样的结果。再考虑它的使用场景,我想,你应该是手到擒来吧。
-
在执行IO耗时操作、网络请求、文件读写、数据库操作等耗时操作时,会出现页面的卡顿现象,可能只是一瞬,也可能是肉眼可见的卡顿,原因就是因为在UI线程中执行了耗时的操作,导致UI线程的阻塞卡顿引起的页面卡顿。
- 怎么解决呢?
- 这就需要用到主角Handler,注意观察下面这段代码
// 在主线程中创建Handler (注意:这儿是需要绑定主线程的Looper的,这样该任务执行时就是在UI线程中进行的,可以直接对相关UI进行操作不会引发异常) private val mainHandler = Handler(Looper.getMainLooper()) { msg -> when(msg.what) { 1-> { // 获取子线程传递的实际数据 val data = msg.obj as String // 更新UI findViewById<TextView>(R.id.text).text = data } } true } // 在子线程(IO线程中)执行耗时的操作 thread { // 模拟IO耗时操作:接口请求等耗时操作 Thread.sleep(2000) val result = "接口返回的数据" // 通过Handler向主线程发送消息(携带返回的结果) val msg = Message.obtain(mainHandler,1,result) mainHandler.sendMessage(msg) }在这段代码中就很详细的展示了Handler的简单使用,也是我们最常见的使用场景
- 怎么解决呢?
-
现在再回想一下,你一定在某一款APP中见到过这样一个页面跳转。点击APP图标启动APP,就会出现欢迎页,三秒后就跳转到登录页这个过程。这个过程就属于延迟任务的执行,可以通过Handler的postDelayed()、sendEmptyMessageDelayed()等方法实现
// 1. 延迟跳转的实现
mainHandler.postDelayed({
startActivity(Intent(this,MainActivity::))
finish()
},3000) // 延迟3秒执行
// 2. 定时任务的实现
// 倒计时10秒的实现
private var count = 10
private val countHandler = Handler(Looper.getMainLooper())
private val countRunnable = object:Runnable {
override fun run() {
tvCount.texat = "倒计时: $count"
count--
if(count >= 0) {
// 一秒重复执行,形成定时循环
countHandler.postDelayed(this,1000)
} else {
tvCount.text = "倒计时结束"
}
}
}
// 启动倒计时
countHandler.post(countRunnable)
为什么用Handler呢?
- 相比Timer/TimerTask,Handler更轻量、无线程安全问题(任务执行是在绑定的线程中执行的,无需跨线程)
- 若Handler绑定的是UI线程的Looper,可以直接进行UI的更新,无需进行线程切换
三. 一个线程可以有几个Handler?
说到线程的问题,很多小伙伴就会想到,那么一个线程里面可以有多少个Handler呢?答案是多个,一个线程可以创建多个Handler,这些Handler会绑定到该线程唯一的Looper上,所有的Handler发送的消息和任务都会进入Looper关联的同一个MessageQueue,Looper通过loop()方法取出Message,根据Message的target字段分发给对应的Handler处理,最终所有任务都在Looper所在的线程串行执行。 小小验证一波
class MainActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// UI 线程创建第一个Handler
val handler1 = Handler(Looper.getMainLooper()){msg->
Log.d("HandlerTest", "handler1处理消息:${msg.what},线程:${Thread.currentThread().name}")
true
}
// UI 线程创建第二个Handler
val handler2 = Handler(Looper.getMainLooper()) { msg ->
Log.d("HandlerTest", "handler2处理消息:${msg.what},线程:${Thread.currentThread().name}")
true
}
// 验证两个Handler是否绑定同一个Looper,是否都为UI线程的MainLooper
Log.d("HandlerTest", "handler1的Looper:${handler1.looper}")
Log.d("HandlerTest", "handler2的Looper:${handler2.looper}")
Log.d("HandlerTest", "主线程Looper:${Looper.getMainLooper()}")
// 测试Handler发送消息
handler1.sendEmptyMessage(1)
handler2.sendEmptyMessage(2)
}
}
五. 线程间通信的原理是什么?
作为标准的"程序猿",大家都大概率思考一个问题,不同线程间的通信方式是什么样的?在实际的开发中,我们经常会遇到这样的场景,一个线程处理的数据,需要在另一个线程中被使用或处理。
- 线程间通信的核心原理 线程是进程的执行单元,同一进程内的所有线程共享该进程的地址空间,这是线程间能进行通信的物理基础。
- 常用的线程通信方式
- 共享内存模式(线程安全的共享变量/集合),这是最基础的方式。通过线程安全的容器(如ConcurrentHashMap、CopyOnWriteArrayList)或加锁的共享变量,让线程间共享数据。
fun main() { val testList = CopyOnWriteArrayList<String>() // 写数据的线程 val writeThread = Thread { for(i in 1..5) { testList.add(i) Thread.sleep(2000) // 模拟耗时操作 } } // 读数据的线程 val readerThread = Thread { // 循环读取,当集合中没有数据时循环等待 while(testList.size < 5) { Thread.sleep(300) } } writeThread.start() readerThread.start() // 等待线程执行完成 writeThread.join() readerThread.join() }- 生产者消费者,使用wait()和notify()实现生产者消费者模型
fun main() { // 共享缓冲区 val buffer = LinkedList<String>() val maxSize = 3 // 缓冲区最大容量 // 生产者线程 val producer = Thread { for(i in 1..5) { synchronized(buffer) { // 如果是缓冲区满了,生产者就该等待 while(buffer.size == maxSize) { buffer.wait() } // 生产数据 val data = "生产的第$i个" buffer.add(data) // 唤醒消费者 buffer.notify() } // 模拟生产者的耗时 Thread.sleep(500) } } // 消费者线程 val consumer = Thread { for(i in 1..5) { synchronized(buffer) { // 缓冲区空了,消费者挂起等待 while(buffer.isEmpty()) { buffer.wait() } // 消费者拿数据消费 val data = buffer.removeFirst() // 消费者取出数据后,唤醒生产者继续生产 buffer.notify() } // 模拟消费者耗时 Thread.sleep(800) } } producer.start() consumer.start() producer.join() consumer.join() } - 总结: 线程间通信的核心基础是同一个进程内的线程共享进程的地址空间 说白了就是同一个进程内的线程共享内存
六. Looper是什么?
Looper是Android为线程提供的消息循环机制,它的核心是为线程绑定一个MessageQueue,并通过无限循环将消息分发给对应的Handler。 通俗点说:Looper就像是循环传送带上的动力装置,负责让传送带(MessageQueue,消息队列)持续循环转动,传送带上的每一个东西就是Message,由不同的人(Handler)放到传送带上,而动力装置(Looper)会把传送带上的东西依次送到对应的接收人(Handler)的手中。