Android A/B System OTA分析(五)客户端参数

Android A/B 系统基础入门系列《Android A/B 系统》已完结,文章列表:

Android A/B System OTA分析(一)概览
Android A/B System OTA分析(二)系统image的生成
Android A/B System OTA分析(三)主系统和bootloader的通信
Android A/B System OTA分析(四)系统的启动和升级
Android A/B System OTA分析(五)客户端参数
Android A/B System OTA分析(六)如何获取 payload 的 offset 和 size


更多关于 Android OTA 升级相关文章,请参考《Android OTA 升级系列专栏文章导读》。

0. 导读

针对群里特别多人问 update_engine_client 的 offset 和 size 参数问题,特地开辟两篇文章全面讲述 offset 和 size 参数,主要包括传入的 offset 和 size 参数在代码中是如何流转的,offset 和 size 参数最终做什么用途?以及如何计算要传入的 offset 和 size 参数?

本篇从命令行开始,一步一步向下跟踪 offset 和 size 参数的传递,由于涉及具体的代码分析,所以整个文章比较繁琐。主要流程包括,命令行参数到客户端参数解析,客户端如何将参数通过 binder 服务传递给服务端,服务端再将参数设置到 HttpFetcher,然后在具体的实现中根据是本地文件还是远程文件进行不同的处理。

不论是本地文件还是远程文件,offset 和 size 都用于指定 payload 数据读取时的偏移量,以及读取数据的大小。

如果你想了解 offset 和 size 被传递和解析过程,那么本篇文章适合你。如果你只关心大概结论,请跳转到 4.3 offset 和 size 总结 以及 5. 总结 查看相关结论。

本文涉及的Android代码版本:android‐7.1.1_r23 (NMF27D)

1. update_engine_client 客户端支持的参数

    在上一篇《Android A/B System OTA 分析(四)系统的启动和升级》中,提供了一个使用 update_engine_client 升级的例子:

bcm7252ssffdr4:/ # update_engine_client \
--payload=http://stbszx-bld-5/public/android/full-ota/payload.bin \
--update \
--headers="\
  FILE_HASH=ozGgyQEcnkI5ZaX+Wbjo5I/PCR7PEZka9fGd0nWa+oY= 
  FILE_SIZE=282164983 
  METADATA_HASH=GLIKfE6KRwylWMHsNadG/Q8iy5f7ENWTatvMdBlpoPg= 
  METADATA_SIZE=21023 
"

   这里传入了 --payload, --update 和 --headers 三个参数。

   update_engine_client 支持的全部参数如下:

update_engine_client --help 
 Android Update Engine Client
 
   --cancel  (Cancel the ongoing update and exit.)  type: bool  default: false
   --follow  (Follow status update changes until a final state is reached. Exit status is 0 if the update succeeded, and 1 otherwise.)  type: bool  default: false
   --headers  (A list of key-value pairs, one element of the list per line. Used when --update is passed.)  type: string  default: ""
   --help  (Show this help message)  type: bool  default: false
   --offset  (The offset in the payload where the CrAU update starts. Used when --update is passed.)  type: int64  default: 0
   --payload  (The URI to the update payload to use.)  type: string  default: "http://127.0.0.1:8080/payload"
   --reset_status  (Reset an already applied update and exit.)  type: bool  default: false
   --resume  (Resume a suspended update.)  type: bool  default: false
   --size  (The size of the CrAU part of the payload. If 0 is passed, it will be autodetected. Used when --update is passed.)  type: int64  default: 0
   --suspend  (Suspend an ongoing update and exit.)  type: bool  default: false
   --update  (Start a new update, if no update in progress.)  type: bool  default: false
2. update_engine_client 的参数是如何解析的?

在终端调用 update_engine_client 进行升级的流程跟踪分析请参考《Android Update Engine 分析(三)客户端进程》,下面将命令行参数的流转流程总结如下:

2.1 客户端命令行传入参数进行升级
# 终端传入参数调用 update_engine_client 升级
update_engine_client \
----payload=http://192.168.1.200/ota_package/payload.bin \
--update \
--headers="\
FILE_HASH=R7FKJ+WstITiZpW2/nMIl6Duln+bwgLnTWy0W1d79cg=
FILE_SIZE=2607014024
METADATA_HASH=eW3P9td/sH6dV0es1gf5t0N1U3iE/CMzdXZpmi1gKUA=
METADATA_SIZE=180056
"
2.2 客户端解析并使用参数

