【我的安卓第一课】Android 多线程与异步通信机制(1)

引言

Android 系统基于 Java 的多线程机制,但又在其基础上发展出了许多特有的多线程和异步通信机制。这些机制旨在解决 Android 开发中常见的并发问题,特别是 UI 更新、网络请求、数据库操作等耗时任务的处理。由于 Android ANR 问题的存在,Android的多线程非常常用。

介绍

Java多线程

在 Android 中,Java 原生的多线程机制依然可用,比如:

  • Thread 类:用于创建新线程执行任务
  • Runnable 接口:定义线程要执行的任务
  • ExecutorService:线程池管理类,提供更高效的线程管理
  • FutureCallable:支持返回值的异步任务

这些类的使用方式和Java下的相同。

Handler (句柄)

handler是 Android 开发中用于线程间通信的核心机制之一,像是一种特殊的消息队列。其基于特有的消息循环机制来进行异步通信。它的核心功能是:

  • 发送消息(Message) :将任务封装为 Message 对象,排入消息队列。
  • 处理消息:在指定线程中执行 Message 对应的回调逻辑。
  • 异步调度:通过延迟发送、定时发送等方式控制任务执行时机。

消息循环机制

消息循环机制一共涉及以下几个关键类:

类名 作用
Handler 发送和处理消息的对象
Message 消息载体,携带要传递的数据
MessageQueue 消息队列,用来存放由 Handler 发送的消息
Looper 消息循环器,负责从 MessageQueue 中取出消息并分发给对应的 Handler 处理
Thread 线程,每个拥有 Looper 的线程都具有一个消息循环

Message

Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。其可以使用what指定消息类型,除此之外还可以使用arg1arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。

Handler

Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用HandlersendMessage()方法、post()方法等,而发出的消息经过一系列地辗转处理后,最终会传递到HandlerhandleMessage()方法中。

MessageQueue

MessageQueue是消息队列的意思,其数据结构为一个先进先出的队列,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。其基于无锁设计,通过 native 层的epoll机制实现阻塞式获取消息,避免 CPU 空转。

Looper

Looper每个线程 中的MessageQueue的管家,调用Looperloop()方法后,就会进入一个无限循环 当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到HandlerhandleMessage()方法中。

一个线程可以拥有多个 Handler,但一个线程只能有一个 Looper对象,一个Looper对象也仅有一个相对应的MessageQueue

多个Handler可共享一个LooperMessageQueue;这些不同的 Handler 可以根据 msg.whatmsg.obj 等字段区分处理不同类型的消息。

其中主线程(UI 线程)在应用启动时自动创建了Looper,子线程需要手动调用 Looper.prepare()Looper.loop() 来开启消息循环。

java 复制代码
new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare(); // 准备 Looper
        Handler handler = new Handler();
        Looper.loop();     // 启动消息循环。此时会陷入死循环
    }
}).start();

会不会有队头阻塞问题?

小结

在使用handler时,其流程为:Handler(发送msg到绑定looper) → (MessageQueue → Looper) → Handler 对应回调处理。主要分为以下两种模型

  1. 单线程下,各类之间使用同一线程的Handler通信。用于分发事件。
css 复制代码
类 A 调用主线程 handler.sendMessage()
    ↓
消息进入主线程的 MessageQueue
    ↓
主线程 Looper 取出消息
    ↓
主线程 Handler.handleMessage() 被调用 → 类 B 或其他模块处理逻辑
  1. 多线程下,子线程持有主线程的Handler向主线程通信。
plain 复制代码
子线程任务完成
    ↓
使用主线程的 handler.sendMessage()
    ↓
消息进入主线程的 MessageQueue
    ↓
主线程 Looper 取出消息
    ↓
主线程的 Handler.handleMessage() 被调用 → 安全更新 UI

AsyncTask(异步任务)

已被 Android 标记遗弃,可用但更推荐Kotlin 协程或 RxJava

AsyncTask 是 Android 提供的一个轻量级异步任务类,用于在后台线程执行任务并在主线程更新 UI,其继承自 Object,本质上是对 Thread + Handler 的一个封装,适用于短时间的后台任务,比如网络请求、数据库查询等。

其通常由主线程启动后台线程执行 ,并将结果返回给主线程

使用

AsyncTask是一个抽象类,所以如果想使用它,就必须创建一个子类去继承它。在继承时可以为AsyncTask类指定三个泛型参数,并重写相关方法。

三个泛型

  • Params:任务参数类型
  • Progress:执行进度类型
  • Result:结果类型

相关方法

