Android多线程实现方式

一、技术概述

1.1 技术介绍

Android系统中,默认所有操作都运行在主线程(UI线程)中,主线程负责处理UI更新、用户交互等操作。如果在主线程中执行耗时操作(如网络请求、文件读写、复杂计算),会导致界面卡顿,甚至ANR(应用无响应)。多线程技术就是将耗时操作放到子线程中执行,避免阻塞主线程,提高应用的流畅性和响应速度。

1.2 适用场景

  • 网络请求、文件上传下载

  • 数据库操作、大量数据计算

  • 音视频解码、图片处理

  • 定时任务、倒计时功能

  • 所有耗时超过50ms的操作都应该放到子线程执行

1.3 优缺点

实现方式 优点 缺点 适用场景
Thread+Handler 灵活可控,功能强大 需要手动处理线程切换,代码相对繁琐 复杂的多线程场景
AsyncTask 简单易用,自动处理线程切换 版本兼容性问题,不适合长时间耗时操作 短时间的后台任务
HandlerThread 自带消息循环的线程,可以处理串行任务 不适合并行任务 串行执行的后台任务
IntentService 适合执行后台串行任务,执行完自动销毁 无法并行处理任务 后台串行任务,如下载、上传
线程池 复用线程,减少线程创建销毁开销,控制并发数 配置相对复杂 大量频繁的耗时操作

二、基本效果

使用多线程后,可以实现:

  1. 执行网络请求时,界面仍然可以响应用户操作,不会卡顿

  2. 游戏倒计时可以在后台运行,不会因为用户操作界面而暂停

  3. 上传头像、下载文件等操作可以在后台执行,不影响用户使用其他功能

  4. 大量数据计算时,界面保持流畅

三、技术本质

Android多线程的本质是CPU时间片的轮转调度,多个线程交替执行,宏观上看起来是同时运行的。Android多线程核心要解决两个问题:

  1. 耗时操作放到子线程执行:避免阻塞主线程

  2. 子线程执行完成后切换回主线程更新UI:因为Android规定只有主线程才能更新UI 运行流程:

  3. 主线程收到耗时操作请求

  4. 创建子线程,将耗时操作放到子线程执行

  5. 子线程执行耗时操作,执行过程中可以通知主线程更新进度

  6. 子线程执行完成后,通过Handler、runOnUiThread等方式切换回主线程

  7. 主线程根据执行结果更新UI

四、核心原理

4.1 线程状态

状态 说明
新建状态(New) 创建了Thread对象,但还没有调用start()方法
就绪状态(Runnable) 调用了start()方法,等待CPU调度执行
运行状态(Running) 获得CPU时间片,正在执行
阻塞状态(Blocked) 因为某些原因暂停执行,如sleep()、wait()、IO阻塞
死亡状态(Terminated) 线程执行完成或异常退出

4.2 线程切换原理

子线程不能直接更新UI,必须切换回主线程,常用的切换方式:

  1. Handler.sendMessage()/post():将消息发送到主线程的消息队列,由主线程处理

  2. Activity.runOnUiThread():直接在子线程中执行主线程的代码

  3. View.post()/postDelayed():将Runnable投递到主线程执行

  4. AsyncTask的onProgressUpdate()/onPostExecute():自动切换到主线程

五、使用示例

5.1 Thread+Handler方式

复制代码
// 主线程中创建Handler
private Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 1001:
                // 更新UI
                int progress = (int) msg.obj;
                progressBar.setProgress(progress);
                break;
            case 1002:
                // 任务完成
                Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
                break;
        }
    }
};
// 启动子线程执行耗时操作
new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            try {
                // 模拟下载耗时
                Thread.sleep(50);
                // 发送进度消息到主线程
                Message msg = Message.obtain();
                msg.what = 1001;
                msg.obj = i;
                mHandler.sendMessage(msg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 发送完成消息
        mHandler.sendEmptyMessage(1002);
    }
}).start();

5.2 runOnUiThread简化方式

复制代码
new Thread(new Runnable() {
    @Override
    public void run() {
        // 子线程执行耗时操作
        final String result = doLongTimeOperation();
        // 切换到主线程更新UI
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                tvResult.setText(result);
            }
        });
    }
}).start();

5.3 AsyncTask方式

复制代码
// 三个泛型参数:1.传入参数类型 2.进度更新类型 3.返回结果类型
private class DownloadTask extends AsyncTask<String, Integer, Boolean> {
    // 任务开始前执行,运行在主线程
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        progressBar.setVisibility(View.VISIBLE);
    }
    // 后台执行耗时操作,运行在子线程
    @Override
    protected Boolean doInBackground(String... urls) {
        String url = urls[0];
        for (int i = 0; i <= 100; i++) {
            try {
                Thread.sleep(50);
                // 发布进度,触发onProgressUpdate
                publishProgress(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }
    // 进度更新,运行在主线程
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        progressBar.setProgress(values[0]);
    }
    // 任务完成,运行在主线程
    @Override
    protected void onPostExecute(Boolean result) {
        super.onPostExecute(result);
        progressBar.setVisibility(View.GONE);
        if (result) {
            Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
        }
    }
}
// 使用方式
new DownloadTask().execute("https://example.com/file.apk");

5.4 线程池方式

复制代码
// 创建固定大小线程池,最多同时3个线程执行
ExecutorService threadPool = Executors.newFixedThreadPool(3);
// 执行任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        // 执行耗时操作1
    }
});
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        // 执行耗时操作2
    }
});
// 关闭线程池
threadPool.shutdown();