客户端实际是在 UpdateEngineClientAndroid::OnInit() 函数中解析并使用参数的,如下:

// 文件: system/update_engine/update_engine_client_android.cc

int UpdateEngineClientAndroid::OnInit() {
  ...


  // 以下定义了 FLAGS_payload, FLAGS_offset, FLAGS_size, FLAGS_headers 等接收命令行的参数
  DEFINE_string(payload,
                "http://127.0.0.1:8080/payload",
                "The URI to the update payload to use.");
  DEFINE_int64(offset, 0,
               "The offset in the payload where the CrAU update starts. "
               "Used when --update is passed.");
  DEFINE_int64(size, 0,
               "The size of the CrAU part of the payload. If 0 is passed, it "
               "will be autodetected. Used when --update is passed.");
  DEFINE_string(headers,
                "",
                "A list of key-value pairs, one element of the list per line. "
                "Used when --update is passed.");

   ...

  // 在指定了 "--update" 进行升级时,将命令行参数传递给 binder 服务的 applyPayload 函数
  if (FLAGS_update) {
    ...
    Status status = service_->applyPayload(
        android::String16{FLAGS_payload.data(), FLAGS_payload.size()},
        FLAGS_offset,
        FLAGS_size,
        and_headers);
    ...
  }

命令行参数为什么是在这里解析,具体分析请参考:请参考《Android Update Engine 分析(三)客户端进程》

客户端将参数解析后将 payload, offset, size 和 headers 参数传递给 binder 服务。

2.3 binder 服务将参数传递给服务端
// 文件: system/update_engine/binder_service_android.cc

Status BinderUpdateEngineAndroidService::applyPayload(
    const android::String16& url,
    int64_t payload_offset,
    int64_t payload_size,
    const std::vector<android::String16>& header_kv_pairs) {
  ...
  // Binder 服务将从客户端拿到的参数传递给服务端的 ApplyPayload 接口
  if (!service_delegate_->ApplyPayload(
          payload_url, payload_offset, payload_size, str_headers, &error)) {
    return ErrorPtrToStatus(error);
  }
  return Status::ok();
}

   在这里 binder 将参数转发给服务端进程。

2.4 服务端处理升级参数

服务端的 UpdateAttempterAndroid::ApplyPayload() 函数最终处理传入的 payload, offset, size 和 headers 参数:

// 文件: system/update_engine/update_attempter_android.cc
bool UpdateAttempterAndroid::ApplyPayload(
    const string& payload_url,
    int64_t payload_offset,
    int64_t payload_size,
    const vector<string>& key_value_pair_headers,
    brillo::ErrorPtr* error) {
  ...

  install_plan_.download_url = payload_url;
  ...
  base_offset_ = payload_offset;
  install_plan_.payload_size = payload_size;
  if (!install_plan_.payload_size) {
    if (!base::StringToUint64(headers[kPayloadPropertyFileSize/*"FILE_SIZE"*/],
                              &install_plan_.payload_size)) {
      install_plan_.payload_size = 0;
    }
  }
  ...

  BuildUpdateActions(payload_url);
  ...
}

从上面的代码里,我们最终看到:

*  命令行参数 --payload 在这里用于设置 install_plan_.download_url,也传递给 BuildUpdateActions(payload_url) 设置 DownloadAction。
*  命令行参数 --offset 在这里用于设置 base_offset_
*  命令行参数 --size 在这里用于设置 install_plan_.payload_size

如果没有提供 --size 参数,则会从属性值中提取 FILE_SIZE 作为 payload_size
对于上面这些参数,问得最多的有以下两个:

1. 除了使用 http 获取远程文件进行升级外,可以使用本地文件进行升级吗?
2. 如果使用 update.zip 文件,如何设置 offset 和 size 参数?


3. 使用远程文件和本地文件升级
3.1 对 download_url 的处理
// 文件: system/update_engine/update_attempter_android.cc
void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
  ...
  
  // 如果是 "file:///" 这样的格式,使用 FileFetcher
  if (FileFetcher::SupportedUrl(url)) {
    DLOG(INFO) << "Using FileFetcher for file URL.";
    download_fetcher = new FileFetcher();
  } else {
    // 其余的使用 LibcurlHttpFetcher,获取网络数据
    LibcurlHttpFetcher* libcurl_fetcher =
        new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
    libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
    download_fetcher = libcurl_fetcher;
  }
  ...
}

