栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android Update Engine分析(十四) 生成 payload 数据

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android Update Engine分析(十四) 生成 payload 数据

相关文章:

Android A/B System 分析系列

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 Update Engine 分析系列

Android Update Engine 分析(一)MakefileAndroid Update Engine 分析(二)Protobuf和AIDL文件Android Update Engine 分析(三)客户端进程Android Update Engine 分析(四)服务端进程Android Update Engine 分析(五)服务端核心之Action机制Android Update Engine 分析(六)服务端核心之Action详解Android Update Engine 分析(七)DownloadAction之FileWriterAndroid Update Engine 分析(八)升级包制作脚本分析Android Update Engine 分析(九)delta_generator 工具的 6 种操作Android Update Engine 分析(十)生成 payload 和 metadata 的哈希Android Update Engine 分析(十一) 更新 payload 签名Android Update Engine 分析(十二) 验证 payload 签名Android Update Engine 分析(十三) 提取 payload 的 property 数据

在《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》中提到了,delta_generator 工具提供的 6 种操作,分别是:

    生成 payload 和 metadata 数据的哈希值更新 payload 和 metadata 数据的签名使用公钥验证 payload 和 metadata 数据的签名提取 payload 文件的 property 数据对 old image 打 delta 补丁生成 payload 数据

生成 payload 数据是 delta_generator 工具最主要的功能,当我准备重新更新这一些列文章时,一开始就想写这个的,不过因为太久没看,不记得 payload 细节了,更无从从细节上分析。所以就先写一些简单的操作,等熟悉了再开始写。经过前面篇的分析了解 payload 结构,现在是时候来解决这个问题了。

本篇主要从相对高一点的层次开始,自上而下分析 payload 数据的生成。不过对于具体到每个 block 数据的处理,这又太过于细节,会留在后续篇章处理。

如果在阅读代码时,觉得用于生成 payload 数据比较复杂,建议参考第 2.2 节我总结的 payload_config 结构图如果你代码的逐行注释不感兴趣,只想从宏观步骤上了解 payload 是如何生成的,请跳转到第 3 节,直接看总结

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

1. 从 ota_from_target_files脚本 到 delta_generator 工具 1.1 ota_from_target_files 制作升级包命令

命令行调用 ota_from_target_files 来生成 payload 数据:

全量升级包的制作

默认制作全量升级包

$ ./build/tools/releasetools/ota_from_target_files bcm7252ssffdr4-target_files-eng.ygu.zip update.zip

增量升级包的制作

当使用 -i 选项指定基线包时,此时生成增量升级包

$./build/tools/releasetools/ota_from_target_files 
    -i dist/old.zip 
    dist/new.zip 
    update.zip

本篇主要分析较为复杂的增量升级包的制作,有了对增量升级包的理解,了解全量升级包就更容易了。

1.2 brillo_update_payload 制作升级包

A/B 系统上,所有 zip 包的操作都交由 brillo_update_payload 脚本处理,比如生成 payload 数据:

# ota_from_target_files 内调用 brillo_update_payload
# 基于 new.zip 和 old.zip 生成 payload 文件
brillo_update_payload generate --payload /tmp/payload-unsigned.bin 
                               --target_image dist/new.zip 
                               --source_image dist/old.zip

在 brillo_update_payload 脚本中,进一步调用 delta_generator 工具来生成 payload:

# 将 new.zip 和 old.zip 中的 boot.img 和 system.img 解包出来, 用于生成 payload-unsigned.bin
# brillo_update_payload 内调用 delta_generator
delta_generator -out_file=dist/payload-unsigned.bin 
                -partition_names=boot:system 
                -new_partitions=/tmp/boot.img.new:/tmp/system.raw.new 
                -old_partitions=/tmp/boot.img.old:/tmp/system.raw.old 
                --minor_version=3 --major_version=2

因此,熟悉 delta_generator 参数的话,以直接在命令行操作。

2. delta_generator 生成 payload 源码分析 2.1 Main 函数

Main 函数中处理生成 payload.bin 的代码有点长,似乎还挺复杂,不过按照功能划分,只做了两件事:

    解析传入参数,设置 payload_config 结构体,用于后期指导如何生成 payload 文件;根据 payload_config 设置生成 payload 文件;
