当前位置:首页>安卓APP>安卓ota之action机制见谷歌如何用设计模式编排复杂流程

安卓ota之action机制见谷歌如何用设计模式编排复杂流程

  • 2026-10-24 10:25:14
安卓ota之action机制见谷歌如何用设计模式编排复杂流程

一个好的框架,不是让代码变多,而是让代码变简单。

上一篇我们拆开了 payload.bin 的二进制结构——CrAU 头、protobuf manifest、data blob,知道了 OTA 包"长什么样"。

但知道包的格式还不够。谁来下载这个包?谁来解析 manifest?谁来执行里面成百上千个 InstallOperation?谁来校验、谁来切 slot?

这些事情不是靠一个函数就能搞定的。一个 OTA 流程涉及十几个步骤,每个步骤的逻辑完全不同,但它们必须有序执行、共享数据、优雅失败。

Google 的解法是:用一套基于模板的 Action 框架,把每个步骤封装成独立的、可组合的、类型安全的 Action,然后用一个调度器按顺序串联它们。

为什么不用简单的函数调用?

最直觉的做法:

bool DoOTA() {  if (!DownloadPayload()) return false;  if (!VerifyFilesystem()) return false;  if (!RunPostinstall()) return false;  if (!SwitchSlot()) return false;  return true; }

看起来简洁,但有几个致命问题:

❌ 简单函数调用

• 无法暂停恢复

• 无法取消

• 无法扩展

• 类型不安全

✅ Action 框架

• 随时暂停/恢复

• 优雅终止

• 开闭原则扩展

• 编译时类型检查

1. 无法暂停恢复 — 网络断了,DownloadPayload() 卡住了怎么办?函数调用是同步的,调用者线程被阻塞,什么都做不了。

2. 无法取消 — 用户点了"取消更新",但函数还在跑,怎么通知它停下来?函数没有标准的"终止"接口。

3. 无法扩展 — 想加一个步骤(比如预校验),得改 do_update() 函数本身。改一个地方,可能影响其他步骤。

4. 类型不安全 — 每个函数的输入输出是什么?靠函数签名说不清楚,全靠程序员的"记忆"。

Action 框架用一套模板机制,优雅地解决了这些问题。

Action 的继承体系

先看整体类图:

AbstractAction(纯虚基类)

+ PerformAction() : void

+ SuspendAction() : void

+ ResumeAction() : void

+ TerminateAction() : void

+ Type() : string

⬇

Action<T>(模板派生类 · CRTP)

T = 子类自身(编译时类型萃取)

InputType = ActionTraits<T>::InputType

OutputType = ActionTraits<T>::OutputType

+ GetInputObject() / GetOutputObject()

⬇

InstallPlanAction

准备安装计划

DownloadAction

下载 payload

FilesystemVerifier

验证文件系统

PostinstallRunner

收尾 + 切 slot

AbstractAction:统一接口