方法名 线程 描述
onPreExecute() 主线程 执行前准备工作(如显示进度条)
doInBackground(Params...) 子线程 执行耗时操作(不能更新 UI)
onProgressUpdate(Progress...) 主线程 更新进度(通过 publishProgress() 触发)
onPostExecute(Result) 主线程 耗时任务完成后回调,用于更新 UI
onCancelled(Result) 主线程 当任务被取消时调用

前三个从上往下依次执行,第四个以及第五个选一个。

java 复制代码
public class DownloadTask extends AsyncTask<String, Integer, String> {

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // 显示进度条或提示
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    protected String doInBackground(String... urls) {
        // 子线程执行耗时任务
         for (int i = 0; i < urls.length; i++) {
            if (isCancelled()) {
                break; // 如果任务被取消,则退出循环
            }
            publishProgress(i);
            try {
                Thread.sleep(100); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
                return "failed";
            }
        }
        return "success";
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // 主线程更新进度条
        progressBar.setProgress(values[0]);
    }

    @Override
    protected void onPostExecute(String result) {
        // 主线程更新 UI
        textView.setText(result);
        progressBar.setVisibility(View.GONE);
    }

    @Override
    protected void onCancelled(String result) {
        // 任务被取消时的逻辑。downloadTask.cancel(true)
        Toast.makeText(context, "Download cancelled", Toast.LENGTH_SHORT).show();
    }
}
java 复制代码
new DownloadTask().execute(url);

EventBus(事件总线)

Android下面著名的异步消息组件,是一个基于发布/订阅模式的事件总线框架,简化了组件间的通信。使用方式非常简单。使用implementation "org.greenrobot:eventbus:3.2.0"

启用

java 复制代码
@Override
protected void onStart() {
    super.onStart();
    // 把自己绑定到 消息总线 上
    EventBus.getDefault().register(this);
}

@Override
protected void onStop() {
    super.onStop();
    // 解绑
    EventBus.getDefault().unregister(this);
}

发送

java 复制代码
// 定义事件类,用于承载数据
public class MessageEvent {
    public String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}
java 复制代码
// 发送普通事件
EventBus.getDefault().post(new MessageEvent("Hello, EventBus!"));
java 复制代码
// 发送粘滞事件
EventBus.getDefault().postSticky(new MessageEvent("I'm sticky"));

接收

java 复制代码
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    // 在主线程接收事件
    textView.setText(event.message);
}

@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    // 即使订阅晚于发送也能接收到
}

线程模型(ThreadMode)

EventBus 支持五种线程模型,控制事件在哪个线程中被处理:

ThreadMode 描述
POSTING(默认) 事件在发送线程中执行(可能是主线程也可能是子线程)
MAIN 保证在主线程执行,适合更新 UI
MAIN_ORDERED 同 MAIN,但事件按顺序排队执行(API 28+)
BACKGROUND 如果事件是从主线程发送 的,那么订阅方法会在后台线程 执行; 如果事件是从子线程发送 的,订阅方法就在该子线程执行
ASYNC 总是在异步线程中执行(新开一个线程)

粘滞事件

  1. 持久性 :粘滞事件发布后会一直保存 在 EventBus 中,直到被明确移除或被新的粘滞事件覆盖
  2. 自动分发 :当有新的订阅者订阅了某个类型的粘滞事件时,EventBus 会自动将该类型的最新粘滞事件发送给这个订阅者。
  3. 单一实例 :对于每种类型的粘滞事件,EventBus 只会保存最近一次发布的那个事件

从上面可以得知,如果滥用粘滞事件,可能会导致内存不足

java 复制代码
// 移除指定粘滞事件
EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// 或者移除所有粘滞事件
EventBus.getDefault().removeAllStickyEvents();
相关推荐
jyan_敬言12 分钟前
【C++】string类(二)相关接口介绍及其使用
android·开发语言·c++·青少年编程·visual studio
程序员老刘32 分钟前
Android 16开发者全解读
android·flutter·客户端
福柯柯1 小时前
Android ContentProvider的使用
android·contenprovider
不想迷路的小男孩1 小时前
Android Studio 中Palette跟Component Tree面板消失怎么恢复正常
android·ide·android studio
餐桌上的王子1 小时前
Android 构建可管理生命周期的应用(一)
android
菠萝加点糖2 小时前
Android Camera2 + OpenGL离屏渲染示例
android·opengl·camera
用户2018792831672 小时前
🌟 童话:四大Context徽章诞生记
android
yzpyzp2 小时前
Android studio在点击运行按钮时执行过程中输出的compileDebugKotlin 这个任务是由gradle执行的吗
android·gradle·android studio
aningxiaoxixi2 小时前
安卓之service
android
TeleostNaCl3 小时前
Android 应用开发 | 一种限制拷贝速率解决因 IO 过高导致系统卡顿的方法
android·经验分享