// 文件: system/update_engine/payload_generator/generate_delta_main.cc
int Main(int argc, char** argv) {
  ...
  
  

  // A payload generation was requested. Convert the flags to a
  // PayloadGenerationConfig.
  PayloadGenerationConfig payload_config;
  vector partition_names, old_partitions, new_partitions;

  
  // 拆分传递的参数 "-partition_names=boot:system" 到 partition_names 中
  partition_names =
      base::SplitString(FLAGS_partition_names, ":", base::TRIM_WHITESPACE,
                        base::SPLIT_WANT_ALL);
  CHECK(!partition_names.empty());
  // 检查 major_version 和 new_partitions 参数
  // 1. major_version 是否为 1,实际传入为 2
  // 2. new_partitions 是否为空
  if (FLAGS_major_version == kChromeOSMajorPayloadVersion ||
      FLAGS_new_partitions.empty()) {
    // 检查 partition_names 参数
    // 1. 如果 partition_names 包含的分区数不为 2
    // 2. 如果 partition_names 包含的分区不是 "root" 和 "system"
    // 则提示错误信息
    LOG_IF(FATAL, partition_names.size() != 2)
        << "To support more than 2 partitions, please use the "
        << "--new_partitions flag and major version 2.";
    LOG_IF(FATAL, partition_names[0] != kLegacyPartitionNameRoot ||
                  partition_names[1] != kLegacyPartitionNameKernel)
        << "To support non-default partition name, please use the "
        << "--new_partitions flag and major version 2.";
  }

  
  // 如果传递了 "-new_partitions" 参数
  if (!FLAGS_new_partitions.empty()) {
    LOG_IF(FATAL, !FLAGS_new_image.empty() || !FLAGS_new_kernel.empty())
        << "--new_image and --new_kernel are deprecated, please use "
        << "--new_partitions for all partitions.";
    // 拆分传递的参数 "-new_partitions=/tmp/boot.img.new:/tmp/system.raw.new" 到 new_partitions 中
    new_partitions =
        base::SplitString(FLAGS_new_partitions, ":", base::TRIM_WHITESPACE,
                          base::SPLIT_WANT_ALL);
    CHECK(partition_names.size() == new_partitions.size());

    // 如果有传递 "-old_partitions" 参数,则为差分升级,否则为整包升级
    payload_config.is_delta = !FLAGS_old_partitions.empty();
    LOG_IF(FATAL, !FLAGS_old_image.empty() || !FLAGS_old_kernel.empty())
        << "--old_image and --old_kernel are deprecated, please use "
        << "--old_partitions if you are using --new_partitions.";
  } else {
    // 如果没有传递 "-new_partitions" 参数, 则使用 "-new_image" 和 "-new_kernel" 参数
    new_partitions = {FLAGS_new_image, FLAGS_new_kernel};
    LOG(WARNING) << "--new_partitions is empty, using deprecated --new_image "
                 << "and --new_kernel flags.";

    // 没有传递 "-old_image" 和 "-old_kernel" 参数,则为整包升级
    payload_config.is_delta = !FLAGS_old_image.empty() ||
                              !FLAGS_old_kernel.empty();
    LOG_IF(FATAL, !FLAGS_old_partitions.empty())
        << "Please use --new_partitions if you are using --old_partitions.";
  }
  // 遍历需要操作的所有分区
  for (size_t i = 0; i < partition_names.size(); i++) {
    LOG_IF(FATAL, partition_names[i].empty())
        << "Partition name can't be empty, see --partition_names.";
    // 使用 new_partitions 参数设置 target.partitions
    //   target.partitions[0].name = boot
    //   target.partitions[0].path = /tmp/boot.img.new
    // 在 target.partitions vector 的最后用 partition_names[i] 初始化一个分区配置信息
    payload_config.target.partitions.emplace_back(partition_names[i]);
    // 设置最后添加 target 分区的 path 信息
    payload_config.target.partitions.back().path = new_partitions[i];
  }

  
  // 判断是否为增量升级
  if (payload_config.is_delta) {
    if (!FLAGS_old_partitions.empty()) {
      // 拆分传递的参数 "-old_partitions=/tmp/boot.img.old:/tmp/system.raw.old" 到 old_partitions 中
      old_partitions =
          base::SplitString(FLAGS_old_partitions, ":", base::TRIM_WHITESPACE,
                            base::SPLIT_WANT_ALL);
      // 确保 old_partitions 和 new_partitions 的数量一致
      CHECK(old_partitions.size() == new_partitions.size());
    } else {
      old_partitions = {FLAGS_old_image, FLAGS_old_kernel};
      LOG(WARNING) << "--old_partitions is empty, using deprecated --old_image "
                   << "and --old_kernel flags.";
    }
    // 使用 old_partitions 参数设置 source.partitions
    //   source.partitions[0].name = boot
    //   source.partitions[0].path = /tmp/boot.img.old
    for (size_t i = 0; i < partition_names.size(); i++) {
      // 在 source.partitions vector 的最后用 partition_names[i] 初始化一个分区配置信息
      payload_config.source.partitions.emplace_back(partition_names[i]);
      // 设置最后添加 source 分区的 path 信息
      payload_config.source.partitions.back().path = old_partitions[i];
    }
  }

  
  // 检查是否传递了 "-new_postinstall_config_file" 参数
  if (!FLAGS_new_postinstall_config_file.empty()) {
    LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion)
        << "Postinstall config is only allowed in major version 2 or newer.";
    brillo::KeyValueStore store;
    // 从 new_postinstall_config_file 文件加载 key=value 键值对
    CHECK(store.Load(base::FilePath(FLAGS_new_postinstall_config_file)));
    CHECK(payload_config.target.LoadPostInstallConfig(store));
  }

  
  // 使用 "-chunk_size" 参数和 kBlockSize 更新 payload_config 设置
  // Use the default soft_chunk_size defined in the config.
  payload_config.hard_chunk_size = FLAGS_chunk_size;
  payload_config.block_size = kBlockSize;

  // The partition size is never passed to the delta_generator, so we
  // need to detect those from the provided files.
  // 根据 source 分区镜像文件设置每一个分区对应的 payload_config.source.partitions[0/1].size 参数
  if (payload_config.is_delta) {
    CHECK(payload_config.source.LoadImageSize());
  }
  // 根据 target 分区镜像文件设置每一个分区对应的 payload_config.target.partitions[0/1].size 参数
  CHECK(payload_config.target.LoadImageSize());

  // 确保 payload 输出文件不能为空
  CHECK(!FLAGS_out_file.empty());

  
  // 根据以下可选参数:
  // - new_channel, 
  // - new_board, 
  // - new_version, 
  // - new_key, 
  // - new_build_channel, 
  // - new_build_version
  // 参数创建 target 对应的 ImageInfo protobuf 结构数据
  // Ignore failures. These are optional arguments.
  ParseImageInfo(FLAGS_new_channel,
                 FLAGS_new_board,
                 FLAGS_new_version,
                 FLAGS_new_key,
                 FLAGS_new_build_channel,
                 FLAGS_new_build_version,
                 &payload_config.target.image_info);

  // 根据以下可选参数:
  // - old_channel, 
  // - old_board, 
  // - old_version, 
  // - old_key, 
  // - old_build_channel, 
  // - old_build_version
  // 参数创建 source 对应的 ImageInfo protobuf 结构数据
  // Ignore failures. These are optional arguments.
  ParseImageInfo(FLAGS_old_channel,
                 FLAGS_old_board,
                 FLAGS_old_version,
                 FLAGS_old_key,
                 FLAGS_old_build_channel,
                 FLAGS_old_build_version,
                 &payload_config.source.image_info);

  // 根据 "-rootfs_partition_size" 参数设置 payload_config
  payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size;

  // 如果是增量升级,获取相应的 target 和 source 分区文件句柄
  if (payload_config.is_delta) {
    // Avoid opening the filesystem interface for full payloads.
    for (PartitionConfig& part : payload_config.target.partitions)
      CHECK(part.OpenFilesystem());
    for (PartitionConfig& part : payload_config.source.partitions)
      CHECK(part.OpenFilesystem());
  }

  
  // 根据 "-major_version" 参数设置 payload_config, Android N 上的 major_version=2
  payload_config.version.major = FLAGS_major_version;
  LOG(INFO) << "Using provided major_version=" << FLAGS_major_version;

  // 检查 "-minor_version" 参数
  // 1. 如果没有传递该参数,则从 update_engine.con 中提取
  // 2. 如果有传递,则根据 "-minor_version" 参数设置 payload_config, Android N 上的 minor_version=3
  if (FLAGS_minor_version == -1) {
    // Autodetect minor_version by looking at the update_engine.conf in the old
    // image.
    if (payload_config.is_delta) {
      payload_config.version.minor = kInPlaceMinorPayloadVersion;
      brillo::KeyValueStore store;
      uint32_t minor_version;
      for (const PartitionConfig& part : payload_config.source.partitions) {
        if (part.fs_interface && part.fs_interface->LoadSettings(&store) &&
            utils::GetMinorVersion(store, &minor_version)) {
          payload_config.version.minor = minor_version;
          break;
        }
      }
    } else {
      payload_config.version.minor = kFullPayloadMinorVersion;
    }
    LOG(INFO) << "Auto-detected minor_version=" << payload_config.version.minor;
  } else {
    payload_config.version.minor = FLAGS_minor_version;
    LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
  }

  // 检查 "-zlib_fingerprint" 参数
  // 如果 IsZlibCompatible(zlib_fingerprint) 检查返回 ture, 则设置 imgdiff_allowed = true
  // 目前还没有弄清楚 ""-zlib_fingerprint"" 参数的意义和用途
  if (!FLAGS_zlib_fingerprint.empty()) {
    if (utils::IsZlibCompatible(FLAGS_zlib_fingerprint)) {
      payload_config.version.imgdiff_allowed = true;
    } else {
      LOG(INFO) << "IMGDIFF operation disabled due to fingerprint mismatch.";
    }
  }

  // 输出生成的 payload 的类型:增量升级,全量升级?
  if (payload_config.is_delta) {
    LOG(INFO) << "Generating delta update";
  } else {
    LOG(INFO) << "Generating full update";
  }

  
  // From this point, all the options have been parsed.
  if (!payload_config.Validate()) {
    LOG(ERROR) << "Invalid options passed. See errors above.";
    return 1;
  }

  
  // 根据 payload_config 的内容,生成 payload 文件
  uint64_t metadata_size;
  if (!GenerateUpdatePayloadFile(payload_config,
                                 FLAGS_out_file,
                                 FLAGS_private_key,
                                 &metadata_size)) {
    return 1;
  }
  
  // 如果有 "-out_metadata_size_file" 参数,则将 metadata size 输出到这个文件中 
  if (!FLAGS_out_metadata_size_file.empty()) {
    string metadata_size_string = std::to_string(metadata_size);
    CHECK(utils::WriteFile(FLAGS_out_metadata_size_file.c_str(),
                           metadata_size_string.data(),
                           metadata_size_string.size()));
  }
  return 0;
}

