下面几块基础组件都和「主线程不卡、组件销毁不漏、版本要对得上」有关:Handler 做线程切换,AsyncTask(已弃用)与 Loader(维护态)是历史方案,LayoutInflater 解 XML,*Compat 做低版本回退。
一、线程切换基本功:Handler 与消息队列
Android Handler 把 Message 投递到某个 Looper 的消息队列,实现线程间通信。主线程 Looper 在启动时已准备好,后台线程要自己 Looper.prepare 和 loop。post 和 sendMessage 的区别在于是否携带 what/obj。常见泄漏是匿名 Handler 持有外部类,Activity 销毁后消息仍到达,要用静态内部类加弱引用,或 View.post 在脱离窗口后自动失效。延迟消息在页面退出时要 removeCallbacks。同步屏障用于优先处理界面刷新,理解它有助于解释 Choreographer 卡顿。不要用 Handler 做精确定时器,系统休眠会拖长延迟。现代代码更多用协程,但系统框架里 Handler 仍无处不在。
二、AsyncTask:Handler + 线程池封装的历史方案
AsyncTask 内部是 Handler 和两个线程池实现的,Handler 用于将线程切换到主线程,两个线程池一个排队、一个执行。execute 封装出 FutureTask 入队,无任务在执行就执行它,完成后接着跑下一个,再通过 Handler 把事件发到主线程。AsyncTask 必须在主线程初始化,因为内部 Handler 是静态对象,类加载时就初始化好了。Android 3.0 起 execute 串行执行,3.0 之前并行;想在 3.0 上并行可调用 executeOnExecutor。新代码不要再引入 AsyncTask(已弃用),理解原理有助于迁移到协程或 Executor + Handler。
怎么取消:靠 cancel,但它是协作式的,正在跑的 doInBackground 要在检查点感知取消,否则不会真正停。
两个容易踩的坑
- 串行还是并行看版本:3.0 起
execute串行,想并行要executeOnExecutor。 - 必须在主线程初始化:内部
Handler是静态对象,类加载时就初始化好了。
三、Loader:生命周期感知的异步加载(已弃用)
Loader 曾在 Activity 重建时保留异步加载,是官方方案。LoaderManager 管理生命周期,CursorLoader 结合 ContentProvider 做查询,配置变化后数据还在、不必重查。如今 Jetpack 更推荐 ViewModel 加协程或 LiveData,Loader 处于维护状态,但读老项目仍会遇到。新代码不要再引入 Loader;迁移时把加载放到 ViewModel、观察者放到界面,ContentProvider 查询可改用 Paging 或直接仓库。遇到 initLoader 先找对应的回调再改。
四、LayoutInflater:把 XML 解成 View 树
LayoutInflater 把 XML 解成 View 树。Activity.setContentView 最终走 PhoneWindow 的 inflater,Fragment/RecyclerView 也要传带正确主题的 Context。inflate(res, parent, attachToRoot) 的第三个参数决定是否立刻挂到父容器,传错会丢 LayoutParams。内部用 XmlPullParser 读标签,createViewFromTag 先查自定义 Factory2(AppCompat 换 Tint 控件就靠这层),再反射构造。merge 与 include 在这一步展开,ViewStub 延迟到真正 inflate。自定义视图只写两参构造,主题属性会掉。热路径可缓存 LayoutInflater 或用 AsyncLayoutInflater,但注意线程安全与主题 Context。
三个容易踩的坑
attachToRoot传错会丢LayoutParams:第三个参数决定是否立刻挂父容器,传错布局就错位。- 自定义
View要写全构造:只写两参构造,主题属性会掉。 - 热路径用
AsyncLayoutInflater注意线程安全与主题Context,否则填充出来不对。
五、AndroidX 兼容层:*Compat 把新 API 包一层回退
AndroidX 的 *Compat 把新 API 包一层,在旧系统上回退。ContextCompat 取颜色/Drawable 并处理主题;ActivityCompat 申请权限与 startPostponedEnterTransition;ViewCompat 处理嵌套滑动、WindowInsets、无障碍。NotificationCompat.Builder 能在各 API 上拼出渠道、气泡、进度;渠道本身仍要在 26+ 单独创建。WindowCompat.setDecorFitsSystemWindows(false) 是边到边布局的入口,再交给 WindowInsetsControllerCompat 控制栏图标。选 API 时优先 Compat 而不是直接调新方法,避免 minSdk 分支散落。注意部分 Compat 只是 no-op;阅读源码看 SDK_INT 分支,能知道某能力最早从哪一版才真正生效。
附:组件选型对照表
| 需求 | 用什么 | 不要用什么 |
|---|---|---|
| 线程间通信 / 延迟执行 | Handler + Looper(或协程) |
Handler 当精确定时器 |
| 后台任务切回主线程(新代码) | 协程 / Executor + Handler |
AsyncTask(已弃用) |
| 旋转屏幕保住数据 | ViewModel + 协程 / LiveData |
Loader(维护态) |
| 填充布局 | LayoutInflater.inflate(res, parent, attachToRoot) |
传错 attachToRoot、缺主题 Context |
| 自定义 Tint 控件 | AppCompat 的 Factory2 |
忽略 Factory2 直接反射 |
| 低版本兼容新 API | *Compat 系列 |
散落的 SDK_INT 分支 |
| 边到边布局 | WindowCompat.setDecorFitsSystemWindows(false) |
直接操作已废弃的 system UI flag |
| 通知渠道 | NotificationCompat.Builder + 26+ 建渠道 |
只在低版本拼通知忽略渠道 |
Android 基础组件与线程:从 Handler 到 AndroidX 兼容层
https://lautung.com/archives/android-components-and-threads
评论