深入浅出安卓多线程与并发

147 阅读4分钟

深入浅出安卓多线程与并发

一、什么是多线程?为什么需要它?

想象你开了一家奶茶店(你的APP),如果只有一个员工(主线程):

  • 既要收银(UI交互)
  • 又要做奶茶(耗时操作)
  • 还要打扫卫生(系统任务)

结果就是:顾客(用户)等得不耐烦走掉了(ANR应用无响应)。多线程就是雇佣更多员工(创建线程),分工合作提高效率。

安卓为什么特别需要多线程?

  1. 主线程(UI线程)很忙:负责所有界面更新
  2. 耗时操作会"卡死"界面:网络请求、数据库读写、图片处理等
  3. 系统规定:主线程阻塞超过5秒就会触发ANR

二、安卓多线程的四大金刚

1. Thread:基础工人

new Thread(() -> {
    // 后台工作
    runOnUiThread(() -> {
        // 回到主线程更新UI
    });
}).start();

特点

  • 最基础的线程创建方式
  • 需要手动管理生命周期
  • 不能直接更新UI(需要通过runOnUiThread或Handler)

2. Handler/Looper:消息快递员

Handler handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 在主线程处理消息
    }
};

new Thread(() -> {
    // 在子线程发送消息
    handler.sendEmptyMessage(0);
}).start();

工作流程: 子线程 → 发送消息 → MessageQueue → Looper循环取出 → Handler处理

3. AsyncTask:退休的老管家(已废弃但常见于旧代码)

new AsyncTask<Void, Void, String>() {
    @Override
    protected String doInBackground(Void... voids) {
        return "结果"; // 后台执行
    }
    
    @Override
    protected void onPostExecute(String result) {
        textView.setText(result); // 主线程更新UI
    }
}.execute();

特点

  • 简单易用但已废弃(Android 11+)
  • 容易引发内存泄漏
  • 不适合长时间任务

4. 线程池:专业团队管理

ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(() -> {
    // 并发任务
});

优势

  • 避免频繁创建/销毁线程
  • 控制并发数量
  • 提供任务队列

三、现代安卓并发工具包(Jetpack)

1. Coroutine(协程):轻量级线程

lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) { // 切换到IO线程
        doNetworkRequest() // 网络请求
    }
    textView.text = result // 自动切回主线程更新UI
}

特点

  • 用同步写法写异步代码
  • 结构化并发,自动取消
  • 轻量(可创建上千个"协程"而不卡顿)

2. WorkManager:定时后台工

val request = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance(context).enqueue(request)

适用场景

  • 延迟任务
  • 定期同步数据
  • 保证任务最终执行(即使APP退出)

3. LiveData:数据快递员

val liveData = MutableLiveData<String>()
liveData.observe(this) { value ->
    textView.text = value // 自动在主线程更新
}

thread {
    liveData.postValue("新数据") // 子线程发送
}

优势

  • 自动感知生命周期
  • 保证UI更新在主线程
  • 数据驱动界面

四、多线程的三大难题与解法

1. 线程安全:防止数据打架

问题场景:多个线程同时修改同一个银行账户余额

解决方案

  • 加锁:synchronized(lock) { 临界区代码 }
  • 使用原子类:AtomicInteger
  • 用协程的Mutex:mutex.withLock { }

2. 内存泄漏:防止员工赖着不走

问题场景:Activity销毁了,但线程还在运行并持有Activity引用

解决方案

  • 使用ViewModel+LiveData
  • 协程用lifecycleScope
  • 手动取消:thread.interrupt()

3. 线程爆炸:防止雇佣太多员工

问题场景:瞬间创建1000个线程导致OOM

解决方案

  • 使用线程池(如Dispatchers.IO
  • 限制并发数量
  • 用协程替代线程

五、实际开发中的选择指南

场景推荐方案不推荐方案
简单后台任务协程(IO调度器)AsyncTask
定期后台任务WorkManagerTimer
高频率UI更新Handler/LiveData直接线程切换
CPU密集型计算线程池(固定大小)无限创建线程
网络请求+UI更新协程+Retrofit裸Thread+Handler

六、性能优化小贴士

  1. 避免在主线程做这些事

    • 网络请求
    • 数据库操作
    • 大图片解码
    • JSON解析
  2. 协程最佳实践

    • viewModelScope替代lifecycleScope(防止旋转屏幕重建)
    • IO密集型用Dispatchers.IO
    • CPU密集型用Dispatchers.Default
  3. 线程池配置原则

    • CPU密集型:核心数=CPU核心数
    • IO密集型:核心数可以多一些(如CPU核心数*2)

七、调试多线程问题

  1. ANR日志:查看/data/anr/traces.txt
  2. Thread Dumpadb shell kill -3 <pid>
  3. Android Studio工具
    • Profiler中的CPU和线程视图
    • 协程调试插件

八、总结

安卓多线程就像管理一家餐厅:

  • 主线程是前台接待员(必须保持响应)
  • 后台线程是厨房员工(处理耗时工作)
  • 协程是全能型临时工(灵活高效)
  • 线程安全就像避免多个厨师同时用同一个锅

记住三个黄金法则:

  1. 不阻塞主线程(5秒ANR红线)
  2. 合理控制并发量(避免线程爆炸)
  3. 注意生命周期管理(防止内存泄漏)

用好多线程,让你的APP像优秀餐厅一样:前台流畅响应,后台高效运作!