提取下上面注释中的重点,如下:

    解析传入参数,设置 payload_config 结构体

1.1 解析 “partition_names” 参数1.2 解析 “new_partitions” 参数,设置 payload_config.target.partitions1.3 解析 “old_partitions” 参数,设置 payload_config.source.partitions1.4 解析 “new_postinstall_config_file” 参数1.5 设置 payload_config 的 hard_chunk_size, block_size1.5 设置 payload_config.target.image_info 和 payload_config.source.image_info1.6 设置 payload_config.version1.7 检查验证 payload_config 内部的各项参数

    根据 payload_config 设置, 调用 GenerateUpdatePayloadFile 生成 payload 文件
2.2 payload_config 结构

不过这里最大的问题就是 payload_config 对象的结构多层嵌套,非常复杂。
不过,再复杂的结构,也扛不住画图,然后对照着图来阅读代码。

我这里用一张图总结了 PayloadGenerationConfig 结构的内容:


其实在设置 payload_config 的属性过程中,我们只关心都有哪些成员变量,因此移除成员函数后的结构如下所示:

文字是一种线性结构,只有你理解了前面的文字,才能继续理解后续的内容。
但图则不同,图会将一个完整的结构呈现给你,有一种全局观,也可以只选择阅读关心的部分。

当代码或数据结构过于复杂时,建议换一种方式,使用图来展示,加深理解。

2.3 GenerateUpdatePayloadFile 函数

从最上层看,GenerateUpdatePayloadFile 函数才是真正干活的那个。

这个函数根据传入的 payload_config 结构的内容,遍历每一个需要操作的分区,选择一种合适的操作策略,生成该分区的 AnnotatedOperation, 按分区组织 AnnotatedOperation, 并将其输出到 payload 文件中。

