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

200 阅读7分钟

引言

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

介绍

Java多线程

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

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

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

Handler (句柄)

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

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

消息循环机制

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

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

image.png

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() 来开启消息循环。

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通信。用于分发事件。
A 调用主线程 handler.sendMessage()
    ↓
消息进入主线程的 MessageQueue
    ↓
主线程 Looper 取出消息
    ↓
主线程 Handler.handleMessage() 被调用 → 类 B 或其他模块处理逻辑

2. 多线程下,子线程持有主线程的Handler向主线程通信。

子线程任务完成
    ↓
使用主线程的 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)主线程当任务被取消时调用

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

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();
    }
}
new DownloadTask().execute(url);

EventBus(事件总线)

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

启用

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

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

发送

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

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

接收

@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 只会保存最近一次发布的那个事件

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

// 移除指定粘滞事件
EventBus.getDefault().removeStickyEvent(MessageEvent.class);
// 或者移除所有粘滞事件
EventBus.getDefault().removeAllStickyEvents();