想象一下:你点开一个App,想刷个朋友圈,结果屏幕冻结了5秒;想抢个红包,却卡在“正在加载”…… 这时候你大概率会骂骂咧咧:“这什么辣鸡应用!” 但作为开发者,心里更苦:明明代码逻辑没问题,为什么界面就是“卡成狗”?
其实,Android系统有个“霸道条款”:只有主线程(UI线程)能更新界面。如果让主线程同时处理耗时操作(比如网络请求、读取大文件),它就会像被老板逼着996的打工仔——累到虚脱,根本没空刷新界面。
举个例子:
你在点击按钮时,偷偷在主线程做了个10秒的循环:
button.setOnClickListener {
for (i in 1..10) {
Thread.sleep(1000) // 模拟耗时操作!危险!
textView.text = "已等待 $i 秒" // 崩溃!子线程不能更新UI
}
}
结果?轻则卡顿,重则直接弹出ANR(Application Not Responding) 警告,用户反手就是一个卸载!
那怎么办?很简单——把耗时活丢给子线程,干完后再通知主线程更新UI。而负责这个“通知”的,就是今天的主角:Handler消息机制。
如果把Handler机制比作外卖配送系统,那么:
Looper = 不停巡逻的外卖站长MessageQueue = 放外卖的货架Handler = 接单派单的外卖小哥Message = 一份份打包好的外卖订单来,咱们逐个拆解:
它的口号是:“我不生产消息,我只是消息的搬运工!”
Looper会死循环检查MessageQueue,有消息就捞出来,没消息就蹲着等。
注意:主线程自带Looper,但子线程用Handler前必须手动启动Looper:
class WorkerThread : Thread() {
lateinit var handler: Handler
override fun run() {
Looper.prepare() // 给线程绑个Looper
handler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
// 处理消息
}
}
Looper.loop() // 开始循环!
}
}
一个线程只有一个MessageQueue,所有消息按时间排队。
比如延时消息会插队到合适位置,就像外卖预约单。
它的两大技能:
sendMessage():发送消息到队列handleMessage():处理回调消息推荐用
Message.obtain()复用对象,避免疯狂创建对象引发GC(垃圾回收)“卡顿攻击”。
下面我们写个经典场景:在子线程加载网络图片,用Handler通知主线程显示。
完整示例代码(Kotlin版):
class MainActivity : AppCompatActivity() {
private lateinit var handler: Handler
private val imageUrl = "https://.../test.jpg"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 主线程的Handler,自动绑定主线程Looper
handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> {
// 收到消息,更新UI
val bitmap = msg.obj as Bitmap
imageView.setImageBitmap(bitmap)
progressBar.visibility = View.GONE
}
-1 -> {
Toast.makeText(this@MainActivity, "加载失败", Toast.LENGTH_SHORT).show()
}
}
}
}
loadImageBtn.setOnClickListener {
progressBar.visibility = View.VISIBLE
// 创建子线程加载图片
thread {
loadImageFromNetwork(imageUrl)
}
}
}
private fun loadImageFromNetwork(url: String) {
try {
val connection = URL(url).openConnection() as HttpURLConnection
val bitmap = BitmapFactory.decodeStream(connection.inputStream)
// 发送成功消息(注意不能在子线程直接操作imageView!)
val msg = handler.obtainMessage()
msg.what = 1
msg.obj = bitmap
handler.sendMessage(msg)
} catch (e: Exception) {
// 发送失败消息
handler.sendEmptyMessage(-1)
}
}
}
代码解读:
点击按钮后显示进度条,启动子线程下载图片子线程通过
handler.sendMessage()把Bitmap打包发送主线程的
handleMessage()收到消息后,安全更新ImageView全程主线程“零负重”,流畅得飞起!
// 5秒后执行
handler.postDelayed({
textView.text = "5秒到了!"
}, 5000)
// 也可以发延时消息
val msg = handler.obtainMessage()
handler.sendMessageDelayed(msg, 5000)
适用场景:轮询检查、防抖操作、定时动画。
看看这段问题代码:
class MainActivity : AppCompatActivity() {
private val handler = object : Handler() {
override fun handleMessage(msg: Message) { ... }
}
}
问题:Handler默认持有Activity引用。如果Activity关闭时消息队列还有未处理消息,Handler会一直持有Activity导致无法回收→内存泄漏!
解决方案:
方案1:在Activity销毁时清空消息
override fun onDestroy() {
handler.removeCallbacksAndMessages(null)
super.onDestroy()
}
方案2:使用静态内部类+弱引用
class SafeHandler(activity: MainActivity) : Handler() {
private val weakRef = WeakReference(activity)
override fun handleMessage(msg: Message) {
val activity = weakRef.get()
if (activity != null && activity.isFinishing) {
// 处理消息
}
}
}
虽然Handler很强大,但Kotlin协程提供了更优雅的异步方案:
// 协程版本加载图片
loadImageBtn.setOnClickListener {
lifecycleScope.launch {
progressBar.visibility = View.VISIBLE
val bitmap = withContext(Dispatchers.IO) {
loadImageFromNetwork(imageUrl)
}
imageView.setImageBitmap(bitmap) // 自动切回主线程
progressBar.visibility = View.GONE
}
}
优势:代码更简洁,避免回调地狱,自动管理生命周期。
但Handler仍是Android底层核心机制,理解它才能:
读懂系统源码(比如ActivityThread的main函数就是Looper循环)处理跨线程复杂通信应对底层原理面试题(懂的都懂!)Handler消息机制的本质是线程间通信的桥梁。它通过“消息队列+循环检查”实现解耦,保证UI流畅。记住三个关键:
主线程操作UI,子线程处理耗时任务Handler负责跨线程发送和处理消息注意内存泄漏,及时清理消息现在回头看看开头那个卡顿的App——是不是突然觉得Handler像个深藏功与名的超级英雄?掌握它,你的App就能从“卡成狗”进化到“纵享丝滑”。毕竟,在这个体验至上的时代,流畅才是最大的体贴!