// 文件: system/update_engine/payload_generator/delta_diff_generator.cc
bool GenerateUpdatePayloadFile(
    const PayloadGenerationConfig& config,
    const string& output_path,
    const string& private_key_path,
    uint64_t* metadata_size) {
  // 检查 major 和 minor version
  if (!config.version.Validate()) {
    LOG(ERROR) << "Unsupported major.minor version: " << config.version.major
               << "." << config.version.minor;
    return false;
  }

  
  // Create empty payload file object.
  PayloadFile payload;
  TEST_AND_RETURN_FALSE(payload.Init(config));

  
  const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
  string temp_file_path;
  int data_file_fd;
  TEST_AND_RETURN_FALSE(
      utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
  // 执行 unlink 文件删除那件
  // 实际上, 文件打开的情况下unlink()并不会立即删除,并且对文件依然可以进行读写操作,在进程结束之后文件会被删除
  ScopedPathUnlinker temp_file_unlinker(temp_file_path);
  TEST_AND_RETURN_FALSE(data_file_fd >= 0);

  {
    off_t data_file_size = 0;
    // 用 data_file_fd 初始化 data_file_fd_closer, 在进程结束时关闭文件
    ScopedFdCloser data_file_fd_closer(&data_file_fd);
    // 使用文件描述符和 file size 初始化一个 FileWrite
    BlobFileWriter blob_file(data_file_fd, &data_file_size);
    // 再次检查增量升级时两个包是否一样大
    if (config.is_delta) {
      TEST_AND_RETURN_FALSE(config.source.partitions.size() ==
                            config.target.partitions.size());
    }
    PartitionConfig empty_part("");
    
    for (size_t i = 0; i < config.target.partitions.size(); i++) {
      // 全量升级时, old_part 为空
      const PartitionConfig& old_part =
          config.is_delta ? config.source.partitions[i] : empty_part;
      // 打印分区信息,例如:
      // Partition name: boot
      // Partition size: 19456000
      // Block count: 4750
      const PartitionConfig& new_part = config.target.partitions[i];
      LOG(INFO) << "Partition name: " << new_part.name;
      LOG(INFO) << "Partition size: " << new_part.size;
      LOG(INFO) << "Block count: " << new_part.size / config.block_size;

      
      // Select payload generation strategy based on the config.
      unique_ptr strategy;
      // We don't efficiently support deltas on squashfs. For now, we will
      // produce full operations in that case.
      if (!old_part.path.empty() &&
          !utils::IsSquashfsFilesystem(new_part.path)) {
        // Delta update.
        if (config.version.minor == kInPlaceMinorPayloadVersion) {
          LOG(INFO) << "Using generator InplaceGenerator().";
          strategy.reset(new InplaceGenerator());
        } else {
          LOG(INFO) << "Using generator ABGenerator().";
          strategy.reset(new ABGenerator());
        }
      } else {
        LOG(INFO) << "Using generator FullUpdateGenerator().";
        strategy.reset(new FullUpdateGenerator());
      }

      
      vector aops;
      // Generate the operations using the strategy we selected above.
      TEST_AND_RETURN_FALSE(strategy->GenerateOperations(config,
                                                         old_part,
                                                         new_part,
                                                         &blob_file,
                                                         &aops));

      
      // Filter the no-operations. OperationsGenerators should not output this
      // kind of operations normally, but this is an extra step to fix that if
      // happened.
      diff_utils::FilterNoopOperations(&aops);

      
      TEST_AND_RETURN_FALSE(payload.AddPartition(old_part, new_part, aops));
    }
  }

  
  LOG(INFO) << "Writing payload file...";
  // Write payload file to disk.
  TEST_AND_RETURN_FALSE(payload.WritePayload(output_path, temp_file_path,
                                             private_key_path, metadata_size));

  LOG(INFO) << "All done. Successfully created delta file with "
            << "metadata size = " << *metadata_size;
  return true;
}

提取下注释中的重点,如下:

    调用 payload.Init 函数设置 manifest_ 结构的 version 以及 old_image_info 和 new_image_info生成名为 “CrAU_temp_data.XXXXXX” 的临时文件遍历所有 target 分区进行操作

    3.1 选择 payload 生成使用的的策略 generator3.2 使用 generator 的 GenerateOperations 操作分区,生成分区对应的 AnnotatedOperation3.3 过滤空操作, 例如增量升级时,同样的数据就会生成 Noop Operation3.4 将所有 AnnotatedOperation 更新到对应的 partition 数据中 调用 payload.WritePayload 函数将临时文件 “CrAU_temp_data.XXXXXX” 的数据输出到 output_path 文件中

下面是 GenerateUpdatePayloadFile 中调用的一些函数注释

PayloadFile::Init 函数

这里使用一个 PayloadGenerationConfig 结构的对象 config,去设置 manifest_ 结构的 version 以及 old_image_info 和 new_image_info 成员。

// 文件: system/update_engine/payload_generator/payload_file.cc
bool PayloadFile::Init(const PayloadGenerationConfig& config) {
  // 检查 config.version 的 major 和 minor 版本信息 
  TEST_AND_RETURN_FALSE(config.version.Validate());
  // 设置 payload 的 major_version_ 版本
  major_version_ = config.version.major;
  // 设置 manifest_ 的 minor 版本
  manifest_.set_minor_version(config.version.minor);

  // 用 config.source.image_info 更新 manifest_ 的 old_image_info()
  if (!config.source.ImageInfoIsEmpty())
    *(manifest_.mutable_old_image_info()) = config.source.image_info;

  // 用 config.target.image_info 更新 manifest_ 的 new_image_info()
  if (!config.target.ImageInfoIsEmpty())
    *(manifest_.mutable_new_image_info()) = config.target.image_info;

  // 设置 manifest_ 的 block_size
  manifest_.set_block_size(config.block_size);
  return true;
}

diff_utils::FilterNoopOperations 函数

FilterNoopOperations 函数遍历所有操作,如果满足条件 IsNoopOperation, 则移除该操作。

void FilterNoopOperations(vector* ops) {
  ops->erase(
      std::remove_if(
          ops->begin(), ops->end(),
          [](const AnnotatedOperation& aop){return IsNoopOperation(aop.op);}),
      ops->end());
}

如果判断是 Noop Operation 的呢?
这里的条件是如果 op 的 type 为 MOVE, 并且 src_extends 和 dst_extends 一样。
至于 ExpandExtents 到底是什么,不是这里关心的重点,不再往下追溯。

// Returns true if |op| is a no-op operation that doesn't do any useful work
// (e.g., a move operation that copies blocks onto themselves).
bool IsNoopOperation(const InstallOperation& op) {
  return (op.type() == InstallOperation::MOVE &&
          ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents()));
}

PayloadFile::AddPartition 函数

将所有的 AnnotatedOperation 操作和分区绑定在一起,并更新该分区新(new)旧(old)状态的 size 和 hash,这个 size 和 hash 在升级时会检查对比。

bool PayloadFile::AddPartition(const PartitionConfig& old_conf,
                               const PartitionConfig& new_conf,
                               const vector& aops) {
  // 检查是否是 ChomeOS 更新,这里因为是 Android, 略过。
  // Check partitions order for Chrome OS
  if (major_version_ == kChromeOSMajorPayloadVersion) {
    const vector part_order = { kLegacyPartitionNameRoot,
                                             kLegacyPartitionNameKernel };
    TEST_AND_RETURN_FALSE(part_vec_.size() < part_order.size());
    TEST_AND_RETURN_FALSE(new_conf.name == part_order[part_vec_.size()]);
  }
  // 更新 part 的 name, 并添加所有的 aops 数据
  Partition part;
  part.name = new_conf.name;
  part.aops = aops;
  part.postinstall = new_conf.postinstall;
  // 更新 old_part 和 new_part 的 size 和 hash 信息
  // Initialize the PartitionInfo objects if present.
  if (!old_conf.path.empty())
    TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(old_conf,
                                                              &part.old_info));
  TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(new_conf,
                                                            &part.new_info));
  part_vec_.push_back(std::move(part));
  return true;
}