仔细查看这个函数传入的 url 参数并没有用来传递给某些变量,而仅仅专递给函数调用 FileFetcher::SupportedUrl(url) 用于判断是是不是 file:/// 开头的文件协议。真正被用来获取数据的,还是 install_plan_.download_url。

因此,升级既可以使用本地文件(“file:///” 协议);也可以使用远程文件(“http://” 或 “https://” 协议)进行升级。

代码中支持 “file:///”, “http://”, “https://”, “file://”, 第一个走本地文件读取路径, 后三个走 curl 读取路径。
但实际上我没有使用 “https://” 和 “file://” 验证过。至于 curl 是否支持其它协议,我还没有试过,如果你有试过,欢迎群里一起讨论。

3.2 远程文件升级示例

从远程 http://192.168.1.200/full_0814.zip 获取升级数据:

update_engine_client \
--payload=http://192.168.1.200/update.zip \
--update \
--offset=7985 \
--size=1096237091 \
--headers="\
FILE_HASH=fyDltdH3RkMxjJMLKWMU8SAkeWlnp+Dxb42jQpo30zc=
FILE_SIZE=1096237091
METADATA_HASH=72+DLYstrkKDp41oTV0xMCJtAIH5YAIs4Mw/4VSUXbY=
METADATA_SIZE=125561
"
3.3 本地文件升级示例

从本地 file:///data/ota_package/payload.bin 获取升级数据:

update_engine_client \
--payload=file:///data/ota_package/payload.bin \
--update \
--headers="\
FILE_HASH=R7FKJ+WstITiZpW2/nMIl6Duln+bwgLnTWy0W1d79cg=
FILE_SIZE=2607014024
METADATA_HASH=eW3P9td/sH6dV0es1gf5t0N1U3iE/CMzdXZpmi1gKUA=
METADATA_SIZE=180056
"
4. 如何设置 offset 和 size 参数?

从第 3 节的两个例子可见,升级时既可以使用 payload.bin 文件,也可以用脚本生成的 update.zip 文件。

使用不同格式的文件,就涉及到另外两个参数 offset 和 size 的设置。

*  使用 payload.bin 该如何设置 offset 和 size?
*  使用 update.zip 又该如何设置 offset 和 size?

想要知道如何设置 offset 和 size 参数,就需要知道这两个参数是用来做什么的,包括两个问题:在哪里使用,以及如何使用?

4.1 offset 和 size 参数的使用

在前面 2.4 节我们分析过传入的 payload, offset 和 size 参数在服务端代码 UpdateAttempterAndroid::ApplyPayload() 中被使用。

我们具体看下 offset 和 size 是如何被使用的:

// 文件: system/update_engine/update_attempter_android.cc
bool UpdateAttempterAndroid::ApplyPayload(
    const string& payload_url,
    int64_t payload_offset,
    int64_t payload_size,
    const vector<string>& key_value_pair_headers,
    brillo::ErrorPtr* error) {
  ...

  install_plan_.download_url = payload_url;
  ...
  base_offset_ = payload_offset;
  install_plan_.payload_size = payload_size;
  if (!install_plan_.payload_size) {
    if (!base::StringToUint64(headers[kPayloadPropertyFileSize/*"FILE_SIZE"*/],
                              &install_plan_.payload_size)) {
      install_plan_.payload_size = 0;
    }
  }
  ...

  BuildUpdateActions(payload_url);
  ...
}

这里 payload_offset 和 payload_size 分别被设置给 base_offset_ 和 install_plan_.payload_size 变量。

代码中搜索 base_offset_,发现只在 UpdateAttempterAndroid::SetupDownload() 函数中被使用,被用来传递给 fetcher->AddRange 函数:

system/update_engine$ grep -Rnw base_offset_ .
./update_attempter_android.h:158:  int64_t base_offset_{0};
./update_attempter_android.cc:146:  base_offset_ = payload_offset;
./update_attempter_android.cc:495:    fetcher->AddRange(base_offset_,
./update_attempter_android.cc:505:      fetcher->AddRange(base_offset_ + resume_offset);
./update_attempter_android.cc:507:      fetcher->AddRange(base_offset_ + resume_offset,
./update_attempter_android.cc:512:      fetcher->AddRange(base_offset_, install_plan_.payload_size);
./update_attempter_android.cc:516:      fetcher->AddRange(base_offset_);
grep: ./.clang-format: No such file or directory

函数 UpdateAttempterAndroid::SetupDownload() 的实现如下:

void UpdateAttempterAndroid::SetupDownload() {
  MultiRangeHttpFetcher* fetcher =
      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
  fetcher->ClearRanges();
  
  // 如果 install_plan_.is_resume 为 true, 即 resume 的情况
  if (install_plan_.is_resume) {
    // Resuming an update so fetch the update manifest metadata first.
    int64_t manifest_metadata_size = 0;
    int64_t manifest_signature_size = 0;
    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
    fetcher->AddRange(base_offset_,
                      manifest_metadata_size + manifest_signature_size);
    // If there're remaining unprocessed data blobs, fetch them. Be careful not
    // to request data beyond the end of the payload to avoid 416 HTTP response
    // error codes.
    int64_t next_data_offset = 0;
    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
    uint64_t resume_offset =
        manifest_metadata_size + manifest_signature_size + next_data_offset;
    if (!install_plan_.payload_size) {
      fetcher->AddRange(base_offset_ + resume_offset);
    } else if (resume_offset < install_plan_.payload_size) {
      fetcher->AddRange(base_offset_ + resume_offset,
                        install_plan_.payload_size - resume_offset);
    }
  } else {
  // 这里 install_plan_.is_resume 为 false,表示非 resume 情况,比如刚开始下载
    if (install_plan_.payload_size) {
      fetcher->AddRange(base_offset_, install_plan_.payload_size);
    } else {
      // If no payload size is passed we assume we read until the end of the
      // stream.
      fetcher->AddRange(base_offset_);
    }
  }
}

简单来说,上面这个函数针对第一次 download,还是暂停后恢复 download 设置提取数据的范围,包括 offset 和 size。

1. 设置第一次下载的 offset 和 size
     对于第一次下载,直接用 base_offset_ 和 install_plan_.payload_size 进行设置,如果没有 payload_size,则只传递 base_offset_ 给 fetcher,意思就是告诉 fetcher 从 base_offset_ 开会,一直下载到数据结束。

if (install_plan_.payload_size) {
  fetcher->AddRange(base_offset_, install_plan_.payload_size);
} else {
  // If no payload size is passed we assume we read until the end of the
  // stream.
  fetcher->AddRange(base_offset_);
}

2. 调整中断后恢复下载的 offset 和 size
    对于中断后恢复下载的情况,无非就是对 base_offset_ 和 payload_size 进行调整。

// 中断后恢复下载,计算恢复后的 resume_offset,然后使用 resume_offset 对 base_offset_ 和 install_plan_.payload_size 进行调整
uint64_t resume_offset =
    manifest_metadata_size + manifest_signature_size + next_data_offset;
if (!install_plan_.payload_size) {
  fetcher->AddRange(base_offset_ + resume_offset);
} else if (resume_offset < install_plan_.payload_size) {
  fetcher->AddRange(base_offset_ + resume_offset,
                    install_plan_.payload_size - resume_offset);
}

3. fetch 是什么?AddRange又做了什么?
   前面提到 offset 和 size 被传递给 fetcher->AddRange() 调用。

   这里的 fetcher 是 MultiRangeHttpFetcher,从定义看,其包装了一个 HttpFetcher。

   调用 fetcher->AddRange(offset, size) 时,将 offset 和 size 参数存放到私有成员变量 ranges_ 中。

  void AddRange(off_t offset, size_t size) {
    CHECK_GT(size, static_cast<size_t>(0));
    // 这里参数 (offset, size) 用于设置 range 的 (offset, length)
    ranges_.push_back(Range(offset, size));
  }

  void AddRange(off_t offset) {
    ranges_.push_back(Range(offset));
  }

    然后在传输开始时,调用 MultiRangeHttpFetcher::StartTransfer() 将上面提到的 ranges_, 将其传递给子类 base_fetcher_ 去设置实际传输的 offset 和 length。

// State change: Stopped or Downloading -> Downloading
void MultiRangeHttpFetcher::StartTransfer() {
  if (current_index_ >= ranges_.size()) {
    return;
  }

  Range range = ranges_[current_index_];
  LOG(INFO) << "starting transfer of range " << range.ToString();

  // 将 offset 传递给具体使用的 base_fetcher
  bytes_received_this_range_ = 0;
  base_fetcher_->SetOffset(range.offset());

  // 将 length 传递给具体使用的 base_fetcher
  if (range.HasLength())
    base_fetcher_->SetLength(range.length());
  else
    base_fetcher_->UnsetLength();
  if (delegate_)
    delegate_->SeekToOffset(range.offset());
  base_fetcher_active_ = true;
  base_fetcher_->BeginTransfer(url_);
}

这里的 base_fetcher_ 对象又是什么呢?

base_fetcher 就是在 UpdateAttempterAndroid::BuildUpdateActions(url) 调用时根据 url 构造的 Fetcher:

*  如果是 file:/// 协议,则是 FileFetcher
*  如果是其他协议(如: http://), 则是 LibcurlHttpFetcher

4.2 FileFetcher 和 LibcurlHttpFetcher

前面分析到,offset 和 size 参数最终会传递给底层的 FileFetcher 或 LibcurlHttpFetcher 的 SetOffset() 和 SetLength() 函数。

这两个底层的 Fetcher 类又是如何处理的呢?

1. FileFetcher

// 文件: system/update_engine/common/file_fetcher.h
void SetOffset(off_t offset) override { offset_ = offset; }
void SetLength(size_t length) override { data_length_ = length; }
void UnsetLength() override { SetLength(0); }

FileFetcher 的 SetOffset(offset) 和 SetLength(length) 接口会设置内部的 offset_ 和 data_length_ 成员,这些成员在 BeginTransfer() 和 ScheduleRead() 中被使用:

// Begins the transfer, which must not have already been started.
void FileFetcher::BeginTransfer(const string& url) {
  ...

  // 根据传入的 url 打开相应的文件
  string file_path = url.substr(strlen("file://"));
  stream_ =
      brillo::FileStream::Open(base::FilePath(file_path),
                               brillo::Stream::AccessMode::READ,
                               brillo::FileStream::Disposition::OPEN_EXISTING,
                               nullptr);

  ...

  // 用 offset_ 设置文件读取开始的位置
  if (offset_)
    stream_->SetPosition(offset_, nullptr);
  
  ...
}

void FileFetcher::ScheduleRead() {
  ...
  // 根据 data_length_ 设置需要读取的数据长度
  if (data_length_ >= 0) {
    bytes_to_read = std::min(static_cast<uint64_t>(bytes_to_read),
                             data_length_ - bytes_copied_);
  }

  ...
}

这里:

1.  传输开始时,FileFetcher::BeginTransfer() 设置文件数据读取的起始位置 offset
2.  传输中,FileFetcher::ScheduleRead() 设置文件数据读取的长度 length

2. LibcurlHttpFetcher

// 文件: system/update_engine/libcurl_http_fetcher.h
void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }
void SetLength(size_t length) override { download_length_ = length; }
void UnsetLength() override { SetLength(0); }

LibcurlHttpFetcher 的 SetOffset(offset) 和 SetLength(length) 接口会设置内部的 bytes_downloaded_ 和 download_length_ 成员。这些成员在 ResumeTransfer() 和 LibcurlWrite() 中被使用。

// 文件: system/update_engine/libcurl_http_fetcher.cc
void LibcurlHttpFetcher::ResumeTransfer(const string& url) {
  ...

  // 根据 bytes_downloaded_ 和 download_length_ 设置 curl 操作读取数据的 range 范围
  if (bytes_downloaded_ > 0 || download_length_) {
    // Resume from where we left off.
    resume_offset_ = bytes_downloaded_;
    CHECK_GE(resume_offset_, 0);

    // Compute end offset, if one is specified. As per HTTP specification, this
    // is an inclusive boundary. Make sure it doesn't overflow.
    size_t end_offset = 0;
    if (download_length_) {
      end_offset = static_cast<size_t>(resume_offset_) + download_length_ - 1;
      CHECK_LE((size_t) resume_offset_, end_offset);
    }

    // Create a string representation of the desired range.
    string range_str = base::StringPrintf(
        "%" PRIu64 "-", static_cast<uint64_t>(resume_offset_));
    if (end_offset)
      range_str += std::to_string(end_offset);
    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_RANGE, range_str.c_str()),
             CURLE_OK);
  }

  ...
}

size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
  ...
  // curl 每次接收到数据后写入本地缓冲,调整 bytes_downloaded_,即下次获取数据的偏移
  bytes_downloaded_ += payload_size;
  ...
  return payload_size;

这里:

1.  传输中,LibcurlHttpFetcher::ResumeTransfer() 根据 bytes_downloaded_(offset) 和 download_length_(size) 设置 curl 获取远程数据的范围
2.  将接收数据写入缓冲区后,LibcurlHttpFetcher::LibcurlWrite() 调整 bytes_downloaded_(offset),设置 curl 随后获取数据的 offset

4.3 offset 和 size 总结

整个 offset 和 size 参数跟踪的路径比我想象的要长不少,前面分析得比较啰嗦,这里捡重点说一下。

命令行传入升级文件的地址和 offset 以及 size 参数后,代码根据升级文件地址是本地(“file:///”)还是远程,使用不同的方式读取文件。

本地文件则直接打开文件进行读取,远程则使用 curl 工具进行获取。

不论是读取本地文件还是获取远程文件,目标都是要获取有效的 payload 数据,因此有三个必须的参数,那就是:

1.  读哪个文件?由命令行的 payload 参数指定
2.  从哪里开始读取?由命令行的 offset 参数指定
3.  读取多少内容?由命令行的 size 参数指定

对于 payload.bin 文件,因为是原始的 payload 文件,所以 offset 为 0, size 为整个 payload.bin 文件的大小,如果没有提供 size, 默认就读取文件直到文件结束。

对于 update.zip 文件,里面包含了压缩的 payload.bin 文件,因为是压缩过的 payload,所以为了读取完整的 payload,必须提供 payload 在 zip 包的偏移位置,以及 payload 在 zip 包压缩后的数据大小。

再强调一次,对于 update.zip,需要提供 payload 在 zip 文件中的偏移(offset),以及压缩后的大小(size)。如果 offset 不正确,则解析不到正确的数据。如果 size 太小,则获取的数据不完整,size 太大没有问题,只不过会多获取一些没用的数据,浪费带宽。

至于 update.zip 包中,payload 文件的 offset 和 size 要如何计算,另外单独开一篇文章详细说明。

5 总结

Android 升级客户端例子 update_engine_client 支持本地文件和远程文件升级,同时也支持使用原始的 payload.bin 文件或压缩包 update.zip 文件。

1.  对于使用 payload.bin 文件升级,可以不用传递 offset 和 size 参数 (offset 默认为 0, size 参数从 headers 的 “FILE_SIZE” 中提取)。
2.  对于使用 update.zip 文件升级,必须需要传递 payload 数据在 update.zip 中的起始位置 (offset) 和大小 (size)。

如果没有传递 size 参数,代码会从 headers 的 “FILE_SIZE” 提取,因为 update.zip 中的 payload 一般是经过压缩的,所以 update.zip 包中 payload 数据一般会小于 “FILE_SIZE” 值。

对于使用本地文件,远程文件,原始的 payload.bin 和升级包 update.zip 进行升级,参考下面两个例子:

使用本地的 payload.bin 文件升级:

update_engine_client \
--payload=file:///data/ota_package/payload.bin \
--update \
--headers="\
FILE_HASH=R7FKJ+WstITiZpW2/nMIl6Duln+bwgLnTWy0W1d79cg=
FILE_SIZE=2607014024
METADATA_HASH=eW3P9td/sH6dV0es1gf5t0N1U3iE/CMzdXZpmi1gKUA=
METADATA_SIZE=180056
"

使用远程的 update.zip 包升级:

update_engine_client \
--payload=http://192.168.1.200/update.zip \
--update \
--offset=7985 \
--size=1096237091 \
--headers="\
FILE_HASH=fyDltdH3RkMxjJMLKWMU8SAkeWlnp+Dxb42jQpo30zc=
FILE_SIZE=1096237091
METADATA_HASH=72+DLYstrkKDp41oTV0xMCJtAIH5YAIs4Mw/4VSUXbY=
METADATA_SIZE=125561
"

对于使用 update.zip 包升级时的 offset 和 size 参数如何获取,请参考下一篇文章。

6. 其它

到目前为止,我写过 Android OTA 升级相关的话题包括:

基础入门:《Android A/B 系统》系列
核心模块:《Android Update Engine 分析》 系列
动态分区:《Android 动态分区》 系列
虚拟 A/B:《Android 虚拟 A/B 分区》系列
升级工具:《Android OTA 相关工具》系列
更多这些关于 Android OTA 升级相关文章的内容,请参考《Android OTA 升级系列专栏文章导读》。
————————————————
版权声明:本文为CSDN博主「洛奇看世界」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/guyongqiangx/article/details/122430246

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值