在 Android 开发中,经常需要在网络请求或者耗时任务完成后更新页面。但如果直接在子线程修改 UI,就会遇到线程相关的异常。
一开始我只是记住了“用 Handler 切回主线程”这条规则,后来发现如果不理解它背后的工作方式,很容易在延迟任务、页面销毁和内存泄漏这些问题上继续踩坑。
为什么 UI 要在主线程更新
Android 的界面系统并不是线程安全的。页面的测量、布局、绘制以及用户输入事件,通常都在主线程中依次处理。如果多个线程同时修改 View,状态就可能变得不可预测。
因此,后台任务可以在子线程执行,但最终影响页面的操作需要交给主线程处理:
private Handler mHandler = new Handler(Looper.getMainLooper());private void loadData() { new Thread(new Runnable() { @Override public void run() { final String result = requestData(); mHandler.post(new Runnable() { @Override public void run() { mTextView.setText(result); } }); } }).start();}
这里并不是 Handler 自己启动了一个线程,而是它把一段待执行的任务投递到了主线程对应的消息队列中。
三个对象如何协作
可以把整个过程简单理解为:
MessageQueue保存还没有被处理的消息和任务。Looper不断从队列中取出下一条消息。Handler负责发送消息,也负责在消息被取出后执行对应处理逻辑。
主线程启动时,系统已经为它准备好了 Looper。因此我们可以通过 Looper.getMainLooper() 创建一个明确关联到主线程的 Handler。
Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what == 1) { mTextView.setText((String) msg.obj); } }};Message message = Message.obtain();message.what = 1;message.obj = "加载完成";handler.sendMessage(message);
如果只是执行一段代码,post(Runnable) 往往更简单;如果需要用类型区分多种任务,Message.what 会比较直观。
延迟任务为什么可能造成泄漏
下面这种写法很常见:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { mTextView.setText("更新完成"); }};
匿名内部类会持有外部 Activity 的引用。如果消息被延迟很久才处理,而用户在此之前已经退出页面,消息队列仍可能通过 Handler 间接持有这个 Activity,使它暂时无法被回收。
一种比较稳妥的方式是使用静态内部类,并通过 WeakReference 引用页面:
private static class PageHandler extends Handler { private WeakReference<MainActivity> mActivityRef; PageHandler(MainActivity activity) { super(Looper.getMainLooper()); mActivityRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = mActivityRef.get(); if (activity == null) { return; } activity.mTextView.setText("更新完成"); }}private final PageHandler mHandler = new PageHandler(this);
除此之外,在页面退出时移除不再需要的任务也很重要:
@Overrideprotected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null);}
Handler 不应该承担所有异步逻辑
Handler 很适合做线程之间的消息切换和短时间延迟任务,但它并不适合替代完整的任务管理机制。
例如,页面已经退出时,网络请求是否要取消;旋转屏幕后任务是否要继续;数据是否应该缓存下来,这些问题不是简单发一条消息就能解决的。Handler 只负责消息投递,任务生命周期仍需要单独考虑。
小结
理解 Handler,重点不在于背诵几个 API,而是建立一个基本认识:主线程通过消息队列顺序处理界面任务,我们通过 Handler 把工作交还给这条线程。
使用时要注意两点:后台任务不要直接触碰 UI;页面退出后不要让无意义的延迟消息继续持有页面。处理好这两个问题,很多常见的线程和内存问题都会更容易定位。