在升级时,如果现有分区的 size 和 hash,跟 payload 数据中的 old_partition_info.hash 比较。
在代码中,下载 payload 数据以后,解析 manifest 时会调用 VerifySourcePartitions 执行这个操作。

当然,升级完成后,也有可以将更新后新的分区 hash 同 payload 数据中的 new_partition_info.hash 比较。
不过代码中似乎并没有这个逻辑,如果你看到了,麻烦说一下,谢谢!

InitializePartitionInfo 函数

InitializePartitionInfo 函数的实际操作就是更新分区的 size 和 hash 信息。

bool InitializePartitionInfo(const PartitionConfig& part, PartitionInfo* info) {
  // 设置 partition size
  info->set_size(part.size);
  HashCalculator hasher;
  // 计算 partition 文件的哈希,例如用于计算 old_part 文件为 /tmp/boot.img.g93Yhs,则设置 
  // part_info->size=19456000
  // part_info->hash=tVjyP4PyAa1TCYisd9NIGr0fdm4VtIrVz+w/Vrjvx5A=
  TEST_AND_RETURN_FALSE(hasher.UpdateFile(part.path, part.size) ==
                        static_cast(part.size));
  TEST_AND_RETURN_FALSE(hasher.Finalize());
  const brillo::Blob& hash = hasher.raw_hash();
  info->set_hash(hash.data(), hash.size());
  // 打印 size 和 hash 信息, 例如: /tmp/boot.img.g93Yhs: size=19456000 hash=tVjyP4PyAa1TCYisd9NIGr0fdm4VtIrVz+w/Vrjvx5A=
  LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
  return true;
}
2.4 PayloadFile::WritePayload 函数

在 GenerateUpdatePayloadFile 函数中,除了遍历分区去生成各个分区对应的 AnnotatedOperation 外,最最重要的就是就是最后一步,如何将这些生成的 AnnotatedOperation 写入到 payload 文件中。

函数 PayloadFile::WritePayload 就是做这个事情,这个函数的代码较长,这里单独分析:

// 文件: system/update_engine/payload_generator/payload_file.cc
bool PayloadFile::WritePayload(const string& payload_file,
                               const string& data_blobs_path,
                               const string& private_key_path,
                               uint64_t* metadata_size_out) {
  
  // Reorder the data blobs with the manifest_.
  string ordered_blobs_path;
  TEST_AND_RETURN_FALSE(utils::MakeTempFile(
      "CrAU_temp_data.ordered.XXXXXX",
      &ordered_blobs_path,
      nullptr));
  // 确保进程退出是删除临时文件
  ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path);
  
  // 对之前生成的 data_blobs_path 文件中的 operation 顺序输出到新文件中
  // 同时更新其对应的 data_offset, data_length 和 data_sha256_hash
  TEST_AND_RETURN_FALSE(ReorderDataBlobs(data_blobs_path, ordered_blobs_path));

  // 再次检查所有 install operation 数据是顺序存放的
  // 即 op.data_offset 是依次递增的
  // Check that install op blobs are in order.
  uint64_t next_blob_offset = 0;
  for (const auto& part : part_vec_) {
    for (const auto& aop : part.aops) {
      if (!aop.op.has_data_offset())
        continue;
      if (aop.op.data_offset() != next_blob_offset) {
        LOG(FATAL) << "bad blob offset! " << aop.op.data_offset() << " != "
                   << next_blob_offset;
      }
      next_blob_offset += aop.op.data_length();
    }
  }

  // 清空 manifest_ 中原有的 install_operations, kernel_install_operations 和 partitions 向量数组
  // Copy the operations and partition info from the part_vec_ to the manifest.
  manifest_.clear_install_operations();
  manifest_.clear_kernel_install_operations();
  manifest_.clear_partitions();
  
  // 遍历所有分区,将分区的 install operations 更新到 manifest_ 结构中
  for (const auto& part : part_vec_) {
    // 对 major_version 为 2(kBrilloMajorPayloadVersion), 即 Android 的情况
    if (major_version_ == kBrilloMajorPayloadVersion) {
      
      // manifest_ 结构的 partitions 数组中新增一个名为 part.name 的项 
      PartitionUpdate* partition = manifest_.add_partitions();
      partition->set_partition_name(part.name);
      // 使用 part 数据更新 manifest_ 结构的 partition 数据:
      // 更新 part.postinstall.run             --> partition.run_postinstall
      // 更新 part.postinstall.path            --> partition.postinstall_path
      // 更新 part.postinstall.filesystem_type --> partition.filesystem_type
      // 更新 part.postinstall.optional        --> partition.postinstall_optional
      if (part.postinstall.run) {
        partition->set_run_postinstall(true);
        if (!part.postinstall.path.empty())
          partition->set_postinstall_path(part.postinstall.path);
        if (!part.postinstall.filesystem_type.empty())
          partition->set_filesystem_type(part.postinstall.filesystem_type);
        partition->set_postinstall_optional(part.postinstall.optional);
      }
      
      for (const AnnotatedOperation& aop : part.aops) {
        *partition->add_operations() = aop.op;
      }
      
      if (part.old_info.has_size() || part.old_info.has_hash())
        *(partition->mutable_old_partition_info()) = part.old_info;
      if (part.new_info.has_size() || part.new_info.has_hash())
        *(partition->mutable_new_partition_info()) = part.new_info;
    } else { // 对于 major_version_ 为 1, 即 ChomeOS 升级的情况,这里暂时不管
      // major_version_ == kChromeOSMajorPayloadVersion
      if (part.name == kLegacyPartitionNameKernel) {
        for (const AnnotatedOperation& aop : part.aops)
          *manifest_.add_kernel_install_operations() = aop.op;
        if (part.old_info.has_size() || part.old_info.has_hash())
          *manifest_.mutable_old_kernel_info() = part.old_info;
        if (part.new_info.has_size() || part.new_info.has_hash())
          *manifest_.mutable_new_kernel_info() = part.new_info;
      } else {
        for (const AnnotatedOperation& aop : part.aops)
          *manifest_.add_install_operations() = aop.op;
        if (part.old_info.has_size() || part.old_info.has_hash())
          *manifest_.mutable_old_rootfs_info() = part.old_info;
        if (part.new_info.has_size() || part.new_info.has_hash())
          *manifest_.mutable_new_rootfs_info() = part.new_info;
      }
    }
  }

  
  // Signatures appear at the end of the blobs. Note the offset in the
  // manifest_.
  // 如果有传递 private_key,则:
  // 1. 使用它对一个字符的 'x' 数据签名,目的是获取签名的长度
  // 2. 使用签名长度更新 manifest_ 中的 signatures_offset 和 signatures_size 数据
  uint64_t signature_blob_length = 0;
  if (!private_key_path.empty()) {
    TEST_AND_RETURN_FALSE(
        PayloadSigner::SignatureBlobLength(vector(1, private_key_path),
                                           &signature_blob_length));
    PayloadSigner::AddSignatureToManifest(
        next_blob_offset, signature_blob_length,
        major_version_ == kChromeOSMajorPayloadVersion, &manifest_);
  }

  
  // 由于 manifest_ 是 protobuf 结构,输出时需要序列化,
  // 这里将其序列化到字符串 serialized_manifest 中,方便后面计算大小和输出
  // Serialize protobuf
  string serialized_manifest;
  TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest));

  
  // 计算 metadata 的大小
  // metadata 包括: 4(magic)+8(file_format_version)+8(manifest_size)
  // 另外还有 4 bytes(metadata_signature_size) 待定
  uint64_t metadata_size =
      sizeof(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size();

  // 打开 payload 文件写入头部数据
  LOG(INFO) << "Writing final delta file header...";
  DirectFileWriter writer;
  TEST_AND_RETURN_FALSE_ERRNO(writer.Open(payload_file.c_str(),
                                          O_WRonLY | O_CREAT | O_TRUNC,
                                          0644) == 0);
  ScopedFileWriterCloser writer_closer(&writer);

  
  
  // Write header
  TEST_AND_RETURN_FALSE(writer.Write(kDeltaMagic, sizeof(kDeltaMagic)));

  
  // Write major version number
  TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer, major_version_));

  
  // Write protobuf length
  TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer,
                                               serialized_manifest.size()));

  
  // 对 major_version = 2(kBrilloMajorPayloadVersion),即 Android 升级的情况,
  // 写入 metadata_signature_size
  // Write metadata signature size.
  uint32_t metadata_signature_size = 0;
  if (major_version_ == kBrilloMajorPayloadVersion) {
    // metadata signature has the same size as payload signature, because they
    // are both the same kind of signature for the same kind of hash.
    // 将 metadata_signature_size 存储大端格式的 signature size
    uint32_t metadata_signature_size = htobe32(signature_blob_length);
    // 写入 metadata_signature_size
    TEST_AND_RETURN_FALSE(writer.Write(&metadata_signature_size,
                                       sizeof(metadata_signature_size)));
    // 原来的 metadata_size 没有包含 metadata_signature_size 成员的 4 bytes 数据
    metadata_size += sizeof(metadata_signature_size);
    // 将大端格式的 metadata_signature_size 转换成原来的数据(原来可能是大端也可能是小端,取悦于机器的运行状态)
    // Set correct size instead of big endian size.
    metadata_signature_size = signature_blob_length;
  }

  
  // Write protobuf
  LOG(INFO) << "Writing final delta file protobuf... "
            << serialized_manifest.size();
  TEST_AND_RETURN_FALSE(writer.Write(serialized_manifest.data(),
                                     serialized_manifest.size()));

  
  // 具体操作:
  // 如果是 Android 升级,并且传递了 private_key, 则:
  // 1. 使用 private_key 对 payload 文件的 metadata 数据签名
  // 2. 将 metadata 的签名写入到 payload 文件中
  // 如果这里没有传入 private_key,则后续处理时需要在 payload 文件中插入 metdata_signature 数据
  // 这里最好的方式是,如果传入 private_key,则计算签名,否则预留一个签名的占位符
  // 问题是不同的签名参数,其签名长度不知道,因此不知道应该预留多大的空位给签名数据
  // Write metadata signature blob.
  if (major_version_ == kBrilloMajorPayloadVersion &&
      !private_key_path.empty()) {
    brillo::Blob metadata_hash, metadata_signature;
    TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile(payload_file,
                                                             metadata_size,
                                                             &metadata_hash));
    TEST_AND_RETURN_FALSE(
        PayloadSigner::SignHashWithKeys(metadata_hash,
                                        vector(1, private_key_path),
                                        &metadata_signature));
    TEST_AND_RETURN_FALSE(writer.Write(metadata_signature.data(),
                                       metadata_signature.size()));
  }

  
  // Append the data blobs
  LOG(INFO) << "Writing final delta file data blobs...";
  int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
  ScopedFdCloser blobs_fd_closer(&blobs_fd);
  TEST_AND_RETURN_FALSE(blobs_fd >= 0);
  for (;;) {
    vector buf(1024 * 1024);
    ssize_t rc = read(blobs_fd, buf.data(), buf.size());
    if (0 == rc) {
      // EOF
      break;
    }
    TEST_AND_RETURN_FALSE_ERRNO(rc > 0);
    TEST_AND_RETURN_FALSE(writer.Write(buf.data(), rc));
  }

  
  // 如果传递了 private_key, 则:
  // 1. 使用 private_key 对 payload 文件的 payload 数据签名
  // 2. 将 payload 数据写入到 payload 文件中
  // Write payload signature blob.
  if (!private_key_path.empty()) {
    LOG(INFO) << "Signing the update...";
    brillo::Blob signature_blob;
    // 计算 payload 数据的签名
    TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
        payload_file,
        vector(1, private_key_path),
        metadata_size,
        metadata_signature_size,
        metadata_size + metadata_signature_size + manifest_.signatures_offset(),
        &signature_blob));
    // 将 payload 数据的签名写入到 payload 文件中
    // 但是在 payload 数据签名的前面,还有个 payload_signature_message_size, 这里没看到更新,是 bug 吗?
    TEST_AND_RETURN_FALSE(writer.Write(signature_blob.data(),
                                       signature_blob.size()));
  }

  
  
  ReportPayloadUsage(metadata_size);

  
  *metadata_size_out = metadata_size;
  return true;
}

