精华内容
下载资源
问答
  • 2018-06-26 20:20:14

    Android Update Engine分析(三)客户端进程

    技术文章直入主题,展示结论,容易让人知其然,不知其所以然。
    我个人更喜欢在文章中展示如何阅读代码,逐步分析解决问题的思路和过程。这样的思考比知道结论更重要,希望我的分析能让你有所收获。

    版权声明:本文为guyongqiangx原创,欢迎转载,请注明出处:
    相关文章:

    A/B System系列

    Update Engine系列

    前面两篇分别分析了Makefile,Protobuf和AIDL相关文件,从本篇开始正式深入功能实现的代码文件去探究Update Engine。

    首先从Update Engine最简单的部分,客户端进程update_engine_client入手。

    1. update_engine_client的文件依赖

    Android自带的update_engine客户端update_engine_client应用很简单,只涉及到几个代码文件。

    依赖的文件:

    update_engine_client
      --> files (
            binder_bindings/android/os/IUpdateEngine.aidl
            binder_bindings/android/os/IUpdateEngineCallback.aidl
            common/error_code_utils.cc
            update_engine_client_android.cc
            update_status_utils.cc
          )
      --> shared libraries (
            libbrillo-stream
            libbrillo
            libchrome
            libbinder
            libbinderwrapper
            libbrillo-binder
            libutils
          )
    

    这里可以看到,Android自带的客户端demo进程update_engine_client的依赖比较简单,代码设计的文件比较少。

    2. update_engine_client代码分析

    2.1 命令行参数

    在开始逐行分析代码前,我们来看看这个客户端都有哪些参数和功能。

    我们在命令行运行update_engine_client --help,其输出如下:

    bcm7252ssffdr4:/ # 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
    

    我们在《Android A/B System OTA分析(四)系统的启动和升级》中有提到一个具体的升级场景,调用参数如下:

    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 \
    "
    

    2.2 代码分析

    在开始代码逐行分析之前,通过检查类UpdateEngineClientAndroid的定义,我画了一个类图,便于查看各个类之间的关系:

    UpdateEngineClientAndroid

    图1. UpdateEngineClientAndroid类图

    下面从update_engine_client入口main函数开始分析:

    # system\update_engine\update_engine_client_android.cc
    int main(int argc, char** argv) {
      chromeos_update_engine::internal::UpdateEngineClientAndroid client(
          argc, argv);
      return client.Run();
    }
    

    这个main函数真是简单,就两句话,先初始化生成一个UpdateEngineClientAndroid对象client,然后执行对象client.Run()方法~~
    纳尼?这就结束了?这就是update_engine_client的全部?

    先看看UpdateEngineClientAndroid的初始化:

    # system\update_engine\update_engine_client_android.cc
    class UpdateEngineClientAndroid : public brillo::Daemon {
      public:
        // 用传入的参数argc, argv初始化私有成员变量argc_, argv_
        UpdateEngineClientAndroid(int argc, char** argv) : argc_(argc), argv_(argv) {
      }
      ...
      private:
        // 下面定义了私有成员变量argc_和argv_用于存放main函数接收到的参数
        // Copy of argc and argv passed to main().
        int argc_;
        char** argv_;
      ...
    }
    

    紧接着调用client.Run(),这个方法在UpdateEngineClientAndroid的父类中定义:

    # external\libbrillo\brillo\daemons\daemon.h
    class BRILLO_EXPORT Daemon : public AsynchronousSignalHandlerInterface {
     public:
      ...
    
      // Performs proper initialization of the daemon and runs the message loop.
      // Blocks until the daemon is finished. The return value is the error
      // code that should be returned from daemon's main(). Returns EX_OK (0) on
      // success.
      virtual int Run();
      ...
     protected:
      ...
      virtual int OnInit();
    }
    

    实现上,由于这里Run()定义为virtual虚函数,所以运行时会先执行子类的同名函数UpdateEngineClientAndroid::Run(),但这里子类UpdateEngineClientAndroid并没有定义Run()函数,所以会执行父类brillo::Daemon的Run()函数,如下:

    # external\libbrillo\brillo\daemons\daemon.cc
    int Daemon::Run() {
      // 1. 执行OnInit函数进行初始化
      int exit_code = OnInit();
      if (exit_code != EX_OK)
        return exit_code;
    
      // 2. 初始化完成后调用brillo_message_loop_.Run()进入消息循环处理模式
      brillo_message_loop_.Run();
    
      // 3. 调用OnShutdown
      OnShutdown(&exit_code_);
    
      // 4. 进入while循环等待退出消息
      // base::RunLoop::QuitClosure() causes the message loop to quit
      // immediately, even if pending tasks are still queued.
      // Run a secondary loop to make sure all those are processed.
      // This becomes important when working with D-Bus since dbus::Bus does
      // a bunch of clean-up tasks asynchronously when shutting down.
      while (brillo_message_loop_.RunOnce(false /* may_block */)) {}
    
      return exit_code_;
    }
    

    这里先后有4个操作:

    1. 执行OnInit函数进行初始化
    2. 初始化完成后调用brillo_message_loop_.Run()进入消息循环处理模式
    3. 调用OnShutdown
    4. 进入while循环等待退出消息

    下面逐个来看这4个操作:

    1. 执行OnInit函数进行初始化
      由于int Daemon::OnInit()定义为虚函数:
    # external\libbrillo\brillo\daemons\daemon.h
    class BRILLO_EXPORT Daemon : public AsynchronousSignalHandlerInterface {
      ...
      protected:
        // 定义了OnInit为虚函数,运行时如果子类实现了OnInit,则执行的是子类的OnInit函数
        virtual int OnInit();
    }
    

    所以运行时执行的是Daemon对应子类UpdateEngineClientAndroid的OnInit()函数,如下:

    # system\update_engine\update_engine_client_android.cc
    int UpdateEngineClientAndroid::OnInit() {
      // 这里在子类中调用父类的OnInit操作,注册信号SIGTERM, SIGINT和SIGHUP的处理函数
      int ret = Daemon::OnInit();
      if (ret != EX_OK)
        return ret;
    

    这里先调用父类的Daemon::OnInit()函数,

    # external\libbrillo\brillo\daemons\daemon.cc
    int Daemon::OnInit() {
      async_signal_handler_.Init();
      for (int signal : {SIGTERM, SIGINT}) {
        async_signal_handler_.RegisterHandler(
            signal, base::Bind(&Daemon::Shutdown, base::Unretained(this)));
      }
      async_signal_handler_.RegisterHandler(
          SIGHUP, base::Bind(&Daemon::Restart, base::Unretained(this)));
      return EX_OK;
    }
    

    这里Daemon::OnInit()也没有做什么特别的,就是调用RegisterHandler注册了两个信号SIGTERM和SIGINT的handler,即Daemon::Shutdown和Daemon::Restart,但这两个handler其实是空的,什么都没做,如下:

    # external\libbrillo\brillo\daemons\daemon.cc
    void Daemon::OnShutdown(int* /* exit_code */) {
      // Do nothing.
    }
    
    bool Daemon::OnRestart() {
      // Not handled.
      return false;  // Returning false will shut down the daemon instead.
    }
    

    然后通过DEFINE_bool定义了一组参数:

      // 定义"update"参数,bool类型,默认为false
      DEFINE_bool(update, false, "Start a new update, if no update in progress.");
      // 定义"payload"参数, string类型
      DEFINE_string(payload,
                    "http://127.0.0.1:8080/payload",
                    "The URI to the update payload to use.");
      // 定义"offset"参数,int64类型
      DEFINE_int64(offset, 0,
                   "The offset in the payload where the CrAU update starts. "
                   "Used when --update is passed.");
      // 定义"size"参数,int64类型
      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.");
      // 定义"headers"参数,字符串类型
      DEFINE_string(headers,
                    "",
                    "A list of key-value pairs, one element of the list per line. "
                    "Used when --update is passed.");
      // 定义"suspend"参数,bool类型,默认为false
      DEFINE_bool(suspend, false, "Suspend an ongoing update and exit.");
      // 定义"resume"参数,bool类型,默认为false
      DEFINE_bool(resume, false, "Resume a suspended update.");
      // 定义"cancel"参数,bool类型,默认为false
      DEFINE_bool(cancel, false, "Cancel the ongoing update and exit.");
      // 定义"reset_status"参数,bool类型,默认为false
      DEFINE_bool(reset_status, false, "Reset an already applied update and exit.");
      // 定义"follow"参数,bool类型,默认为false
      DEFINE_bool(follow,
                  false,
                  "Follow status update changes until a final state is reached. "
                  "Exit status is 0 if the update succeeded, and 1 otherwise.");
    
      // 用argc_, argv_初始化命令行解析器
      // Boilerplate init commands.
      base::CommandLine::Init(argc_, argv_);
      // 我的理解是在这里解析argc_和argv_参数,如果不带参数,则显示错误并返回
      brillo::FlagHelper::Init(argc_, argv_, "Android Update Engine Client");
      if (argc_ == 1) {
        LOG(ERROR) << "Nothing to do. Run with --help for help.";
        return 1;
      }
    
      // 检查位置参数,没有详细去看,但不影响对整体的理解
      // Ensure there are no positional arguments.
      const std::vector<std::string> positional_args =
          base::CommandLine::ForCurrentProcess()->GetArgs();
      if (!positional_args.empty()) {
        LOG(ERROR) << "Found a positional argument '" << positional_args.front()
                   << "'. If you want to pass a value to a flag, pass it as "
                      "--flag=value.";
        return 1;
      }
    

    参考2.1节的命令行参数,显然,命令行处理选项将宏DEFINE_xxx展开,最终得到FLAGS_xxx变量,因此命令行选项和生成的FLAGS_xxx变量的对应关系为:

    • update --> FLAGS_update,
    • payload --> FLAGS_payload,
    • offset --> FLAGS_offset,
    • size --> FLAGS_size,
    • headers --> FLAGS_headers,
    • suspend --> FLAGS_suspend,
    • resume --> FLAGS_resume,
    • cancel --> FLAGS_cancel,
    • reset_status --> FLAGS_reset_status,
    • follow --> FLAGS_follow

    我没有深入看过base::CommandLine和brillo::FlagHelper类,从网上的介绍看是进行命令行处理的,从后面的操作看,这里应该是对argc_, argv_里面包含的命令行参数进行解析。

    联想到我们命令行调用的操作:

    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 \
    "
    

    所以这里有:

    FLAGS_payload: "http://stbszx-bld-5/public/android/full-ota/payload.bin"
    FLAGS_update: true
    FLAGS_headers: "FILE_HASH=ozGgyQEcnkI5ZaX+Wbjo5I/PCR7PEZka9fGd0nWa+oY= \
                    FILE_SIZE=282164983
                    METADATA_HASH=GLIKfE6KRwylWMHsNadG/Q8iy5f7ENWTatvMdBlpoPg= \
                    METADATA_SIZE=21023"
    

    分析完命令行选项解析后,继续查看后面的代码:

      bool keep_running = false;
      // 初始化Log操作
      brillo::InitLog(brillo::kLogToStderr);
    
      // Initialize a binder watcher early in the process before any interaction
      // with the binder driver.
      // binder_watcher_的初始化,对`binder_watcher_`具体作用还不太了解,这里先不做深入。
      binder_watcher_.Init();
    

    接下来获取"android.os.UpdateEngineService"服务,并将其代理对象存放到service_中,可以简单理解为所有UpdateEngineService服务的操作都可以调用service_成员的相应方法来实现。

      // 获取名为"android.os.UpdateEngineService"的服务对象
      android::status_t status = android::getService(
          android::String16("android.os.UpdateEngineService"), &service_);
      // 服务获取失败,提示错误并退出
      if (status != android::OK) {
        LOG(ERROR) << "Failed to get IUpdateEngine binder from service manager: "
                   << Status::fromStatusT(status).toString8();
        return ExitWhenIdle(1);
      }
    

    剩下的就是将命令行update_engine_client提供的各种操作,如suspend, resume, cancel, reset_status, follow, update通过代理对象service_通知服务进程UpdateEngineService。

      // 调用服务进程的"suspend"操作
      if (FLAGS_suspend) {
        return ExitWhenIdle(service_->suspend());
      }
    
      // 调用服务进程的"resume"操作
      if (FLAGS_resume) {
        return ExitWhenIdle(service_->resume());
      }
    
      // 调用服务进程的"cancel"操作
      if (FLAGS_cancel) {
        return ExitWhenIdle(service_->cancel());
      }
    
      // 调用服务进程的"resetStatus"操作
      if (FLAGS_reset_status) {
        return ExitWhenIdle(service_->resetStatus());
      }
    
      // 如果指定"follow"选项,则绑定回调操作UECallback
      if (FLAGS_follow) {
        // Register a callback object with the service.
        callback_ = new UECallback(this);
        bool bound;
        if (!service_->bind(callback_, &bound).isOk() || !bound) {
          LOG(ERROR) << "Failed to bind() the UpdateEngine daemon.";
          return 1;
        }
        keep_running = true;
      }
    
      // 如果指定"update"操作,则解析"headers"参数
      if (FLAGS_update) {
        // 解析"headers",生成键值对列表
        std::vector<std::string> headers = base::SplitString(
            FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
        std::vector<android::String16> and_headers;
        for (const auto& header : headers) {
          and_headers.push_back(android::String16{header.data(), header.size()});
        }
        // 调用服务进程的"applyPlayload"操作
        Status status = service_->applyPayload(
            android::String16{FLAGS_payload.data(), FLAGS_payload.size()},
            FLAGS_offset,
            FLAGS_size,
            and_headers);
        if (!status.isOk())
          return ExitWhenIdle(status);
      }
    
      if (!keep_running)
        return ExitWhenIdle(EX_OK);
    

    前面几个suspend, resume, cancelreset_status都比较直接,直接通过无参数调用service_->suspend(), service_->resume(), service_->cancel()service_->resetStatus()通知服务进程UpdateEngineService。

    对于follow操作,则生成一个UECallback对象,并通过service_->bind(callback_, &bound)将其绑定到UpdateEngineService服务端的IUpdateEngineCallback对象上。

    对于update操作,

      if (FLAGS_update) {
        std::vector<std::string> headers = base::SplitString(
            FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
        std::vector<android::String16> and_headers;
        for (const auto& header : headers) {
          and_headers.push_back(android::String16{header.data(), header.size()});
        }
        Status status = service_->applyPayload(
            android::String16{FLAGS_payload.data(), FLAGS_payload.size()},
            FLAGS_offset,
            FLAGS_size,
            and_headers);
        if (!status.isOk())
          return ExitWhenIdle(status);
      }
    

    这里先将FLAGS_headers按照换行符"\n"进行拆分,并存放到headers中,
    然后将headers的每一项通过push_back操作存放到容器and_headers中。
    可以简单理解为将headers操作的每一行对分别存放到容器and_header中,这样and_header中的每一项都是一个键值对字符串:

    FILE_HASH=ozGgyQEcnkI5ZaX+Wbjo5I/PCR7PEZka9fGd0nWa+oY=
    FILE_SIZE=282164983
    METADATA_HASH=GLIKfE6KRwylWMHsNadG/Q8iy5f7ENWTatvMdBlpoPg=
    METADATA_SIZE=21023
    

    然后将payload, offset, size参数和解析得到的and_headers一并传递给service_->applyPayload()方法,此时服务端UpdateEngineService进程会调用applyPayload进行升级更新。

    如果service_->applyPayload()调用操作失败,则调用ExitWhenIdle(status)并退出:

        if (!status.isOk())
          return ExitWhenIdle(status);
    

    由于follow状态需要一直跟踪server端的状态,因此要求一直运行,但除follow操作外的其它操作,在执行完后就完成了,不再需要继续执行,所以如果keep_runing为false,则退出:

      if (!keep_running)
        return ExitWhenIdle(EX_OK);
    

    接下来就是client运行在follow状态才出现的情况了?需要一直follow到永远吗?原则上是的。但是如果server端挂掉了,再follow就没有意义了,所以注册一个事件来检查server端是否已经挂掉:

      // When following updates status changes, exit if the update_engine daemon
      // dies.
      android::BinderWrapper::Create();
      android::BinderWrapper::Get()->RegisterForDeathNotifications(
          android::os::IUpdateEngine::asBinder(service_),
          base::Bind(&UpdateEngineClientAndroid::UpdateEngineServiceDied,
                     base::Unretained(this)));
    
      return EX_OK;
    }
    

    对OnInit()函数总体描述如下:

    1. 解析可执行程序的命令行参数
    2. 根据命令行参数指定的操作,调用服务端的相应接口
    3. 如果是follow操作,则向服务端注册callback的客户端调用接口callback_
    4. 如果是update操作,则解析payload的相关参数,并将其传递给服务端的service_->applyPayload操作
    5. 对于follow操作,客户端需要一直跟踪服务端update_engine状态,如果update_engine进程退出了,那客户端也需要收到通知并退出

    回到client.Run()实际执行的Daemon::Run()函数,其实会发现除了前面分析的OnInit函数,剩下的就很简单了:

    # external\libbrillo\brillo\daemons\daemon.cc
    int Daemon::Run() {
      int exit_code = OnInit();
      if (exit_code != EX_OK)
        return exit_code;
    
      brillo_message_loop_.Run();
    
      OnShutdown(&exit_code_);
    
      // base::RunLoop::QuitClosure() causes the message loop to quit
      // immediately, even if pending tasks are still queued.
      // Run a secondary loop to make sure all those are processed.
      // This becomes important when working with D-Bus since dbus::Bus does
      // a bunch of clean-up tasks asynchronously when shutting down.
      while (brillo_message_loop_.RunOnce(false /* may_block */)) {}
    
      return exit_code_;
    }
    

    完成OnInit()操作后,如果OnInit返回了非EX_OK值,说明操作失败,直接退出程序。

    成功执行OnInit()操作后,进程调用brillo_message_loop_.Run()来循环处理消息。

    3. 总结

    分析完update_engine_client的代码,我们发现整个操作还是比较简单,一句话总结如下:

    update_engine_client解析命令行的各种操作(suspend/resume/cancel/reset_status/follow/update),并将这些操作和参数通过binder机制,转发为对服务端进程UpdateEngineService相应操作的调用。

    所以,剩下的事就是根据update_engine_client的各种操作和传入参数,分析服务端进程UpdateEngineService的行为。

    4. 联系和福利

    • 个人微信公众号“洛奇看世界”,一个大龄码农的救赎之路。

      • 公众号回复关键词“Android电子书”,获取超过150本Android相关的电子书和文档。电子书包含了Android开发相关的方方面面,从此你再也不需要到处找Android开发的电子书了。
      • 公众号回复关键词“个人微信”,获取个人微信联系方式。我组建了一个Android OTA的讨论组,联系我,说明Android OTA,拉你进组一起讨论。

      image

    更多相关内容
  • AndroidUpdate.zip

    2019-09-20 14:56:58
    AndroidUpdate.zip,一款简洁的App升级库
  • 上一篇《Android Update Engine 分析(十)生成 payload 和 metadata 的哈希》详细分析了 metadata 和 payload 的哈希时如何生成的,本篇继续分析脚本中是如何对这两个哈希进行签名,并将签名结果更新到 payload ...

    Android Update Engine分析(十一) 更新 payload 签名

    相关文章:

    Android A/B System 分析系列

    Android Update Engine 分析系列

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

    1. 生成 payload 和 metadata 数据的哈希值
    2. 更新 payload 和 metadata 数据的签名
    3. 带 public_key 参数的情况,验证 payload 的 signature
    4. 提取 payload 文件的 properties数据
    5. 对 old image 打 delta 补丁
    6. 生成 payload 数据

    上一篇《Android Update Engine 分析(十)生成 payload 和 metadata 的哈希》详细分析了 metadata 和 payload 的哈希时如何生成的,本篇继续分析脚本中是如何对这两个哈希进行签名,并将签名结果更新到 payload 文件中。

    本篇比较简单,主要内容有两点:

    1. 总结 payload 的处理流程
    2. 详细分析 payload 的签名是如何更新的

    实际上,最核心的函数 AddSignatureToPayload 在前一篇已经分析过,所以可以根据你的实际情况考虑是否阅读。

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

    1. 总结 payload 数据的生成和处理

    Android Update Engine 分析(八)升级包制作脚本分析》分析过 OTA 包的制作流程,这里重新简述相关内容。

    下面的总结很有用,实际上就是生成 payload 文件的核心操作,当你明白了这些执行的逻辑,可以自己在命令行上手动通过命令生成 payload 数据,或者根据需要对生成 payload 的逻辑进行修改。

    1.1 ota_from_target_files.py 脚本层面

    ota_from_target_files.py 脚本层面调用 brillo_update_payload:

    # 步骤 1. 基于 new.zip 和 old.zip 生成 payload 文件
    # brillo_update_payload generate --payload /tmp/payload-unsigned.bin \
    #                                --target_image dist/new.zip \
    #                                --source_image dist/old.zip
    
    # 步骤 2. 生成 payload 和 metadata 数据的哈希值
    # 从 payload 数据生成 payload 哈希和 metadata 哈希:
    # brillo_update_payload hash --unsigned_payload /tmp/payload-unsigned.bin \
    #                            --signature_size 256 \
    #                            --metadata_hash_file /tmp/metadata-hash.bin \
    #                            --payload_hash_file /tmp/payload-hash.bin
    #
    
    # 步骤 3. 对 payload 哈希和 metadata 哈希数据进行签名
    # 如果没有指定签名使用的 key, 则基于 testkey.pk8 生成一个临时 key 用于签名
    # 'openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt -out /tmp/key-temp.key'
    
    # 步骤 3a. 使用 rsa_key 对 payload 哈希进行签名
    # openssl pkeyutl -sign -inkey /tmp/key-temp.key \
    #                       -pkeyopt digest:sha256 \
    #                       -in /tmp/payload-hash.bin \
    #                       -out /tmp/signed-payload-hash.bin
    
    # 步骤 3b. 使用 rsa_key 对 metadata 哈希进行签名
    # openssl pkeyutl -sign -inkey /tmp/key-temp.key \
    #                       -pkeyopt digest:sha256 \
    #                       -in /tmp/metadata-hash.bin \
    #                       -out /tmp/signed-metadata-hash.bin
    
    # 步骤 3c. 将 payload 签名 和metadata 签名写回 payload 数据
    # brillo_update_payload sign --unsigned_payload /tmp/payload-unsigned.bin \
    #                            --payload /tmp/signed-payload.bin \
    #                            --signature_size 256 \
    #                            --metadata_signature_file /tmp/signed-metadata-hash.bin \
    #                            --payload_signature_file /tmp/signed-payload-hash.bin
    
    # 步骤 4. 提取签名后的 payload 文件的 properties 数据
    # brillo_update_payload properties --payload /tmp/signed-payload.bin \
    #                                  --properties_file /tmp/signed-payload-properties.txt
    

    在制作 OTA 包时,中间生成的临时文件都是一些无意义的名字,例如: sig-Cdhb80.bin。这里为了便于理解,将这些临时文件名替换成一些有意义的名字:

    • 原始的 payload: payload-unsigned.bin
    • 基于 payload-unsigned.bin 生成的哈希
      • metadata 哈希: metadata-hash.bin
      • payload 哈希: payload-hash.bin
    • 对哈希进行签名的 key: key-temp.key
    • payload 哈希的签名: signed-payload-hash.bin
    • metadata 哈希的签名: signed-metadata-hash.bin
    • 签名后的 payload: signed-payload.bin
    • 签名后的 payload 文件的 property 数据: signed-payload-properties.txt

    1.2 delta_generator 工具层面

    深入下一层,在 brillo_update_payload 脚本调用 delta_generator 工具:

    # 步骤 1. 基于 new.zip 和 old.zip 生成 payload 文件
    # 将 new.zip 和 old.zip 中的 boot.img 和 system.img 解包出来, 用于生成 payload-unsigned.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
    
    # 步骤 2. 生成 payload 和 metadata 数据的哈希值
    # 使用 delta_generator 从 payload 数据生成 payload 哈希和 metadata 哈希:
    # delta_generator -in_file=/tmp/payload_file.bin \
    #                 -signature_size=256 \
    #                 -out_hash_file=/tmp/payload-hash.bin \
    #                 -out_metadata_hash_file=/tmp/metadata-hash.bin
    
    # 步骤 3. 对 payload 哈希和 metadata 哈希数据进行签名
    # 如果没有指定签名使用的 key, 则基于 testkey.pk8 生成一个临时 key 用于签名
    # 'openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt -out /tmp/key-temp.key'
    
    # 步骤 3a. 使用 rsa_key 对 payload 哈希进行签名
    # openssl pkeyutl -sign -inkey /tmp/key-temp.key \
    #                       -pkeyopt digest:sha256 \
    #                       -in /tmp/payload-hash.bin \
    #                       -out /tmp/signed-payload-hash.bin
    
    # 步骤 3b. 使用 rsa_key 对 metadata 哈希进行签名
    # openssl pkeyutl -sign -inkey /tmp/key-temp.key \
    #                       -pkeyopt digest:sha256 \
    #                       -in /tmp/metadata-hash.bin \
    #                       -out /tmp/signed-metadata-hash.bin
    
    # 步骤 3c. 将 payload 签名 和metadata 签名写回 payload 数据
    # delta_generator -in_file=/tmp/payload_file.bin \
    #                 -signature_size=256 \
    #                 -signature_file=/tmp/signed-payload-hash.bin \
    #                 -metadata_signature_file=/tmp/signed-metadata-hash.bin \
    #                 -out_file=/tmp/signed-payload.bin
    
    # 步骤 4. 提取签名后的 payload 文件的 properties 数据
    # delta_generator -in_file=/tmp/signed-payload.bin \
    #                 -properties_file=/tmp/signed-payload-properties.txt
    

    2. 更新 payload 文件签名

    所谓的更新 payload 文件签名,实际上是先用 openssl 对 payload 数据的哈希和 metadata 数据的哈希签名,然后在这里将签名更新到 payload 文件中。

    在 1.2 节总结的步骤中,delta_generator 先生成 metadata 和 payload 的哈希,然后步骤 3a, 3b 使用 key 对其进行签名,步骤 3c 再将签名文件写回 payload 文件。

    写回签名时,还是通过 delta_generator 工具进行的,这就是本篇想要分析的代码:

    delta_generator -in_file=/tmp/payload_file.bin \
                    -signature_size=256 \
                    -signature_file=/tmp/signed-payload-hash.bin \
                    -metadata_signature_file=/tmp/signed-metadata-hash.bin \
                    -out_file=/tmp/signed-payload.bin
    

    关于关于参数的传递,这里不再详细分析,不清楚的请转到《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》 第 2.2 节,查看参数到底是如何解析的。

    2.1 Main 函数

    在 Main 函数中,是这样处理的:

      if (!FLAGS_signature_file.empty()) {
        SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file,
                    FLAGS_metadata_signature_file, FLAGS_out_metadata_size_file);
        return 0;
      }
    

    直接将 in_file, out_file, signature_filemetadata_signature_file 传递给 SignPayload 函数,至于 out_metadata_size_file,由于没有传递这个参数,所以为空。

    2.2 SignPayload 函数

    关于 SignPayload 函数,其逻辑也比较简单:

    void SignPayload(const string& in_file,
                     const string& out_file,
                     const string& payload_signature_file,
                     const string& metadata_signature_file,
                     const string& out_metadata_size_file) {
      # 检查传入的 in_file, out_file, payload_signature_file 参数
      LOG(INFO) << "Signing payload.";
      LOG_IF(FATAL, in_file.empty())
          << "Must pass --in_file to sign payload.";
      LOG_IF(FATAL, out_file.empty())
          << "Must pass --out_file to sign payload.";
      LOG_IF(FATAL, payload_signature_file.empty())
          << "Must pass --signature_file to sign payload.";
          
      # 将 payload 和 metadata 的签名文件转换为 protobuf 格式数据
      vector<brillo::Blob> signatures, metadata_signatures;
      SignatureFileFlagToBlobs(payload_signature_file, &signatures);
      SignatureFileFlagToBlobs(metadata_signature_file, &metadata_signatures);
      
      # 调用 AddSignatureToPayload 将 payload 和 metadata 的签名写入到 payload 中并输出到 out_file 指定的文件
      uint64_t final_metadata_size;
      CHECK(PayloadSigner::AddSignatureToPayload(in_file, signatures,
          metadata_signatures, out_file, &final_metadata_size));
      LOG(INFO) << "Done signing payload. Final metadata size = "
                << final_metadata_size;
    
      # 将写入签名后的 payload 文件的 metadata size 写入到 out_metadata_size_file 文件中
      # 由于 delta_generator 调用时没有提供 out_metadata_size_file 参数,所以这里的操作不会执行
      if (!out_metadata_size_file.empty()) {
        string metadata_size_string = std::to_string(final_metadata_size);
        CHECK(utils::WriteFile(out_metadata_size_file.c_str(),
                               metadata_size_string.data(),
                               metadata_size_string.size()));
      }
    }
    

    2.3 AddSignatureToPayload 函数

    AddSignatureToPayload 将 metadata 和 payload 的签名写入到 payload 文件,如果想了解操作的位置细节,还需要用到上一章中的 payload 的布局图:

    payload 文件布局

    对照着 payload 的布局图,再理解 AddSignatureToPayload 就容易了很多。

    实际上,我们在上一篇《Android Update Engine 分析(十)生成 payload 和 metadata 的哈希》的第 “2.4 AddSignatureBlobToPayload 函数” 中已经分析过了这个函数,因此这里不再重复分析。

    不同的是,在生成 payload 和 metadata 的哈希时,通过 AddSignatureToPayload 写入的是全 0 的伪签名数据,而这里写入的是真实的签名。

    3. 总结写回 payload 和 metadata 签名的流程

    以下简单总结写回 payload 和 metadata 签名的流程:

    缩进关系表示下一级调用, 同样的缩进表明同一层次的调用

    "ota_from_target_files -i dist/base.zip dist/new.zip dist/update.zip", 差分包制作命令
       --> "brillo_update_payload hash --unsigned_payload /tmp/payload-unsigned.bin \
                                --signature_size 256 \
                                --metadata_hash_file /tmp/metadata-hash.bin \
                                --payload_hash_file /tmp/payload-hash.bin" 生成哈希
       --> "brillo_update_payload sign --unsigned_payload /tmp/payload-unsigned.bin \
                                 --payload /tmp/signed-payload.bin \
                                 --signature_size 256 \
                                 --metadata_signature_file /tmp/signed-metadata-hash.bin \
                                 --payload_signature_file /tmp/signed-payload-hash.bin" 写回哈希
          --> SignPayload
             --> AddSignatureToPayload
             --> WriteFile(out_metadata_size_file)
    

    因为前面已经分析过 AddSignatureToPayload 函数,所以这里就很简单。

    4. 其它

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

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

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

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

    收钱码

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

    公众号

    展开全文
  • Android Update Engine分析(四)服务端进程 前面三篇分别分析了Makefile,Protobuf和AIDL相关文件以及Update Engine的客户端进程update_engine_client, Android Update Engine分析(一)Makefile Android ...

    Android Update Engine分析(四)服务端进程

    前面三篇分别分析了Makefile,Protobuf和AIDL相关文件以及Update Engine的客户端进程update_engine_client

    相关文章:

    A/B System系列

    Update Engine系列

    本篇开始分析Update Engine的服务端进程update_engine

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

    1. update_engine的文件依赖关系

    老规矩,在开始代码之前,先看看文件的依赖关系。

    有人或许会问,分析代码就分析代码,干嘛还在先去列举依赖的文件?岂不多此一举?

    当你尝试阅读代码就会明白,代码中涉及多个分支的条件编译时,有些函数在多个分支实现中都会存在,这时到底应该分析哪个文件就是个问题。如果将这些文件的依赖都列举出来,很容易就知道哪些文件有参与编译,哪些文件没有被使用。如果不确定,那就回头来看看代码的文件依赖关系就知道了。

    例如名为update_engine_client.cc的文件,里面有main函数,有各种实现代码,看起来像是客户端的入口,那这个文件真的是客户端update_engine_client的入口吗?

    又例如,名为update_attempter.cc的文件,里面也实现了一套Update Engine的核心逻辑,那应该分析这个文件吗?

    单从文件命名上来看,根本无法确定上面提到的这两个文件是否有参与编译。如果你有文件依赖列表,你就知道客户端进程的代码是update_engine_client_android.cc;而Android上Update Engine中使用的是update_attempter_android.cc而不是update_attempter.cc

    Android Update Engine的服务端守护进程自身的代码文件只有一个,那就是main.cc,其余是对各种库的依赖,以下列举服务端守护进程update_engine和Update Engine相关库和文件的依赖关系。

    服务端守护进程update_engine

    update_engine
      --> files (
            main.cc
          )
      --> static libraries (
            libupdate_engine_android
            libpayload_consumer
            libfs_mgr
            update_metadata-protos
            libxz-host
            libbz
          )
      --> shared libraries (
            libcrypto-host
            libprotobuf-cpp-lite
            libandroid
            libbinder
            libbinderwrapper
            libcutils
            libcurl
            libhardware
            libssl
            libssl
          )
    

    服务端守护进程update_engine除去依赖Android的公共静态和动态库之外,跟Update Engine相关的库主要有三个:

    • libupdate_engine_android
    • libpayload_consumer
    • update_metadata-protos

    我们的目标是分析Update Engine相关内容,因此这里对公共的静态和动态库不做展开说明。

    下面是Update Engine三个主要的库文件依赖关系:

    libupdate_engine_android (STATIC_LIBRARIES)
      --> binder_bindings/android/os/IUpdateEngine.aidl
          binder_bindings/android/os/IUpdateEngineCallback.aidl
          binder_service_android.cc
          boot_control_android.cc
          certificate_checker.cc
          daemon.cc
          daemon_state_android.cc
          hardware_android.cc
          libcurl_http_fetcher.cc
          network_selector_android.cc
          proxy_resolver.cc
          update_attempter_android.cc
          update_status_utils.cc
          utils_android.cc
    
    libpayload_consumer (STATIC_LIBRARIES)
      --> common/action_processor.cc
          common/boot_control_stub.cc
          common/clock.cc
          common/constants.cc
          common/cpu_limiter.cc
          common/error_code_utils.cc
          common/hash_calculator.cc
          common/http_common.cc
          common/http_fetcher.cc
          common/file_fetcher.cc
          common/hwid_override.cc
          common/multi_range_http_fetcher.cc
          common/platform_constants_android.cc
          common/prefs.cc
          common/subprocess.cc
          common/terminator.cc
          common/utils.cc
          payload_consumer/bzip_extent_writer.cc
          payload_consumer/delta_performer.cc
          payload_consumer/download_action.cc
          payload_consumer/extent_writer.cc
          payload_consumer/file_descriptor.cc
          payload_consumer/file_writer.cc
          payload_consumer/filesystem_verifier_action.cc
          payload_consumer/install_plan.cc
          payload_consumer/payload_constants.cc
          payload_consumer/payload_verifier.cc
          payload_consumer/postinstall_runner_action.cc
          payload_consumer/xz_extent_writer.cc
    
    update_metadata-protos (STATIC_LIBRARIES)
      --> update_metadata.proto
    

    如果阅读代码时不清楚文件是否有用,回头来看看这个列表吧。

    闲话到此位置,以下对代码展开分析。

    2. update_engine代码分析

    2.1 main.cc

    服务端进程update_engine的入口在main.cc文件中,因此这里从main函数入手。

    // 文件: system\update_engine\main.cc
    int main(int argc, char** argv) {
      DEFINE_bool(logtostderr, false,
                  "Write logs to stderr instead of to a file in log_dir.");
      DEFINE_bool(foreground, false,
                  "Don't daemon()ize; run in foreground.");
    
      chromeos_update_engine::Terminator::Init();
      brillo::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
      chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
      if (!FLAGS_foreground)
        PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
    
      LOG(INFO) << "Chrome OS Update Engine starting";
    
      // xz-embedded requires to initialize its CRC-32 table once on startup.
      xz_crc32_init();
    
      // Ensure that all written files have safe permissions.
      // This is a mask, so we _block_ all permissions for the group owner and other
      // users but allow all permissions for the user owner. We allow execution
      // for the owner so we can create directories.
      // Done _after_ log file creation.
      umask(S_IRWXG | S_IRWXO);
    
      chromeos_update_engine::UpdateEngineDaemon update_engine_daemon;
      int exit_code = update_engine_daemon.Run();
    
      LOG(INFO) << "Chrome OS Update Engine terminating with exit code "
                << exit_code;
      return exit_code;
    }
    

    整个main函数看起来比较简单:

    1. 定义入口参数并进行初始化;
    2. 初始化CRC-32 table;
    3. 生成update_engine_daemon,并调用其Run()方法;
    • 定义入口参数并进行初始化
      # 定义参数logtostderr
      DEFINE_bool(logtostderr, false,
                  "Write logs to stderr instead of to a file in log_dir.");
      # 定义参数foreground
      DEFINE_bool(foreground, false,
                  "Don't daemon()ize; run in foreground.");
    
      # 初始化Terminator
      chromeos_update_engine::Terminator::Init();
      # 解析参数
      brillo::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
      chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
      if (!FLAGS_foreground)
        PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
    
      LOG(INFO) << "Chrome OS Update Engine starting";
    

    通过DEFINE_bool宏定义了两个参数logtostderr, foreground,展开后得到两个变量FLAGS_logtostderrFLAGS_foreground。前者用于设置日志输出重定向,后者用于指定 update_engine进程是否以forground方式运行。

    • 初始化CRC-32 table
      // xz-embedded requires to initialize its CRC-32 table once on startup.
      xz_crc32_init();
    
      // Ensure that all written files have safe permissions.
      // This is a mask, so we _block_ all permissions for the group owner and other
      // users but allow all permissions for the user owner. We allow execution
      // for the owner so we can create directories.
      // Done _after_ log file creation.
      umask(S_IRWXG | S_IRWXO);
    

    这段代码目前不清楚后面哪里会用到,看起来像是创建一个查找表,用于解压缩时提高性能。不是分析重点,不解释。

    umask操作设置当前进程的文件操作权限。

    • 生成update_engine_daemon对象,并调用其Run()方法

    Update Engine服务端守护进程的核心update_engine_daemon对象。

      chromeos_update_engine::UpdateEngineDaemon update_engine_daemon;
      int exit_code = update_engine_daemon.Run();
    
      LOG(INFO) << "Chrome OS Update Engine terminating with exit code "
                << exit_code;
      return exit_code;
    

    update_engine跟客户端进程update_engine_client一样,都是派生于brillo::Daemon类,所以这里会执行父类brillo::DaemonRun()方法:

    // 文件: external\libbrillo\brillo\daemons\daemon.cc
    int Daemon::Run() {
      // 1. 执行OnInit函数进行初始化
      int exit_code = OnInit();
      if (exit_code != EX_OK)
        return exit_code;
    
      // 2. 初始化完成后调用brillo_message_loop_.Run()进入消息循环处理模式
      brillo_message_loop_.Run();
    
      // 3. 调用OnShutdown
      OnShutdown(&exit_code_);
    
      // 4. 等待退出消息
      // base::RunLoop::QuitClosure() causes the message loop to quit
      // immediately, even if pending tasks are still queued.
      // Run a secondary loop to make sure all those are processed.
      // This becomes important when working with D-Bus since dbus::Bus does
      // a bunch of clean-up tasks asynchronously when shutting down.
      while (brillo_message_loop_.RunOnce(false /* may_block */)) {}
    
      return exit_code_;
    }
    

    这里先后有4个操作:

    1. 执行OnInit()函数进行初始化
    2. 初始化完成后调用brillo_message_loop_.Run()进入消息循环处理模式
    3. Run()退出则调用OnShutdown(),其实OnShutdown()操作里面什么都没做。
    4. 等待退出消息

    总体上,整个进程的结构比较简单,就是基于brillo::Daemon类的结构,进行初始化,启动brilllo::MessageLoop机制进行消息循环处理直到退出。这4个操作中,重点是文件system\update_engine\daemon.cc中的OnInit()函数。

    2.2 UpdateEngineDaemon

    文件system\update_engine\daemon.cc提供了类UpdateEngineDaemon的实现。

    留意跟Update Engine相关的daemon.cc文件有两个,分别是:

    • external\libbrillo\brillo\daemons\daemon.cc
    • system\update_engine\daemon.cc

    前者定义了brillo::Daemon类,作为update_engine系统的消息处理框架。后者定义了UpdateEngineDaemon类, 继承自前者,在update_engine守护进程中实例化生成业务对象update_engine_daemon

    update_engine_daemon.Run()会执行父类的Daemon::Run()方法,在该方法中调用OnInit()函数进行初始化时,由于int Daemon::OnInit()定义为虚函数,所以这里执行的是其子类UpdateEngineDaemonOnInit()函数,这是整个Update Engine初始化最重要的部分:

    // 文件: system\update_engine\daemon.cc
    
    int UpdateEngineDaemon::OnInit() {
      // Register the |subprocess_| singleton with this Daemon as the signal
      // handler.
      subprocess_.Init(this);
    
      // 调用父类brillo::Daemon的OnInit()方法,注册SIGTERM, SIGINT, SIGHUP的处理函数
      int exit_code = Daemon::OnInit();
      if (exit_code != EX_OK)
        return exit_code;
    
      // Android.mk中分析过USE_WEAVE=0, USE_BINDER=1,以下代码会被编译
    #if USE_WEAVE || USE_BINDER
      android::BinderWrapper::Create();
      binder_watcher_.Init();
    #endif  // USE_WEAVE || USE_BINDER
    
      // Android.mk中分析过,这里USE_DBUS=0, 不会编译以下代码,略过
    #if USE_DBUS
      // We wait for the D-Bus connection for up two minutes to avoid re-spawning
      // the daemon too fast causing thrashing if dbus-daemon is not running.
      scoped_refptr<dbus::Bus> bus = dbus_connection_.ConnectWithTimeout(
          base::TimeDelta::FromSeconds(kDBusSystemMaxWaitSeconds));
    
      if (!bus) {
        // TODO(deymo): Make it possible to run update_engine even if dbus-daemon
        // is not running or constantly crashing.
        LOG(ERROR) << "Failed to initialize DBus, aborting.";
        return 1;
      }
    
      CHECK(bus->SetUpAsyncOperations());
    #endif  // USE_DBUS
    
      // 由于没有定义__BRILLO__和__CHROMEOS__,所以这里应该走else路径
    #if defined(__BRILLO__) || defined(__CHROMEOS__)
      // Initialize update engine global state but continue if something fails.
      // TODO(deymo): Move the daemon_state_ initialization to a factory method
      // avoiding the explicit re-usage of the |bus| instance, shared between
      // D-Bus service and D-Bus client calls.
      RealSystemState* real_system_state = new RealSystemState(bus);
      daemon_state_.reset(real_system_state);
      LOG_IF(ERROR, !real_system_state->Initialize())
          << "Failed to initialize system state.";
    #else  // !(defined(__BRILLO__) || defined(__CHROMEOS__))
      // 针对非__BRILLO__和__CHROMEOS__的路径
      // 初始化一个类DaemonStateAndroid的实例,赋值到daemon_state_android
      // 对于DaemonStateAndroid,其构造函数DaemonStateAndroid()为空,没有任何操作:
      // DaemonStateAndroid() = default;
      DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
      // 用指针daemon_state_android设置daemon_state_成员
      daemon_state_.reset(daemon_state_android);
      
      // 接下来调用DaemonStateAndroid类的方法Initialize()进行初始化
      // update_engine进程真正的初始化开始啦……
      LOG_IF(ERROR, !daemon_state_android->Initialize())
          << "Failed to initialize system state.";
    #endif  // defined(__BRILLO__) || defined(__CHROMEOS__)
    
      // USE_BINDER=1,以下代码会被编译
    #if USE_BINDER
      // Create the Binder Service.
    #if defined(__BRILLO__) || defined(__CHROMEOS__) // 由于没有定义__BRILLO__和__CHROMEOS__,所以这里应该走else路径
      binder_service_ = new BinderUpdateEngineBrilloService{real_system_state};
    #else  // !(defined(__BRILLO__) || defined(__CHROMEOS__))
      // 生成Binder服务对象`binder_service_`
      binder_service_ = new BinderUpdateEngineAndroidService{
          daemon_state_android->service_delegate()};
    #endif  // defined(__BRILLO__) || defined(__CHROMEOS__)
      // 使用`binder_service_`向系统注册名为`"android.os.UpdateEngineService"`的服务
      auto binder_wrapper = android::BinderWrapper::Get();
      if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
                                           binder_service_)) {
        LOG(ERROR) << "Failed to register binder service.";
      }
    
      // 以观察者模式将`binder_service`添加到`daemon_state_`的观察者集合中
      // 观察者设计模式中,被观察对象以广播方式向注册的观察者发送消息,降低各对象的耦合度
      daemon_state_->AddObserver(binder_service_.get());
    #endif  // USE_BINDER
    
    // Android.mk中分析过,这里USE_DBUS=0, 不会编译以下代码,略过
    #if USE_DBUS  
      // Create the DBus service.
      dbus_adaptor_.reset(new UpdateEngineAdaptor(real_system_state, bus));
      daemon_state_->AddObserver(dbus_adaptor_.get());
    
      dbus_adaptor_->RegisterAsync(base::Bind(&UpdateEngineDaemon::OnDBusRegistered,
                                              base::Unretained(this)));
      LOG(INFO) << "Waiting for DBus object to be registered.";
    #else  // !USE_DBUS
      // 从字面`StartUpdater`看,这里开始Update Engine的核心工作
      daemon_state_->StartUpdater();
    #endif  // USE_DBUS
      return EX_OK;
    }
    
    

    这段代码使用的宏特别多,看起来很凌乱,整理一下,主要有以下几件事:

    • 创建一个DaemonStateAndroid类的对象daemon_state_android,并对其进行初始化

      DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
      daemon_state_.reset(daemon_state_android);
      LOG_IF(ERROR, !daemon_state_android->Initialize())
          << "Failed to initialize system state.";
      

      UpdateEngineDaemon中通过unique_ptr指针daemon_state_访问daemon_state_android

    • 类对象daemon_state_android调用service_delegate()返回一个委托对象,用于构造提供Binder服务的私有私有智能指针binder_service_

      binder_service_ = new BinderUpdateEngineAndroidService{
          daemon_state_android->service_delegate()};
      

      其实binder_service_也是通过daemon_state_实现的。从代码可见,通过调用daemon_state_androidservice_delegate()接口:

      ServiceDelegateAndroidInterface* DaemonStateAndroid::service_delegate() {
        return update_attempter_.get();
      }
      

      将其私有成员update_attempter_传递给BinderUpdateEngineAndroidService类的构造函数:

      BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService(
          ServiceDelegateAndroidInterface* service_delegate)
          : service_delegate_(service_delegate) {
      }
      

      所以这里就是将daemon_state_的私有成员update_attempter_委托给binder_service_的私有成员service_delegate_

      BinderUpdateEngineAndroidService的实现中,applyPayload/suspend/resume/cancel/resetStatus操作都会转发给成员service_delegate_,所以这些操作最终由update_attempter_来执行。可以这么理解,binder_service_也是通过daemon_state_来实现。

      实际上,如果机械的理解"binder_service_是通过daemon_state_来实现"是有问题的,因为除了上面提到的"applyPayload / suspend / resume / cancel / resetStatus"部分,binder_service_在运行中还有调用callback进行的状态更新操作,这些callback操作接口是使用bind调用的传入参数设置的,并非来自daemon_state_,所以不是由daemon_state_实现。

      所以这里只是粗略的将binder_service_想象为是通过daemon_state_来实现而已。

    • 使用私有的binder_service_对象向系统注册名为"android.brillo.UpdateEngineService"的系统服务

      auto binder_wrapper = android::BinderWrapper::Get();
      if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
                                           binder_service_)) {
        LOG(ERROR) << "Failed to register binder service.";
      }
      
    • binder_service添加为daemon_state_的观察对象,并通过StartUpdater()调用升级的主类UpdateAttempterAndroid进行各种处理

      daemon_state_->AddObserver(binder_service_.get());
      ...
      daemon_state_->StartUpdater();
      

      进行完上面的设置,整个Update Engine服务端的业务基本上也就交由daemon_state_的私有成员update_attempter_来处理了。所以daemon_state_对象对应的类DaemonStateAndroid,是真正的Update Engine业务实现者,而其私有成员update_attempter_所在的类才是整个Update Engine服务端的核心类。

    综述一下,代码看起来很复杂,但简化理解起来也还算简单。为什么这么说呢?

    服务端进程的业务对象update_engine_daemonUpdateEngineDaemon类的实例,通过继承brillo::Daemon类构建了daemon进程的基本功能,包含了3个重要的私有指针成员:

    • binder_watcher_
    • binder_service_
    • daemon_state_

    这里binder_watcher_是构建Binder服务的公共基础设施。binder_service_通过binder_watcher_RegisterService接口进行注册,从而向外界提供名为android.brillo.UpdateEngineService的Binder服务。

    因此客户端对IUpdateEngine接口的调用会转化为对binder_service_调用,这些操作会进一步被转发为daemon_state_私有成员update_attempter_的相应操作。

    以下是省略掉binder_watcher后服务端进程对象update_engine_daemon简单的示意图。所以daemon_state_对象对应的的类DaemonStateAndroid,是真正的Update Engine业务实现者。

    update_engine_daemon

    daemon_state_调用AddObserver()binder_service_添加到私有的service_observers_集合中去:

    daemon_state_->AddObserver(binder_service_.get())
    

    因为每个service在bind时会注册相应的callback接口,所以服务端可以通过调用每个观察者的callback接口向每个使用服务的客户端广播消息,例如升级进度。

    daemon_state_调用StartUpdater()开始Update Engine的正式工作了。

    daemon_state_->StartUpdater()
    

    阅读代码就会发现,StartUpdater()是其私有成员update_attempter_调用Init()操作,这之后的工作就基本上交给update_attempter_了。

    bool DaemonStateAndroid::StartUpdater() {
      // The DaemonState in Android is a passive daemon. It will only start applying
      // an update when instructed to do so from the exposed binder API.
      update_attempter_->Init();
      return true;
    }
    

    好了,工作转到update_attempter_以后我们就不需要再关心外层的update_engine_daemon是干什么的了。余下的重点就是对象binder_service_对应的类BinderUpdateEngineAndroidService和对象daemon_state_对应的类DaemonStateAndroid进行分析。

    2.3 BinderUpdateEngineAndroidService

    update_engine_daemon的私有指针binder_service_对应的类为BinderUpdateEngineAndroidService,该类提供了IUpdateEngine操作和IUpdateEngineCallback回调接口的实现。其对应的代码位于文件system\update_engine\binder_service_android.cc中。

    # file: system\update_engine\binder_service_android.h
    
    class BinderUpdateEngineAndroidService : public android::os::BnUpdateEngine,
                                             public ServiceObserverInterface {
     public:
      BinderUpdateEngineAndroidService(
          ServiceDelegateAndroidInterface* service_delegate);
      ~BinderUpdateEngineAndroidService() override = default;
    
      const char* ServiceName() const {
        return "android.os.UpdateEngineService";
      }
    
      // ServiceObserverInterface overrides.
      void SendStatusUpdate(int64_t last_checked_time,
                            double progress,
                            update_engine::UpdateStatus status,
                            const std::string& new_version,
                            int64_t new_size) override;
      void SendPayloadApplicationComplete(ErrorCode error_code) override;
    
      // Channel tracking changes are ignored.
      void SendChannelChangeUpdate(const std::string& tracking_channel) override {}
    
      // android::os::BnUpdateEngine overrides.
      android::binder::Status applyPayload(
          const android::String16& url,
          int64_t payload_offset,
          int64_t payload_size,
          const std::vector<android::String16>& header_kv_pairs) override;
      android::binder::Status bind(
          const android::sp<android::os::IUpdateEngineCallback>& callback,
          bool* return_value) override;
      android::binder::Status suspend() override;
      android::binder::Status resume() override;
      android::binder::Status cancel() override;
      android::binder::Status resetStatus() override;
    
     private:
      // Remove the passed |callback| from the list of registered callbacks. Called
      // whenever the callback object is destroyed.
      void UnbindCallback(android::os::IUpdateEngineCallback* callback);
    
      // List of currently bound callbacks.
      std::vector<android::sp<android::os::IUpdateEngineCallback>> callbacks_;
    
      // Cached copy of the last status update sent. Used to send an initial
      // notification when bind() is called from the client.
      int last_status_{-1};
      double last_progress_{0.0};
    
      ServiceDelegateAndroidInterface* service_delegate_;
    };
    

    从定义看,类BinderUpdateEngineAndroidService继承自BnUpdateEngine类和ServiceObserverInterface类,实际上这两个类都属于接口类,主要用于统一定义接口,然后由子类来实现。这方面,C#和Java就比较方便,可以直接定义接口类型实现接口继承。
    BinderUpdateEngineAndroidService的父类中:

    • BnUpdateEngine主要是用于实现IUpdateEngine接口;
    • ServiceObserverInterface类用于实现回调通知接口,包括:SendStatusUpdateSendPayloadApplicationCompleteSendChannelChangeUpdate

    除去以上的接口,我们可以看到BinderUpdateEngineAndroidService类还有两个重要的私有成员,IUpdateEngineCallback类型的回调对象callbacks_和Binder服务的委托对象service_delegate_

    前一节中分析过,BinderUpdateEngineAndroidService的构造函数中,直接将传递进来的参数用于构造binder_service_的委托对象service_delegate_

    一句话,BinderUpdateEngineAndroidServiceservice_delegate_就是DaemonStateAndroid类的私有成员update_attempter_。所有对service_delegate_的操作实际上转化为对update_attempter_相应方法的调用,如下:

    Status BinderUpdateEngineAndroidService::applyPayload(
        const android::String16& url,
        int64_t payload_offset,
        int64_t payload_size,
        const std::vector<android::String16>& header_kv_pairs) {
      const std::string payload_url{android::String8{url}.string()};
      std::vector<std::string> str_headers;
      str_headers.reserve(header_kv_pairs.size());
      for (const auto& header : header_kv_pairs) {
        str_headers.emplace_back(android::String8{header}.string());
      }
    
      brillo::ErrorPtr error;
      if (!service_delegate_->ApplyPayload(
              payload_url, payload_offset, payload_size, str_headers, &error)) {
        return ErrorPtrToStatus(error);
      }
      return Status::ok();
    }
    
    Status BinderUpdateEngineAndroidService::suspend() {
      brillo::ErrorPtr error;
      if (!service_delegate_->SuspendUpdate(&error))
        return ErrorPtrToStatus(error);
      return Status::ok();
    }
    
    Status BinderUpdateEngineAndroidService::resume() {
      brillo::ErrorPtr error;
      if (!service_delegate_->ResumeUpdate(&error))
        return ErrorPtrToStatus(error);
      return Status::ok();
    }
    
    Status BinderUpdateEngineAndroidService::cancel() {
      brillo::ErrorPtr error;
      if (!service_delegate_->CancelUpdate(&error))
        return ErrorPtrToStatus(error);
      return Status::ok();
    }
    
    Status BinderUpdateEngineAndroidService::resetStatus() {
      brillo::ErrorPtr error;
      if (!service_delegate_->ResetStatus(&error))
        return ErrorPtrToStatus(error);
      return Status::ok();
    }
    

    另外,在BinderUpdateEngineAndroidServicebind操作时,会使用传入的callback参数设置私有的callbacks_成员:

    Status BinderUpdateEngineAndroidService::bind(
        const android::sp<IUpdateEngineCallback>& callback, bool* return_value) {
      callbacks_.emplace_back(callback);
    
      auto binder_wrapper = android::BinderWrapper::Get();
      binder_wrapper->RegisterForDeathNotifications(
          IUpdateEngineCallback::asBinder(callback),
          base::Bind(&BinderUpdateEngineAndroidService::UnbindCallback,
                     base::Unretained(this),
                     base::Unretained(callback.get())));
    
      // Send an status update on connection (except when no update sent so far),
      // since the status update is oneway and we don't need to wait for the
      // response.
      if (last_status_ != -1)
        callback->onStatusUpdate(last_status_, last_progress_);
    
      *return_value = true;
      return Status::ok();
    }
    

    另外两个方法SendStatusUpdateSendPayloadApplicationComplete用于实现IUpdateEngineCallback接口,对这两个方法的调用,最后都是对bind操作传入的callback的调用(callback参数被保存到callback_私有成员中了):

    void BinderUpdateEngineAndroidService::SendStatusUpdate(
        int64_t /* last_checked_time */,
        double progress,
        update_engine::UpdateStatus status,
        const std::string& /* new_version  */,
        int64_t /* new_size */) {
      last_status_ = static_cast<int>(status);
      last_progress_ = progress;
      for (auto& callback : callbacks_) {
        callback->onStatusUpdate(last_status_, last_progress_);
      }
    }
    
    void BinderUpdateEngineAndroidService::SendPayloadApplicationComplete(
        ErrorCode error_code) {
      for (auto& callback : callbacks_) {
        callback->onPayloadApplicationComplete(static_cast<int>(error_code));
      }
    }
    

    总结下BinderUpdateEngineAndroidService类,主要是将外部服务接收到的请求发送到DaemonStateAndroid类的update_attempter_对象,如果有状态更新,则调用bind操作时传入的IUpdateEngineCallback类型的回调函数通知外部应用。

    2.4 DaemonStateAndroid

    UpdateEngineDaemon的私有指针binder_service_对应的类为BinderUpdateEngineAndroidService,该类提供了IUpdateEngine操作和IUpdateEngineCallback回调接口的实现。其对应的代码位于文件system\update_engine\binder_service_android.cc中。

    UpdateEngineDaemon的私有指针daemon_state_对应的类为DaemonStateAndroid,是整个Update Engine业务的实现这,看完代码会发现这个实现者还有个核心,那就是update_attempter_

    废话少数,先来看看DaemonStateAndroid类的实现文件system\update_engine\daemon_state_android.cc

    文件中总共定义了5个函数:

    • Initialize()
    • StartUpdater()
    • AddObserver(* observer) (这里*表示是observer的指针)
    • RemoveObserver(* observer) (这里*表示是observer的指针)
    • service_delegate()

    我们看看这几个函数在UpdateEngineDaemon::OnInit()中是如何使用的吧。还记得吗?再啰嗦一下吧:

    // file: system\update_engine\daemon.cc
    
    int UpdateEngineDaemon::OnInit() {
    
      ...
    
      DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
      daemon_state_.reset(daemon_state_android);
      LOG_IF(ERROR, !daemon_state_android->Initialize())
          << "Failed to initialize system state.";
    
      ...
      
      binder_service_ = new BinderUpdateEngineAndroidService{
          daemon_state_android->service_delegate()};
    
      auto binder_wrapper = android::BinderWrapper::Get();
      if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
                                           binder_service_)) {
        LOG(ERROR) << "Failed to register binder service.";
      }
    
      daemon_state_->AddObserver(binder_service_.get());
    
      daemon_state_->StartUpdater();
    }
    

    以上是经过简化后的UpdateEngineDaemon::OnInit()函数,这里只突出了DaemonStateAndroid类及其对象的活动,下面对这些活动逐个分析。

    初始化daemon_state_

      DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
      daemon_state_.reset(daemon_state_android);
    

    使用new操作构造一个DaemonStateAndroid类的对象daemon_state_android,并用这个对象初始化指针成员daemon_state_
    实际上其构造函数是一个空操作,啥也没做:

    class DaemonStateAndroid : public DaemonStateInterface {
     public:
      DaemonStateAndroid() = default;
      ~DaemonStateAndroid() override = default;
      
      ...
    
    }
    

    留意daemon_state_实际上是DaemonStateAndroid父类DaemonStateInterface的指针,这里指向了子类的对象。

    调用Initialize()

    接下来调用Initialize()进行对daemon_state_android进行初始化:

      LOG_IF(ERROR, !daemon_state_android->Initialize())
          << "Failed to initialize system state.";
    

    Initialize函数代码如下:

    bool DaemonStateAndroid::Initialize() {
      // 调用CreateBootControl创建boot_control_,连接BootControl的HAL模块
      boot_control_ = boot_control::CreateBootControl();
      if (!boot_control_) {
        LOG(WARNING) << "Unable to create BootControl instance, using stub "
                     << "instead. All update attempts will fail.";
        boot_control_.reset(new BootControlStub());
      }
    
      // 调用CreateHardware创建hardware_,连接Hardware的HAL模块
      hardware_ = hardware::CreateHardware();
      if (!hardware_) {
        LOG(ERROR) << "Error intializing the HardwareInterface.";
        return false;
      }
    
      // 检查boot mode和official build
      LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode.";
      LOG_IF(INFO, !hardware_->IsOfficialBuild()) << "Booted non-official build.";
    
      // Initialize prefs.
      base::FilePath non_volatile_path;
      // TODO(deymo): Fall back to in-memory prefs if there's no physical directory
      // available.
      if (!hardware_->GetNonVolatileDirectory(&non_volatile_path)) {
        LOG(ERROR) << "Failed to get a non-volatile directory.";
        return false;
      }
      Prefs* prefs = new Prefs();
      prefs_.reset(prefs);
      if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) {
        LOG(ERROR) << "Failed to initialize preferences.";
        return false;
      }
    
      // The CertificateChecker singleton is used by the update attempter.
      certificate_checker_.reset(
          new CertificateChecker(prefs_.get(), &openssl_wrapper_));
      certificate_checker_->Init();
    
      // Initialize the UpdateAttempter before the UpdateManager.
      update_attempter_.reset(new UpdateAttempterAndroid(
          this, prefs_.get(), boot_control_.get(), hardware_.get()));
    
      return true;
    }
    

    上面这段代码的思路比较清晰,主要操作包括:

    • 使用BootControlAndroid类的CreateBootControl()方法创建类对象并初始化boot_control_变量
    • 使用HardwareAndroid类的CreateHardware()方法创建类对象并初始化hardware_变量
    • 创建Prefs类的对象并初始化prefs_变量
    • 创建CertificateChecker类的对象,并初始化certificate_checker_变量,然后执行Init()操作
    • 使用当前DaemonStateAndroid类的对象this和前面生成的prefs_boot_control_hardware_变量来构造一个UpdateAttempterAndroid类对象用于设置update_attempter_变量

    为了避免陷进代码的细节,这里不再深入下一层代码。最后总结一下,整个Initialize()函数主要就是初始化certificate_checker_update_attempter_

    创建binder_service_对象

    创建binder_service_对象:

      binder_service_ = new BinderUpdateEngineAndroidService{
          daemon_state_android->service_delegate()};
    

    这里使用daemon_state_android->service_delegate()操作返回的对象来创建binder_service_对象。

    service_deletegate()操作到底做了什么?

    ServiceDelegateAndroidInterface* DaemonStateAndroid::service_delegate() {
      return update_attempter_.get();
    }
    

    我去,这里就是返回私有成员update_attempter_而已。

    比较有意思的是,我们来看看BinderUpdateEngineAndroidService的构造函数:

    BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService(
        ServiceDelegateAndroidInterface* service_delegate)
        : service_delegate_(service_delegate) {
    }
    

    这里干嘛了?就是将外部传入的参数service_delegate(这里实际上是update_attempter_)设置给service_delegate_成员。

    从名字service_delegate_看,这也是一个委托对象。浏览下BinderUpdateEngineAndroidService代码,关于IUpdateEngine接口(包括applyPayload, suspend, resume, cancel, resetStatus)的调用都是直接将其转发给了service_delegate_对象,这意味这所有这些对象最终都是调用update_attemper_的相应操作!!

    注册Update Engine的Binder服务

    注册Update Engine的Binder服务,并将binder_service_添加到daemon_state的观察对象中:

      if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
                                           binder_service_)) {
        LOG(ERROR) << "Failed to register binder service.";
      }
    
      daemon_state_->AddObserver(binder_service_.get());
    

    这里的AddObserver干了什么呢?不妨看看代码实现:

    void DaemonStateAndroid::AddObserver(ServiceObserverInterface* observer) {
      service_observers_.insert(observer);
    }
    

    真简单,就是把传入的observer参数(这里为binder_service_)添加到service_observers_集合中去。这里的service_observers_有什么用呢?在DaemonStateAndroid的实现代码中没有提到,我开始也是一脸懵逼,直到我看了UpdateAttempterAndroid的代码。

    先转到UpdateAttempterAndroid中,构造函数是这样的:

    UpdateAttempterAndroid::UpdateAttempterAndroid(
        DaemonStateInterface* daemon_state,
        PrefsInterface* prefs,
        BootControlInterface* boot_control,
        HardwareInterface* hardware)
        : daemon_state_(daemon_state),
          prefs_(prefs),
          boot_control_(boot_control),
          hardware_(hardware),
          processor_(new ActionProcessor()) {
      network_selector_ = network::CreateNetworkSelector();
    }
    

    仔细留意这里的daemon_state_(daemon_state),这里用传入的daemon_state初始化私有的daemon_state_成员。有两个成员函数会使用到daemon_state_成员,如下:

    void UpdateAttempterAndroid::TerminateUpdateAndNotify(ErrorCode error_code) {
      if (status_ == UpdateStatus::IDLE) {
        LOG(ERROR) << "No ongoing update, but TerminatedUpdate() called.";
        return;
      }
    
      // Reset cpu shares back to normal.
      cpu_limiter_.StopLimiter();
      download_progress_ = 0;
      actions_.clear();
      UpdateStatus new_status =
          (error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT
                                             : UpdateStatus::IDLE);
      SetStatusAndNotify(new_status);
      ongoing_update_ = false;
    
      for (auto observer : daemon_state_->service_observers())
        observer->SendPayloadApplicationComplete(error_code);
    }
    
    void UpdateAttempterAndroid::SetStatusAndNotify(UpdateStatus status) {
      status_ = status;
      for (auto observer : daemon_state_->service_observers()) {
        observer->SendStatusUpdate(
            0, download_progress_, status_, "", install_plan_.payload_size);
      }
      last_notify_time_ = TimeTicks::Now();
    }
    

    这两个函数分别在Update结束和状态更新时对service_observers集合的成员逐个调用SendPayloadApplicationCompleteSendStatusUpdate,目的是向外界发送通知状态更新。

    3. Update Engine的回调通知是如何实现的?

    前面的第2.4节说道,对daemon_state_service_observers集合成员逐个调用SendPayloadApplicationCompleteSendStatusUpdate,外接就能接收到通知。这是如何实现的呢?

    让我们先回到service_observers所属的类BinderUpdateEngineAndroidService中代码实现:

    void BinderUpdateEngineAndroidService::SendStatusUpdate(
        int64_t /* last_checked_time */,
        double progress,
        update_engine::UpdateStatus status,
        const std::string& /* new_version  */,
        int64_t /* new_size */) {
      last_status_ = static_cast<int>(status);
      last_progress_ = progress;
      for (auto& callback : callbacks_) {
        callback->onStatusUpdate(last_status_, last_progress_);
      }
    }
    
    void BinderUpdateEngineAndroidService::SendPayloadApplicationComplete(
        ErrorCode error_code) {
      for (auto& callback : callbacks_) {
        callback->onPayloadApplicationComplete(static_cast<int>(error_code));
      }
    }
    

    这里的两个方法SendStatusUpdateSendPayloadApplicationComplete,实际上是调用callbacks_onStatusUpdateonPayloadApplicationComplete

    callbacks_IUpdateEngineCallback的集合,是在bind操作是作为参数传入的:

    Status BinderUpdateEngineAndroidService::bind(
        const android::sp<IUpdateEngineCallback>& callback, bool* return_value) {
      // 将传入的参数callback保存到callbacks_中
      callbacks_.emplace_back(callback);
    
      auto binder_wrapper = android::BinderWrapper::Get();
      binder_wrapper->RegisterForDeathNotifications(
          IUpdateEngineCallback::asBinder(callback),
          base::Bind(&BinderUpdateEngineAndroidService::UnbindCallback,
                     base::Unretained(this),
                     base::Unretained(callback.get())));
    
      // Send an status update on connection (except when no update sent so far),
      // since the status update is oneway and we don't need to wait for the
      // response.
      if (last_status_ != -1)
        callback->onStatusUpdate(last_status_, last_progress_);
    
      *return_value = true;
      return Status::ok();
    }
    

    代码扯得有点远了,云里雾里的,我们以Android自带的demo应用update_engine_client_android看看到底是怎么回事。

    在文件update_engine_client_android.cc中,以BnUpdateEngineCallback为基类定义了一个UECallback类,这个类只有两个函数onStatusUpdateonPayloadApplicationComplete

      class UECallback : public android::os::BnUpdateEngineCallback {
       public:
        explicit UECallback(UpdateEngineClientAndroid* client) : client_(client) {}
    
        // android::os::BnUpdateEngineCallback overrides.
        Status onStatusUpdate(int status_code, float progress) override;
        Status onPayloadApplicationComplete(int error_code) override;
    
       private:
        UpdateEngineClientAndroid* client_;
      };
    

    函数的详细实现如下:

    Status UpdateEngineClientAndroid::UECallback::onStatusUpdate(
        int status_code, float progress) {
      update_engine::UpdateStatus status =
          static_cast<update_engine::UpdateStatus>(status_code);
      LOG(INFO) << "onStatusUpdate(" << UpdateStatusToString(status) << " ("
                << status_code << "), " << progress << ")";
      return Status::ok();
    }
    
    Status UpdateEngineClientAndroid::UECallback::onPayloadApplicationComplete(
        int error_code) {
      ErrorCode code = static_cast<ErrorCode>(error_code);
      LOG(INFO) << "onPayloadApplicationComplete(" << utils::ErrorCodeToString(code)
                << " (" << error_code << "))";
      client_->ExitWhenIdle(code == ErrorCode::kSuccess ? EX_OK : 1);
      return Status::ok();
    }
    

    这两个函数本身比较简单,通过打印的方式输出状态信息。

    然后在OnInit()函数的bind操作时生成回调对象callback_并向服务端注册进行注册,如下:

    int UpdateEngineClientAndroid::OnInit() {
      ...
    
      android::status_t status = android::getService(
          android::String16("android.os.UpdateEngineService"), &service_);
      if (status != android::OK) {
        LOG(ERROR) << "Failed to get IUpdateEngine binder from service manager: "
                   << Status::fromStatusT(status).toString8();
        return ExitWhenIdle(1);
      }
    
      ...
    
      if (FLAGS_follow) {
        // 创建包含onStatusUpdate和onPayloadApplicationComplete实现的回调对象callback_
        // Register a callback object with the service.
        callback_ = new UECallback(this);
        bool bound;
        // 调用bind,向"android.os.UpdateEngineService"服务注册回调对象callback_
        if (!service_->bind(callback_, &bound).isOk() || !bound) {
          LOG(ERROR) << "Failed to bind() the UpdateEngine daemon.";
          return 1;
        }
        keep_running = true;
      }
    
      ...
    
      return EX_OK;
    }
    

    在执行bind操作时,服务端函数BinderUpdateEngineAndroidService::bind(...)会接收到传入的callback_,并被保存在binder_service_callbacks_中。相应地,服务端binder_service_对象中的callbacks_就是这里客户端定义的回调函数类对应的模板对象。

    所以服务端callback->onStatusUpdatecallback->onPayloadApplicationComplete分别是客户端实现的UECallback::onStatusUpdateUECallback::onPayloadApplicationComplete函数。

    4. 总结

    总结一下:

    服务端进程(代码main.cc)在main函数中先解析命令行参数并进行简单初始化,随后创建update_engine_daemon对象,并调用对象的Run()方法进入服务等待状态。

    Run()中进入主循环前,通过OnInit()初始化生成两个业务对象binder_service_daemon_state_,前者负责binder服务对外的工作,后者则负责后台的实际业务。

    binder_service_在客户端调用bind操作时会保存客户端注册的回调函数,从而在适当的时候通过回调函数告知客户端升级的状态信息;同时binder_service_接收到客户端的服务请求后,将其交给daemon_state_的成员update_attempter_去完成,所以update_attempter_才是Update Engine服务端业务的核心。

    可以看到,目前基本上所有调用最后都会转到update_attempter_中,代码分析都在涉及到update_attempter_的操作时停止。所以update_attempter_是Update Engine服务端的核心对象,代码比较复杂,我们另外开篇分析。

    5. 联系和福利

    • 个人微信公众号“洛奇看世界”,一个大龄码农的救赎之路。

      • 公众号回复关键词“Android电子书”,获取超过150本Android相关的电子书和文档。电子书包含了Android开发相关的方方面面,从此你再也不需要到处找Android开发的电子书了。
      • 公众号回复关键词“个人微信”,获取个人微信联系方式。我组建了一个Android OTA的讨论组,联系我,说明Android OTA,拉你进组一起讨论。

      image

    展开全文
  • Android Update 是一个基于配置文件的自动更新方案,可以通过一张在线配置表实现应用自动更新,强制更新
  • 最近在搞 Android A/B 分区 OTA 升级,开始以为挺简单的一个事,没想到折腾了几天,还好终于搞定!代码调用其实就那么点,但是参数以及权限之类很关键,搬砖过来走不通,我就是卡了2天,一些莫名的错误,没法定位。...
  • 最初计划《Android Update Engine分析》系列的时候,大概有十二篇左右,后来分析 payload 数据生成时觉得要花很多时间去分析整理和消化,就放了一放,结果这一放,3 年就过去了,时间真是过得很快。中间不少朋友加我...

    Android Update Engine分析(九) delta_generator 工具的 6 种操作

    delta_generator 工具的 6 种操作

    题图: 用关键词"Android Upgrade"谷歌搜索了一下图片,没有找到比较合适的,刚好在结果中看到这个图片,你知道的,有美女~
    来源: https://www.chinatimes.com/realtimenews/20210518003354-260412?chdtv

    相关文章:

    A/B System系列

    Update Engine系列

    最初计划《Android Update Engine分析》系列的时候,大概有十二篇左右,后来分析 payload 数据生成时觉得要花很多时间去分析整理和消化,就放了一放,结果这一放,3 年就过去了,时间真是过得很快。中间不少朋友加我微信询问 delta 数据生成的问题,也只能不了了之。

    最近决定继续这一系列的写作。计划中的后续内容包括 payload 生成的所有细节,以及 update engine 从 Android N 开始的演化,动态逻辑分区等。

    本篇内容比较短,基本上是以前写好了但没有发出来的笔记,主要从大方向看 delta_generator 的代码,分析其 6 种操作,后面分 6 篇对每一个操作的源码进行详细分析。

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

    为了方便阅读,以下为本篇目录,只想了解特定内容请点击相应链接跳转:

    1. 制作升级数据的步骤

    上一篇《Android Update Engine分析(八)升级包制作脚本分析》中分析了ota_from_target_files脚本,其将升级包的制作分解为以下步骤:

    1. 准备制作升级包需要的key
      • 包括对升级数据(payload 和 metadata)签名的payload_signer和升级包自身签名的package_key
    2. 准备升级包的 metadata
      • 这里的 metadata 指升级包 metadata 文件中的数据,而非payload.bin中的metadata数据
    3. 制作升级包
      • 3.1 生成 payload 文件
      • 3.2 提取 payload 数据和 metadata 数据的哈希值
      • 3.3 对 payload 哈希和 metadata 哈希数据使用 payload_signer 进行签名
      • 3.4 提取 payload 文件的 properties 数据并更新
      • 3.5 将 payload 文件, properties 文件, metadata 文件以及 care_map 文件写入升级包文件
    4. 使用 package_key 对升级包文件进行签名

    升级所使用 payload.bin 数据的生成和处理集中在第3步,其中步骤 3.1~3.4 通过调用 openssl 命令或 brillo_update_payload 脚本进行处理,具体如下:

    #
    # 3.1 生成payload文件
    #
    # (new.zip + old.zip --> payload_file.bin)
    brillo_update_payload generate --payload /tmp/payload_file.bin \
                                   --target_image dist/new.zip \
                                   --source_image dist/old.zip
    
    #
    # 3.2 生成payload和metadata数据的哈希值
    #
    # (payload_file.bin --> metadata_sig_file.bin, payload_sig_file.bin)
    brillo_update_payload hash --unsigned_payload /tmp/payload_file.bin \
                               --signature_size 256 \
                               --metadata_hash_file /tmp/metadata_sig_file.bin \
                               --payload_hash_file /tmp/payload_sig_file.bin
    
    #
    # 3.3 对payload哈希和metadata哈希进行签名并写入payload文件
    #
    # 准备rsa key, 将testkey从DER格式转换为PEM格式
    # (testkey.pk8 --> testkey.pem.key)
    openssl pkcs8 -in build/target/product/security/testkey.pk8 \
                  -inform DER -nocrypt \
                  -out /tmp/testkey.pem.key
    
    # 3.3a 对payload哈希进行签名
    # (payload_sig_file.bin --testkey.pem.key--> signed_payload_sig_file.bin)
    openssl pkeyutl -sign -inkey /tmp/testkey.pem.key \
                          -pkeyopt digest:sha256 \
                          -in /tmp/payload_sig_file.bin \
                          -out /tmp/signed_payload_sig_file.bin
    
    # 3.3b 对metadata哈希进行签名
    # (metadata_sig_file.bin --testkey.pem.key--> signed_metadata_sig_file.bin)
    openssl pkeyutl -sign -inkey /tmp/testkey.pem.key \
                          -pkeyopt digest:sha256 \
                          -in /tmp/metadata_sig_file.bin \
                          -out /tmp/signed_metadata_sig_file.bin
    
    # 3.3c 将payload签名和metadata签名写回payload文件
    # (signed_metadata_sig_file.bin, signed_payload_sig_file.bin --> signed_payload_file.bin)
    brillo_update_payload sign --unsigned_payload /tmp/payload_file.bin \
                               --payload /tmp/signed_payload_file.bin \
                               --signature_size 256 \
                               --metadata_signature_file /tmp/signed_metadata_sig_file.bin \
                               --payload_signature_file /tmp/signed_payload_sig_file.bin
    
    #
    # 3.4 提取payload文件的properties数据
    #
    # (signed_payload_file.bin --> payload_properties_file.txt)
    brillo_update_payload properties --payload /tmp/signed_payload_file.bin \
                                     --properties_file /tmp/payload_properties_file.txt
    

    上面的步骤中,3.1, 3.2, 3.3c, 3.4 是对brillo_update_payload脚本的调用。后者将操作命令转发给delta_generator去执行。最终,delta_generator收到的命令如下:

    #
    # 3.1 生成payload文件
    #
    # (new.zip[boot.img+system.img] + old.zip[boot.img+system.img] --> payload_file.bin)
    delta_generator -out_file=dist/payload.bin \
                    -partition_names=boot:system \
                    -new_partitions=/tmp/boot.img.oiHmvn:/tmp/system.raw.ZkArkk \
                    -old_partitions=/tmp/boot.img.cXD4Dt:/tmp/system.raw.IlRpgW \
                    --minor_version=3 --major_version=2
    #
    # 3.2 生成payload和metadata数据的哈希值
    #
    # (payload_file.bin --> metadata_sig_file.bin, payload_sig_file.bin)
    delta_generator -in_file=/tmp/payload_file.bin \
                    -signature_size=256 \
                    -out_hash_file=/tmp/payload_sig_file.bin \
                    -out_metadata_hash_file=/tmp/metadata_sig_file.bin
    #
    # 3.3 对payload哈希和metadata哈希进行签名并写入payload文件
    #
    # 3.3a 对payload哈希进行签名
    # ...
    # 3.3b 对metadata哈希进行签名
    # ...
    # 3.3c 将payload签名和metadata签名写回payload文件
    # (signed_metadata_sig_file.bin, signed_payload_sig_file.bin --> signed_payload_file.bin)
    delta_generator -in_file=/tmp/payload_file.bin \
                    -signature_size=256 \
                    -signature_file=/tmp/signed_payload_file.bin \
                    -metadata_signature_file=/tmp/signed_metadata_sig_file.bin \
                    -out_file=/tmp/signed_payload_file.bin
    #
    # 3.4 提取payload文件的properties数据
    #
    # (signed_payload_file.bin --> payload_properties_file.txt)
    delta_generator -in_file=/tmp/signed_payload_file.bin \
                    -properties_file=/tmp/payload_properties_file.txt
    

    这4条命令有以下几个特点:

    • 所有命令都没有子命令来对应不同的操作
    • 3.1中生成 payload 文件的命令没有 -in_file 选项
    • 3.2中生成 payload 和 metadata 数据哈希的命令带有-out_hash_file-out_metadata_hash_file选项
    • 3.3c中将 payload 签名和 metadata 签名写回 payload 文件的命令带有-signature_file-metadata_signature_file选项
    • 3.4中提取 payload 文件 properties 数据的命令带有-properties_file选项

    一个应用支持几个不同操作时,按照常规思路,通常就会有4个子命令来执行每一个操作,命令格式就类似于:

    command subcommand [arg1] [arg2]...
    

    例如前面的 brillo_update_payload 脚本有generatesignhashproperties四个子命令,但这里的 delta_generator 没有子命令。

    所以,delta_generator要执行不同的操作,需要通过判断不同的输入选项来决定,接下来对delta_generator进行源码分析,看看是否真如猜想的这样。

    2. delta_generator源码分析

    第一篇《Android Update Engine分析(一)Makefile》中对delta_generator进行过分析,其对库和代码文件的依赖如下:

    # 库文件依赖
    delta_generator (host)
      --> libpayload_generator
        --> libpayload_consumer
          --> update_metadata-protos
    
    # 代码文件依赖
    delta_generator (EXECUTABLES)
      --> payload_generator/generate_delta_main.cc
    

    所以这里从delta_generator的代码generate_delta_main.cc入手进行分析。

    2.1 Main()函数

    generate_delta_main.ccmain()函数将所有处理交由Main()函数。

    Main()函数大概有240+行,主要分为几个部分:

    1. 参数处理
    2. 根据传入的参数执行 5 种操作
    3. 执行第 6 种操作,生成 payload.bin 数据

    2.2 命令行参数的解析和初始化

    在函数的一开始,调用DEFINE_string宏定义了一大堆字符串参数,然后将这些参数交由brillo::FlagHelper 进行解析处理。

    这里还是简单说下处理过程,参数处理的类 brillo::FlagHelper 定义在 external/libbrillo/brillo/flag_helper.h 文件中。

    int Main(int argc, char** argv) {
      DEFINE_string(old_image, "", "Path to the old rootfs");
      DEFINE_string(new_image, "", "Path to the new rootfs");
      ...
      DEFINE_string(new_postinstall_config_file, "",
                    "A config file specifying postinstall related metadata. "
                    "Only allowed in major version 2 or newer.");
    
      brillo::FlagHelper::Init(argc, argv,
          "Generates a payload to provide to ChromeOS' update_engine.\n\n"
          "This tool can create full payloads and also delta payloads if the src\n"
          "image is provided. It also provides debugging options to apply, sign\n"
          "and verify payloads.");
    
      ...
    }
    
    1. 在 Main 函数中,先通过 DEFINE_string(old_image, "", "Path to the old rootfs"); 这样的方式定义好需要的参数。
    2. 再通过调用brillo::FlagHelper::Init()函数,将函数参数解析到一大堆FLAGS_xxx变量中。

    例如,"old_image" 选项,经过解析后,会将参数值存放到字符串变量(std::string类型) FLAGS_old_image 中进行后续处理,其余类似。

    参数很多,这里不关注参数的类型,默认值和注释等细节,后面使用参数的时候再回来看,所以这段代码简单略过。

    关于DEFINE_string/int32/uint64这一类宏和brillo::FlagHelper是如何工作的,请参考external/libbrillo/brillo/flag_helper.h的注释和源码。

    在调用brillo::FlagHelper::Init()处理完main函数的参数后,开始一些其他的初始化工作。

      ...
      Terminator::Init();
    
      logging::LoggingSettings log_settings;
      log_settings.log_file     = "delta_generator.log";
      log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
      log_settings.lock_log     = logging::LOCK_LOG_FILE;
      log_settings.delete_old   = logging::APPEND_TO_OLD_LOG_FILE;
    
      logging::InitLogging(log_settings);
    
      // Initialize the Xz compressor.
      XzCompressInit();
    
      vector<int> signature_sizes;
      ParseSignatureSizes(FLAGS_signature_size, &signature_sizes);
      ...
    

    注意ParseSignatureSizes(FLAGS_signature_size, &signature_sizes)调用,传入的FLAGS_signature_size字符串可能包含多个用":"分隔的数值,如“2048:2048:4096”,这里将其分割存放到signature_sizes向量中。

    2.3 根据传入参数判断并执行 6 种操作

    在结束命令行参数的解析和初始化后,代码根据传入参数进行判断并执行响应操作:

      ...
      //
      // 1. 生成payload和metadata数据的哈希值
      //
      // 参数 out_hash_file 和 out_metadata_hash_file 不为空的情况,
      // 调用CalculateHashForSigning()函数生成payload和metadata数据的哈希
      //
      if (!FLAGS_out_hash_file.empty() || !FLAGS_out_metadata_hash_file.empty()) {
        CHECK(FLAGS_out_metadata_size_file.empty());
        CalculateHashForSigning(signature_sizes, FLAGS_out_hash_file,
                                FLAGS_out_metadata_hash_file, FLAGS_in_file);
        return 0;
      }
      //
      // 2. 将 payload 签名和 metadata 签名写入 payload 文件
      //
      // 参数 signature_file 不为空的情况,
      // 调用 SignPayload() 函数将 payload 签名和 metadata 签名写入 payload 文件
      //
      if (!FLAGS_signature_file.empty()) {
        SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file,
                    FLAGS_metadata_signature_file, FLAGS_out_metadata_size_file);
        return 0;
      }
      //
      // 3. 带 public_key 参数的情况,验证 payload 的 signature
      // 从判断的条件看,验证 payload 的 signature 除了需要 public_key 外,还需要 in_file 参数
      if (!FLAGS_public_key.empty()) {
        LOG_IF(WARNING, FLAGS_public_key_version != -1)
            << "--public_key_version is deprecated and ignored.";
        VerifySignedPayload(FLAGS_in_file, FLAGS_public_key);
        return 0;
      }
      //
      // 4. 提取 payload 文件的 properties 数据
      //
      // 参数 properties_file 不为空的情况,
      // 调用 ExtractProperties()提取 payload 文件的 properties 数据
      //
      if (!FLAGS_properties_file.empty()) {
        return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1;
      }
      //
      // 5. 对 old image 打 delta 补丁
      //
      // 这里的用途说起来有点别扭,
      // 从 in_file 参数的注释看,似乎在调试时可以使用 in_file 来 apply delta over old_image
      // 个人理解是相当于在 old_image 的基础上应用 in_file 文件,最后得到 new_image
      //
      if (!FLAGS_in_file.empty()) {
        ApplyDelta(FLAGS_in_file, FLAGS_old_kernel, FLAGS_old_image,
                   FLAGS_prefs_dir);
        return 0;
      }
      ...
    

    这对代码对参数进行判断,得到5种操作,分别是:

    1. 生成 payload 和 metadata 数据的哈希值
    2. 将 payload 签名和 metadata 签名写入 payload 文件
    3. 带 public_key 参数的情况,验证 payload 的 signature
    4. 提取 payload 文件的 properties数据
    5. 对 old image 打 delta 补丁

    由于操作内容太多,后面会专门对每一个操作进行详细的分析说明。

    仔细看上面的这 5 个操作,都是围绕 payload 数据进行(如生成哈希,计算签名等),唯独没有生成 payload 数据。因此,Main 函数剩余的部分就是用于生成 payload 数据了,即第 6 种操作。

    3. delta_generator 工具的 6 种操作总结

    所以,总结一下,可执行文件 delta_generator 的入口位于 payload_generator/generate_delta_main.cc

    从大方向看,delta_generator 一共提供了 6 种操作,分别是:

    1. 生成 payload 和 metadata 数据的哈希值
    2. 将 payload 签名和 metadata 签名写入 payload 文件
    3. 带 public_key 参数的情况,验证 payload 的 signature
    4. 提取 payload 文件的 properties数据
    5. 对 old image 打 delta 补丁
    6. 生成 payload 数据

    后面会专门分篇对每部分进行详细说明。

    4. 其它

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

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

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

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

    收钱码

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

    公众号

    展开全文
  • AndroidUpdate.json

    2021-03-27 14:34:04
    Android应用版本更新服务端配置文件
  • android_system_update_engine

    2021-03-19 18:20:31
    android_system_update_engine
  • android-update.zip_刷机包制作教程
  • 上两篇详细分析了 metadata 和 payload 的哈希如何生成,如何签名,如何更新到 payload 文件中,本篇继续分析代码是如何验证 payload 签名的,包括代码中验证签名的流程和签名在命令行的手动验证。...
  • 生成 payload 数据是 `delta_generator` 工具最主要的功能,当我准备重新更新这一些列文章时,一开始就想写这个的,不过因为太久没看,不记得 payload 细节了,更无从从细节上分析。所以就先写一些简单的操作,等...
  • Android Update Engine分析(一)Makefile

    万次阅读 多人点赞 2017-08-28 16:28:21
    写完《Android AB System OTA分析》系列后打算一口气将Update Engine也写了的,但一直由于各种借口,最终没有完成。后来6月份的时候陆陆续续读了Update Engine部分代码,记了点笔记,本打算等彻底读完再分享的,但...
  • Android11.0 V-A/B无缝OTA升级update_engine

    千次阅读 热门讨论 2021-06-16 15:31:34
    前言 V-AB 升级方案其实早在 7.0 就...相较于 Android 10.0,Android 11.0 的 Recovery 分区与 cache 分区已删除。 AB 方案介绍 AB 方案就是双分区双系统,每个镜像都是双份,分别对应两个分区。 V-AB 方案介绍 V-AB
  • Android Update Engine 分析(三)客户端进程 Android Update Engine 分析(四)服务端进程 Android Update Engine 分析(五)服务端核心之Action机制 Android Update Engine 分析(六)服务端核心之Action详解 ...
  • 最近在搞 Android A/B 分区 OTA 升级,手机方案公司出来之后就好久开始以为挺简单的一个事,没想到折腾了几天,还给MTK提eService了也没有回复,大公司就这个尿性,还好终于搞定! 最开始以为还是走 RecoverySystem....
  • 当AB系统升级时,有两种方式来调用updateengine,来实现升级,一种方法是直接执行shell命令,调用 update_engine_client,带参数来实现升级,另一种方式是应用层直接调用UpdateEngine的applyPayload方法来升级。...
  • 我正在尝试使用rawQuery和execsql方法来操作我的数据库,而不是.update,.insert等.我正在尝试使用以下代码执行更新:db.execsql("UPDATE configuration " +"SET number_fields = " + number_fields + ","+ "frequency...
  • AppUpdate,一个 Android 版本更新的库、A Android version update library
  • android--update--apk

    2017-06-09 18:02:52
    android 升级 6.0 7.0
  • Android Update Engine分析(二)Protobuf和AIDL文件 技术文章直入主题,展示结论,容易让人知其然,不知其所以然。 我个人更喜欢在文章中展示如何阅读代码,逐步分析解决问题的思路和过程。这样的思考比知道...
  • Android update api

    2014-08-26 11:00:00
    修改公共api后,需要  make update-api  比较framework/base/api 下的current.xml跟原始x.xml(比如2.2为8.xml, 2.3.3为10.xml),同时修改x.xml  然后make
  • android版本自动升级框架 先看效果 使用 package com.ydl.versionupdate ; import android.app.Activity ; import android.content.Context ; import android.os.Bundle ; import android.view.Menu ; import ...
  • Android Update Engine分析(八)升级包制作脚本分析 本系列到现在为止共有七篇,分别如下: Android Update Engine分析(一)Makefile Android Update Engine分析(二)Protobuf和AIDL文件 Android Update Engine...
  • Android 调用update

    2012-07-13 19:09:51
    下文详细说明介绍Android ...然后等到苏醒后在Handler中就会让系统区绘制上次修改过的二维方块地图,然后再次Android 调用update,如此循环反复,生生不息),才使得游戏不断被推进,因此,比做“引擎“不为过。 既然u
  • Android代码-AppUpdate

    2019-08-05 18:00:08
    查看版本中的Log只需要过滤AppUpdate开头的Tag 重点: 如果没有设置downloadPath则默认为getExternalCacheDir()目录,同时不会申请[存储]权限! 目录 效果图 功能介绍 DownloadManager UpdateConfiguration 使用...
  • 清除缓存, 打开如下操作 File>InvalidateCaches/Restart
  • I believe that you are asking how to INSERT new rows or UPDATE your existing rows in one step. While that is possible in a single raw SQL as discussed in this answer, I found that it easier to do this...
  • android update project -p .

    千次阅读 2015-04-24 21:36:07
  • 安卓Android源码——auto-update-service-develop.zip

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 185,952
精华内容 74,380
关键字:

安卓update