class AbstractAction { public:  virtual ~AbstractAction() = default;  // 生命周期四件套  virtual void PerformAction() = 0;      // 开始执行  virtual void SuspendAction() = 0;      // 暂停  virtual void ResumeAction() = 0;       // 恢复  virtual void TerminateAction() = 0;    // 终止  // 类型信息(用于日志和调试)  virtual std::string Type() const = 0; };

这是所有 Action 的根接口。ActionProcessor 只认这个接口,不关心具体实现。

Action<T>:模板 + CRTP

template <class T> class Action : public AbstractAction { public:  // CRTP:T 是子类自身  using DerivedType = T;  // 输入输出类型声明  using InputType = typename ActionTraits<T>::InputType;  using OutputType = typename ActionTraits<T>::OutputType;  // 获取输入/输出对象  InputType* GetInputObject() { return input_; }  OutputType* GetOutputObject() { return output_; } protected:  InputType* input_ = nullptr;  OutputType* output_ = nullptr; };

什么是 CRTP?

CRTP(Curiously Recurring Template Pattern)是 C++ 的经典技巧:基类用模板参数拿到子类的类型信息。好处是编译时类型检查——如果两个 Action 的类型不匹配,编译器直接报错,不用等到运行时才发现。

ActionTraits:类型萃取

// DownloadAction 的 traits template <> struct ActionTraits<DownloadAction> {  using InputType = InstallPlan;  using OutputType = InstallPlan; }; // FilesystemVerifierAction 的 traits template <> struct ActionTraits<FilesystemVerifierAction> {  using InputType = InstallPlan;  using OutputType = InstallPlan; };

ActionTraits 是一个类型萃取器。每个 Action 通过特化它来声明"我需要什么输入"和"我产出什么输出"。ActionProcessor 在编译时就能检查 Action 链的类型是否兼容。

Action 之间的数据传递

Action 之间怎么传数据?不是通过函数参数,而是通过共享 InstallPlan 对象。

InstallPlan 的字段被逐步填充

📦 InstallPlan(共享对象)

download_url · payload · partitions · signatures · verity_hash · switch_slot

① InstallPlanAction

→ download_url
→ source_slot / target_slot

② DownloadAction

→ partitions
→ signatures

③ VerifierAction

→ verity_hash
→ verified = true

④ PostinstallRunner

→ switch_slot_on_reboot

每个 Action 往 InstallPlan 里添加自己的结果,下一个 Action 读取前一个写入的字段

为什么用共享对象而不是管道?

OTA 的数据流是单向线性的:没有分支,没有并发,共享对象就够了。每个 Action 只修改 InstallPlan 中自己负责的字段,互不干扰。

ActionProcessor 的调度机制

上一篇详细讲过 ActionProcessor 的代码实现。这里从设计角度补充几个关键点。

状态机视角

IDLE

初始状态

→

RUNNING

执行 Action

↓ 成功

↓ 失败

StartNext

下一个 Action

ABORTED

流水线终止

↓ 队列空

FINISHED ✅

所有 Action 完成

委托模式

ActionProcessor 通过 ActionProcessorDelegate 接口通知上层:

class ActionProcessorDelegate { public:  // 所有 Action 完成(成功或失败)  virtual void ProcessingDone(      ActionProcessor* processor,      ErrorCode code) = 0; };

UpdateAttempter 实现这个接口,收到通知后更新状态、上报指标、通知用户。

好处:ActionProcessor 不知道也不关心上层是谁。它只认 Delegate 接口。你可以用同一套框架跑不同的流程——只要提供不同的 Delegate 实现。

单线程 + 消息循环

整个 update_engine 运行在单线程上。异步操作通过消息循环实现非阻塞:

// 不是阻塞等待网络数据 // 而是注册回调,数据到了由消息循环触发 http_fetcher_->SetReceivedBytesCallback(    [this](const void* data, size_t length) {      return OnReceivedBytes(data, length);    });

🔄

天然顺序

OTA 流程无并行需求

🔒

无锁设计

避免竞态条件

⚡

异步 I/O

磁盘和网络不阻塞

三种 Action 实现模式

每个 Action 都遵循相同的接口,但根据自身特性选择不同的实现模式。

① 同步 Action

代表:InstallPlanAction

特点:做完直接回调

场景:准备工作、数据填充

PerformAction()
→ 直接 ActionComplete()

② 异步 Action

代表:DownloadAction

特点:启动后等回调

场景:网络请求、I/O 操作

PerformAction()
→ BeginTransfer()
→ OnComplete() 才回调

③ 可暂停 Action

代表:FilesystemVerifier

特点:支持暂停/恢复

场景:长时间运行、资源调度

PerformAction()
SuspendAction() 暂停
ResumeAction() 恢复

错误处理:一票否决

ActionProcessor 的错误策略极其简单:任何一个 Action 失败,整条流水线立即终止。

void ActionProcessor::ActionComplete(    AbstractAction* action, ErrorCode code) {  if (code != ErrorCode::kSuccess) {    // 一票否决:清理所有剩余 Action    for (auto* remaining : actions_) {      remaining->TerminateAction();    }    actions_.clear();    delegate_->ProcessingDone(this, code);    return;  }  StartNextAction(); }

为什么不在 ActionProcessor 层面做重试?

每个 Action 内部自己处理重试:DownloadAction 网络抖动自动重试,VerifierAction 验证失败无重试意义,PostinstallRunner 脚本失败重试大概率还是失败。把重试策略下放到 Action 内部,让每个 Action 根据自身特性选择最合适的策略。

GoF 设计模式对照表

设计模式 在 Action 框架中的体现
Chain of Responsibility ActionProcessor 按顺序调用每个 Action
Template Method AbstractAction 定义接口,子类实现具体逻辑
Observer ActionProcessorDelegate 监听生命周期事件
Command 每个 Action 封装了一个独立的操作
CRTP Action<T> 的模板参数是子类自身,编译时多态

不是刻意套模式,而是问题的本质决定了这些模式自然地出现。

框架的扩展性

想加一个新的步骤?只需要三步:

// 1. 定义新 Action class MyNewAction : public Action<MyNewAction> { public:  void PerformAction() override {    // 做你的事情    processor_->ActionComplete(this, ErrorCode::kSuccess);  } }; // 2. 特化 ActionTraits template <> struct ActionTraits<MyNewAction> {  using InputType = InstallPlan;  using OutputType = InstallPlan; }; // 3. 加入流水线 processor_->EnqueueAction(    std::make_unique<MyNewAction>());

开闭原则 — 对扩展开放,对修改关闭。不需要改 ActionProcessor,不需要改其他 Action。

写在第三篇结尾

Action 框架的设计教给我们一个道理:

复杂系统的优雅,不在于用了多花哨的模式,而在于对问题本质的深刻理解。

OTA 就是一系列步骤,按顺序执行,每步都可能失败。Action 框架用不到 300 行核心代码完美地建模了这个问题:模板保证类型安全,委托模式解耦上下文,单线程 + 异步 I/O 避免锁的复杂性。

下一篇,我会深入 DownloadAction 和 DeltaPerformer,看看 OTA 包是怎么从服务器安全地边下载边写入 inactive slot 的。

💡 思考题

如果要让 ActionProcessor 支持并行执行(比如同时下载和验证),框架需要做哪些改动?

📚 系列文章

第一篇:update_engine 的灵魂骨架

第二篇:Payload 格式 — 一个 OTA 包的二进制解剖

第三篇:谷歌如何用设计模式编排复杂流程 ← 你在这里

第四篇:DownloadAction 与 DeltaPerformer(即将发布)

关注我,持续分享 Android 系统底层知识 👇

我的灵魂是一片海

最新文章

随机文章