提取这段代码的注释重点, 看看都做了什么操作:

    创建临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX, 用于按顺序存放 AnnotatedOperation更新每一个操作对应数据的 data_offset, data_length 和 data_sha256_hash,并确保 data_offset 顺序存放遍历分区,更新 manifest_ 结构

    3.1 更新分区的 post install 相关内容3.2 遍历分区的所有操作, 逐个添加到 partition.operations 向量数组中3.3 更新分区的 old_partition_info 和 new_partition_info 如果有传递 private_key, 计算 signature 数据的大小,并更新 signature_offset 和 signature_size 信息串行化 manifest 结构成 protobuf 格式数据更新 metdata_size更新 payload 的 metadata 数据

    7.1 写入 4 字节 magic(“CrAU”)7.2 写入 写入 8 字节 major_version7.3 写入 8 字节 manifest_size7.4 计算 metadata_signature_size,并写入到文件中7.5 写入按 protbuf 格式序列化以后的 manifest 数据 如果有传递 private_key, 计算 metadata 的签名,并写入到 payload 文件中遍历所有的 AnnotatedOperation, 每次读取其 1M 数据添加到 payload 文件中如果有传递 private_key, 计算 payload 的签名,并写入到 payload 文件中遍历所有 AnnotatedOperation, 打印每个操作的信息返回 metadata_size

PayloadFile::ReorderDataBlobs 函数

bool PayloadFile::ReorderDataBlobs(
    const string& data_blobs_path,
    const string& new_data_blobs_path) {
  // 打开旧文件: /tmp/CrAU_temp_data.XXXXXX
  int in_fd = open(data_blobs_path.c_str(), O_RDONLY, 0);
  TEST_AND_RETURN_FALSE_ERRNO(in_fd >= 0);
  ScopedFdCloser in_fd_closer(&in_fd);

  // 打开新文件: /tmp/CrAU_temp_data.ordered.XXXXXX
  DirectFileWriter writer;
  TEST_AND_RETURN_FALSE(
      writer.Open(new_data_blobs_path.c_str(),
                  O_WRonLY | O_TRUNC | O_CREAT,
                  0644) == 0);
  ScopedFileWriterCloser writer_closer(&writer);
  uint64_t out_file_size = 0;

  // 外层循环遍历所有的分区
  for (auto& part : part_vec_) {
    // 内层循环遍历分区内的所有操作
    for (AnnotatedOperation& aop : part.aops) {
      // 如果 operation 操作有 data_offset 成员,则丢弃该数据
      if (!aop.op.has_data_offset())
        continue;
      // 确保 operation 操作有 data_length() 成员
      CHECK(aop.op.has_data_length());
      // 用 operation 操作中数据的长度初始化一个 buf 缓冲
      brillo::Blob buf(aop.op.data_length());
      // pread 函数带偏移量地原子的从文件中读取数据
      // 函数原型: ssize_t pread(intfd, void *buf, size_t count, off_t offset);
      // 这里从旧文件中 op.data_offset 指定的位置读取长度为 op.data_length 的数据到 buf 缓冲中
      ssize_t rc = pread(in_fd, buf.data(), buf.size(), aop.op.data_offset());
      TEST_AND_RETURN_FALSE(rc == static_cast(buf.size()));

      // 计算 operation 操作中数据的 sha256 哈希到 op.data_sha256_hash 中
      // Add the hash of the data blobs for this operation
      TEST_AND_RETURN_FALSE(AddOperationHash(&aop.op, buf));

      // 将 out_file_size 更新为 op.data_offset 值
      aop.op.set_data_offset(out_file_size);
      // 将 buf 中的数据写入到新文件中,数据的 offset 和 size 记录在 op.data_offset 和 op.data_length 中
      TEST_AND_RETURN_FALSE(writer.Write(buf.data(), buf.size()));
      // 更新下一个 operation 的 offset
      out_file_size += buf.size();
    }
  }
  return true;
}
2.5 AnnotatedOperation 结构

看了前面的代码,你有可能会好奇,所有分区最基本的操作都是 InstallOperation, 为什么我们看到的都是 AnnotatedOperation。

从 AnnotatedOperation 的定义可见,这个结构实际上是对 InstallOperation 的封装:

// 文件: system/update_engine/payload_generator/annotated_operation.h
struct AnnotatedOperation {
  // The name given to the operation, for logging and debugging purposes only.
  // This normally includes the path to the file and the chunk used, if any.
  std::string name;

  // The InstallOperation, as defined by the protobuf.
  InstallOperation op;

  // Writes |blob| to the end of |blob_file|. It sets the data_offset and
  // data_length in AnnotatedOperation to match the offset and size of |blob|
  // in |blob_file|.
  bool SetOperationBlob(const brillo::Blob& blob, BlobFileWriter* blob_file);
};

基本上各个地方都操作的都是 AnnotatedOperation 内部封装的 InstallOperation, 在生成 payload.bin 文件的最后,调用 ReportPayloadUsage 函数打印相关信息。

3. payload 生成总结

本文从比较高的层次上分析了生成 payload 文件的源码,这里总结如下:

3.1 命令行层面

命令行使用 ota_from_target_files 工具通过一下命令生成 update.zip 包:

$./build/tools/releasetools/ota_from_target_files 
    -i dist/old.zip 
    dist/new.zip 
    update.zip

在脚本中会解包 old.zip 和 new.zip,这两个包是通过 make otapackage 命令生成的。
解包后会得到 boot.img 和 system.img 的临时文件,然后 delta_generator 操作这些临时文件生成 payload.bin 文件。