5.5 在专注力测试APP中的应用示例

游戏倒计时功能使用Thread+Handler实现:

复制代码
// 游戏倒计时
private Handler mGameHandler = new Handler(Looper.getMainLooper());
private Runnable mCountDownRunnable;
private int mCountDownTime = 30; // 30秒
private void startCountDown() {
    mCountDownRunnable = new Runnable() {
        @Override
        public void run() {
            mCountDownTime--;
            tvTime.setText("剩余时间:" + mCountDownTime + "s");
            if (mCountDownTime > 0) {
                // 每秒执行一次
                mGameHandler.postDelayed(this, 1000);
            } else {
                // 时间到,结束游戏
                endGame();
            }
        }
    };
    mGameHandler.post(mCountDownRunnable);
}
@Override
protected void onDestroy() {
    super.onDestroy();
    // 移除回调,避免内存泄漏
    mGameHandler.removeCallbacks(mCountDownRunnable);
}

六、常见问题与解决方案

6.1 ANR问题

问题 :应用出现无响应弹窗 原因 :主线程被耗时操作阻塞超过5秒,或者BroadcastReceiver 10秒内没有处理完成 解决方案

  1. 所有耗时操作(网络、文件、数据库、复杂计算)都放到子线程执行

  2. 避免在主线程中执行复杂的布局计算、大量循环操作

6.2 内存泄漏问题

问题 :非静态内部类线程持有Activity引用,Activity销毁后线程还在运行,导致Activity无法回收 解决方案

  1. 使用静态内部类+弱引用的方式
复制代码
private static class MyRunnable implements Runnable {
    private WeakReference<MainActivity> mActivity;
    public MyRunnable(MainActivity activity) {
        mActivity = new WeakReference<>(activity);
    }
    @Override
    public void run() {
        MainActivity activity = mActivity.get();
        if (activity != null) {
            // 执行操作
        }
    }
}
  1. Activity销毁时,停止正在执行的线程,移除Handler的回调和消息

6.3 线程安全问题

问题 :多个线程同时访问同一个数据,导致数据混乱 解决方案

  1. 使用synchronized关键字同步访问共享数据

  2. 使用线程安全的集合类,如ConcurrentHashMap

  3. 尽量避免多个线程同时修改同一个变量

6.4 子线程更新UI崩溃

问题 :在子线程中直接更新UI,抛出CalledFromWrongThreadException异常 解决方案

  1. 使用Handler、runOnUiThread、View.post等方式切换到主线程再更新UI

  2. 严格遵守只有主线程才能更新UI的规则

七、学习总结

7.1 学习收获

掌握了Android中几种常用的多线程实现方式,了解了每种方式的优缺点和适用场景,理解了线程切换的原理,能够在开发中选择合适的多线程实现方式,避免主线程阻塞和ANR问题。

7.2 项目应用场景

在本次专注力测试APP中,多线程的应用场景:

  1. 游戏倒计时、计时功能的实现

  2. 网络请求(登录、上传成绩、获取排行榜等)放到子线程执行

  3. 头像上传、图片加载处理

  4. 本地数据库操作、文件读写

  5. 游戏成绩计算、复杂逻辑处理

相关推荐
黄林晴15 小时前
告别 KMP 选型地狱!klibs.io 上线,全平台库一键筛选太省心
android·kotlin
cyw899815 小时前
m3e向量化mysql某表
android·数据库·mysql
索西引擎15 小时前
【LangChain 1.0】接入 DeepSeek API:从 API Key 申请到流式响应的完整实践
android·java·langchain
山峰哥15 小时前
索引策略与SQL优化:从Explain对比到生产调优的完整方法论
android·java·数据库·sql·性能优化·深度优先
二蛋和他的大花15 小时前
高德地图 Flutter 插件:跨 Android / iOS / HarmonyOS 的完整实现
android·flutter·ios
2601_9574188015 小时前
相机如何连接手机?通俗易懂的PTP/MTP连接原理解析
android·数码相机·架构
Co_Hui15 小时前
Android: 事件分发
android
吕氏春秋i15 小时前
android kotlin Compose 蓝牙库推荐
android·gitee·kotlin
鹏晨互联15 小时前
《Kotlin高阶函数完全指南:从入门到精通的15个核心函数》
android·开发语言·kotlin