delta_generator -out_file=dist/payload-unsigned.bin 
                -partition_names=boot:system 
                -new_partitions=/tmp/boot.img.new:/tmp/system.raw.new 
                -old_partitions=/tmp/boot.img.old:/tmp/system.raw.old 
                --minor_version=3 --major_version=2
3.2 delta_generator 源码

在 delta_generator 的 Main 函数中,主要做了两件事:

    解析参数,生成 payload_config 结构;基于 payload_config 结构中的内容,调用 GenerateUpdatePayloadFile 去实现功能;

payload_config 结构比较复杂,可以配合下图阅读代码:

在 GenerateUpdatePayloadFile 函数中,根据 partition_names 数组,遍历要操作的分区。

在遍历分区一开始,就根据分区和 minor 版本号,选择不同的生成策略来产生数据。

如果 minor 版本号是 kInPlaceMinorPayloadVersion, 则选用 InplaceGenerator 策略;如果同时提供了新旧两个分区,则选用 ABGenerator 产生差分增量数据;如果只提供了新分区,但没有旧分区,则选用 FullUpdateGenerator 产生全量数据;

然后调用所用 Generator 的 GenerateOperations 操作,检查分区内部的每一个 block,然后产生一大堆 AnnotatedOperation 数据。AnnotatedOperation 实际上是对 InstallOperation 的简单封装,因此也就是对每一个 block 产生了 InstallOperation 数据,存放在临时文件/tmp/CrAU_temp_data.XXXXXX 中。

然后遍历所有 AnnotatedOperation, 筛选出没有使用的空操作,将剩余的 AnnotatedOperation 数据指针存放在 Partition 数组中。换句话说,只需要找到分区的 aops 数组,并对其遍历就能还原每一个操作。

对 partition_names 数组中的每一个分区都执行遍历生成 AnnotatedOperation 数据。

遍历完成后,调用 payload.WritePayload 函数将生成的数据输出到指定 payload 文件中。

WritePayload 具体操作上大致有以下这些步骤:

    提取临时文件/tmp/CrAU_temp_data.XXXXXX 中的 AnnotatedOperation 数据,根据操作位置的 data_offset 按从小到大顺序存放到临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX 中;记录每一个 AnnotatedOperation 的信息,包括 data_offset, data_length 和 data_sha256_hash 存放到分区的 install operation 数组中;

因此,对每一个 operation, 其信息(offset, length 和 hash) 是存放在分区数组中的,但实际操作的数据其实是按起始位置从小到大存放在临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX 中。

    更新 manifest 数据, 并将其按照 protobuf 格式序列化;开始往输出文件中写入数据:
    4.1 写入 payload 头部的 header 数据(包括 magic, major_version, manifest_size, metadata_signature_size等)
    4.2 写入序列化的 manifest 数据(保罗payload signature offset 和 size, 新旧分区的 image_info,以及按分区组织的 AnnotatedOperation 信息等);
    4.3 如果有提供签名用的私钥,则计算前面这部分写入数据的签名,并写入到 payload 文件中;顺序写入从 /tmp/CrAU_temp_data.ordered.XXXXXX 文件中提取的 AnnotatedOperation 数据;如果有提供签名用的私钥,则计算 payload 文件的签名(计算范围不包含 metadata 和 payload 的签名自身),然后写入到 payload 文件末尾;打印输出所有的 AnnotatedOperation 数据信息;

通过以上操作,就将所有数据组织输出到了 payload.bin 文件中,后续步骤会进一步对 payload.bin 进行操作,包括生成 payload 和 metadata 的哈希并签名, 提取 payload 和 metadata 的 property 数据等。

3.2 delta_generator 生成 payload 的调用关系

下面是 delta_generator 生成 payload 的调用关系:

ota_from_target_files -i dist/old.zip dist/new.zip update.zip
   --> delta_generator -out_file=payload.bin -partition_names=boot:system -new_partitions=/tmp/boot.img.new:/tmp/system.raw.new -old_partitions=/tmp/boot.img.old:/tmp/system.raw.old --minor_version=3 --major_version=2
     --> GenerateUpdatePayloadFile(config)
        --> payload.Init(config)
        --> 遍历分区进行操作
           --> InplaceGenerator/ABGenerator/FullUpdateGenerator.GenerateOperations(config, old_part, new_part, ...)
           --> FilterNoopOperations
           --> payload.AddPartition
              --> InitializePartitionInfo(old_part)
                 --> HashCalculator(old_part), 计算 old_part 的哈希
              --> InitializePartitionInfo(new_part)
                 --> HashCalculator(new_part), 计算 new_part 的哈希
        --> payload.WritePayload
           --> ReorderDataBlobs
           --> AddSignatureToManifest
           --> manifest_.AppendToString(&serialized_manifest)
           --> Write(kDeltaMagic)
           --> Write(major_version_)
           --> Write(serialized_manifest.size)
           --> Write(metadata_signature_size)
           --> Write(serialized_manifest.data)
           --> HashCalculator::RawHashOfFile(metadata, &metadata_hash)
           --> PayloadSigner::SignHashWithKeys(metadata_hash, private_key_path, &metadata_signature)
           --> Write(metadata_signature.data)
           --> Write(major_version_)
           --> Write(serialized_manifest.size)
           --> Write(metadata_signature_size)
           --> Write(serialized_manifest.data)
           --> Write(operation.data)
           --> PayloadSigner::SignPayload(payload, &signature_blob)
           --> Write(signature_blob.data)
           --> ReportPayloadUsage(metadata_size)

上面这里的调用关系部分地方不是原始的函数名和参数的,大致表示下调用的逻辑和意义。

3.3 下一篇的内容

本篇分析了:

    生成 payload 参数,简述根据不同的情况选用不同策略的 Generator 对每一个分区的每一个 block 生成相应的 Install Operation,然后筛选重组这些 Install Operation 的数据, 将 Install Operation 的信息按分区存放在 manifest 中,最后输出 manifest 和 install operation 数据。

但第 2 步中,对于不同策略的 Generator 对每一个分区的每一个 block 到底是如何操作的并没有进一步分析。
下一篇将详细分析 3 种不同策略的 Generator 操作。

4. 其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。一个Android OTA的讨论组,请说明加Android OTA群。一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/723794.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号