精华内容
下载资源
问答
  • 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

    本篇开始分析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 Engine分析(八)升级包制作脚本分析 本系列到现在为止共有七篇,分别如下: Android Update Engine分析(一)Makefile Android Update Engine分析(二)Protobuf和AIDL文件 Android Update Engine...

    Android Update Engine分析(八)升级包制作脚本分析

    本系列到现在为止共有七篇,分别如下:

    前面几篇分别分析了Update Engine的Makefile,客户端demo进程和服务端,基本了解了Update Engine关于升级是如何运作的。而在升级之前,需要先制作升级包。升级包的制作和使用升级包进行升级是两个相反的过程,理解了升级包数据是如何产生的,反过来有利于我们理解Update Engine升级过程中的一些行为。所以我们将从差分包的制作命令开始,跟踪分析整个差分升级包的制作流程,看看升级数据到底是如何生成的。

    一直以来,ota_from_target_files脚本负责Android系统升级包的制作,不论是传统的升级方式还是A/B系统的升级方式。

    在A/B系统中,ota_from_target_files会将升级包制作的流程分解,并将payload文件制作和更新的操作转交给brillo_update_payload脚本处理,而后者会进一步调用可执行文件delta_generator去生成或更新用于升级的payload数据。因此,整个升级包的制作分为三个层次,顶层为ota_from_target_files,接下来是brillo_update_payload,最底层是delta_generator。为避免文章太长,本文主要分析脚本ota_from_target_filesbrillo_update_payload的行为,下一篇将对delta_generator的代码进行详细分析。

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

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

    1. 如何制作升级包?

    《Android A/B System OTA分析(四)系统的启动和升级》一文中提到了全量升级包和增量升级包的制作方式,主要有两步:

    1. 编译系统
    2. 制作升级包

    如果是做全量升级包,则只需要编译一次系统,在此系统的基础上制作升级文件;

    如果是做增量升级包,则需要先编译一遍系统保存起来,修改代码,再编译一遍系统。然后工具基于这里的新旧两个系统制作升级包。

    以下是我在该篇文章中使用的命令:

    #
    # 编译系统
    #
    
    $ source build/envsetup.sh
    $ lunch bcm7252ssffdr4-userdebug
    $ mkdir dist_output
    $ make -j32 dist DIST_DIR=dist_output
      [...]
    $ ls -lh dist-output/*target_files*
    -rw-r--r-- 1 ygu users 566M May 21 14:49 bcm7252ssffdr4-target_files-eng.ygu.zip
    
    #
    # 制作全量升级包
    #
    
    $ ./build/tools/releasetools/ota_from_target_files \
        dist-output/bcm7252ssffdr4-target_files-eng.ygu.zip \
        full-ota.zip
    
    # 
    # 制作增量升级包
    #
    
    $./build/tools/releasetools/ota_from_target_files \
        -i dist-output/bcm7252ssffdr4-target_files-eng.ygu.zip \
        dist-output-new/bcm7252ssffdr4-target_files-eng.ygu.zip \
        incremental-ota.zip
    

    2. 脚本ota_from_target_files

    假设系统修改前后编译生成的ota包分别叫做old.zip和new.zip,生成的差分升级包叫做update.zip。

    差分包制作脚本ota_from_target_files位于目录:build/tools/releasetools

    本篇以差分升级包的制作为例,执行命令:

    android-7.1.1_r23$ ./build/tools/releasetools/ota_from_target_files \
        -i dist/old.zip dist/new.zip \
        dist/update.zip
    

    然后跟踪代码执行路径来分析差分包是如何生成的。

    2.1 脚本入口

    差分包制作命令的入口点在ota_from_target_files.py脚本的if __name__ == '__main__'语句:

    if __name__ == '__main__':
      try:
        common.CloseInheritedPipes()
    
        # main函数接收到的参数 sys.argv: [
        # './build/tools/releasetools/ota_from_target_files', 
        # '-i', 
        # 'dist/old.zip', 
        # 'dist/new.zip', 
        # 'dist/update.zip'
        # ]
        main(sys.argv[1:])
      except common.ExternalError as e:
        print
        print "   ERROR: %s" % (e,)
        print
        sys.exit(1)
      finally:
        common.Cleanup()
    

    这里会将除脚本名称外的参数传递到函数main(sys.argv[1:])去执行。

    2.2 main函数

    main函数接收命令行传递过来的参数,并进行处理:

    def main(argv):
    
      # 这里定义了option参数的处理函数option_handler, 略过函数细节
      def option_handler(o, a):
        ...
        return True
    
      # 对传入的参数argv调用common.ParseOptions进行解析
      # 解析后的结果:
      # args = ['dist/new.zip', 'dist/update.zip']
      # OPTIONS.incremental_source = dist/old.zip
      args = common.ParseOptions(argv, __doc__,
                                 extra_opts="b:k:i:d:wne:t:a:2o:",
                                 extra_long_opts=[
                                     "board_config=",
                                     "package_key=",
                                     "incremental_from=",
                                     "full_radio",
                                     "full_bootloader",
                                     "wipe_user_data",
                                     "no_prereq",
                                     "downgrade",
                                     "extra_script=",
                                     "worker_threads=",
                                     "aslr_mode=",
                                     "two_step",
                                     "no_signing",
                                     "block",
                                     "binary=",
                                     "oem_settings=",
                                     "oem_no_mount",
                                     "verify",
                                     "no_fallback_to_full",
                                     "stash_threshold=",
                                     "gen_verify",
                                     "log_diff=",
                                     "payload_signer=",
                                     "payload_signer_args=",
                                 ], extra_option_handler=option_handler)
    
      if len(args) != 2:
        common.Usage(__doc__)
        sys.exit(1)
    
      # 没有指定downgrade参数,略过
      if OPTIONS.downgrade:
        # Sanity check to enforce a data wipe.
        if not OPTIONS.wipe_user_data:
          raise ValueError("Cannot downgrade without a data wipe")
    
        # We should only allow downgrading incrementals (as opposed to full).
        # Otherwise the device may go back from arbitrary build with this full
        # OTA package.
        if OPTIONS.incremental_source is None:
          raise ValueError("Cannot generate downgradable full OTAs - consider"
                           "using --omit_prereq?")
    
      # 加载args[0](即'dist/new.zip')中指定词典文件的键值(key/value)对信息, 
      # LoadInfoDict('dist/new.zip')函数中读取的文件包括:
      # 1. META/misc_info.txt
      # 2. BOOT/RAMDISK/etc/recovery.fstab
      # 3. SYSTEM/build.prop
      # 
      # Load the dict file from the zip directly to have a peek at the OTA type.
      # For packages using A/B update, unzipping is not needed.
      input_zip = zipfile.ZipFile(args[0], "r")
      OPTIONS.info_dict = common.LoadInfoDict(input_zip)
      common.ZipClose(input_zip)
    
      # 检查是否A/B系统, 读取到的 info_dict['ab_update'] = (str) true
      ab_update = OPTIONS.info_dict.get("ab_update") == "true"
    
      # 当前的A/B系统走这里
      if ab_update:
        # 前面解析参数时得到:OPTIONS.incremental_source = dist/old.zip
        if OPTIONS.incremental_source is not None:
          # 从'dist/new.zip'加载的key/value信息作为target_info_dict
          OPTIONS.target_info_dict = OPTIONS.info_dict
          
          # 从'dist/old.zip'加载的key/value信息作为source_info_dict
          source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
          OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
          common.ZipClose(source_zip)
    
        # 如果在命令行中添加了'-v'选项,则这里打印获取到的key/value信息
        if OPTIONS.verbose:
          print "--- target info ---"
          common.DumpInfoDict(OPTIONS.info_dict)
    
          if OPTIONS.incremental_source is not None:
            print "--- source info ---"
            common.DumpInfoDict(OPTIONS.source_info_dict)
    
        # 所有生成A/B系统payload.bin的操作都由这里的调用搞定
        # target_file='dist/new.zip'
        # output_file='dist/update.zip'
        # source_file='dist/old.zip'
        WriteABOTAPackageWithBrilloScript(
            target_file=args[0],
            output_file=args[1],
            source_file=OPTIONS.incremental_source)
    
        print "done."
        return
    

    main()函数的操作比较简单,先解析命令行参数,然后根据参数读取target包的相关词典文件信息,提取key/value键值对。如果提取到的key/value键值对中,“ab_update"对应的信息为"true”,则说明当前是基于A/B系统制作升级包。如果当前是制作差分包,则还需要提取source包的key/value键值对。

    关于到底提取了那些key/value信息,可以在命令行添加’-v’选项,这样在执行时会打印所有target和source的键值对:

    android-7.1.1_r23$ ./build/tools/releasetools/ota_from_target_files \ 
      -v -i dist/old.zip dist/new.zip \ 
      dist/update.zip
    

    完成键值对的提取后,调用WriteABOTAPackageWithBrilloScript()函数制作升级包,所以剩余的工作都在这个函数里。

    2.3 WriteABOTAPackageWithBrilloScript函数

    def WriteABOTAPackageWithBrilloScript(target_file, output_file,
                                          source_file=None):
      """Generate an Android OTA package that has A/B update payload."""
    
      # 差分包制作命令'ota_from_target_files -i dist/old.zip dist/new.zip dist/update.zip'的传入参数:
      # target_file='dist/new.zip'
      # output_file='dist/update.zip'
      # source_file='dist/old.zip'
    
      #
      # 设置 OPTIONS.package_key
      #
      # OPTIONS.package_key选项从命令行参数'-k'或'--package_key'解析得到,所以默认情况下为None
      # 在META/misc_info.txt中,'default_system_dev_certificate=build/target/product/security/testkey'
      #
      # Setup signing keys.
      if OPTIONS.package_key is None:
        OPTIONS.package_key = OPTIONS.info_dict.get(
            "default_system_dev_certificate",
            "build/target/product/security/testkey")
    
      #
      # 设置 OPTIONS.payload_signer
      #
      # OPTIONS.payload_signer选项从命令行参数'--payload_signer'解析得到,所以默认情况下为None
      # 如果没有设置OPTIONS.payload_signer, 这里构造openssl命令基于package_key生成临时的rsa_key:
      # 'openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt -out /tmp/key-oQvVbH.key'
      #
      # A/B updater expects a signing key in RSA format. Gets the key ready for
      # later use in step 3, unless a payload_signer has been specified.
      if OPTIONS.payload_signer is None:
        cmd = ["openssl", "pkcs8",
               "-in", OPTIONS.package_key + OPTIONS.private_key_suffix,
               "-inform", "DER", "-nocrypt"]
        rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
        cmd.extend(["-out", rsa_key])
        p1 = common.Run(cmd, stdout=subprocess.PIPE)
        p1.wait()
        assert p1.returncode == 0, "openssl pkcs8 failed"
    
      # 准备临时文件,用于output文件的生成
      # Stage the output zip package for package signing.
      temp_zip_file = tempfile.NamedTemporaryFile()
      output_zip = zipfile.ZipFile(temp_zip_file, "w",
                                   compression=zipfile.ZIP_DEFLATED)
    
      # 提取键值对的"oem_fingerprint_properties"信息,默认情况下没有,为None
      # Metadata to comply with Android OTA package format.
      oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None)
      oem_dict = None
      if oem_props:
        if OPTIONS.oem_source is None:
          raise common.ExternalError("OEM source required for this build")
        oem_dict = common.LoadDictionaryFromLines(
            open(OPTIONS.oem_source).readlines())
    
      #
      # 构造 metadata
      # 
      # 从字典信息构建metadata, 我制作升级包时得到的metadata信息为:
      # 'post-build': 'broadcom/bcm72604usff/bcm72604usff:7.1.1/NMF27D/rg935706151800:userdebug/test-keys', 
      # 'post-build-incremental': 'eng.rg9357.20180615.180010', 
      # 'pre-device': 'bcm72604usff'
      # 'post-timestamp': '1529056810', 
      # 'ota-type': 'AB', 
      # 'ota-required-cache': '0'
      #
      metadata = {
          "post-build": CalculateFingerprint(oem_props, oem_dict,
                                             OPTIONS.info_dict),
          "post-build-incremental" : GetBuildProp("ro.build.version.incremental",
                                                  OPTIONS.info_dict),
          "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
                                       OPTIONS.info_dict),
          "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
          "ota-required-cache": "0",
          "ota-type": "AB",
      }
    
      # 制作差分包时,添加相应的pre-build/pre-build-incremental信息:
      # 'pre-build': 'broadcom/bcm72604usff/bcm72604usff:7.1.1/NMF27D/rg935706151800:userdebug/test-keys'
      # 'pre-build-incremental': 'eng.rg9357.20180615.180010', 
      #
      if source_file is not None:
        metadata["pre-build"] = CalculateFingerprint(oem_props, oem_dict,
                                                     OPTIONS.source_info_dict)
        metadata["pre-build-incremental"] = GetBuildProp(
            "ro.build.version.incremental", OPTIONS.source_info_dict)
    
      #
      # 1. 生成payload文件
      #
      # 构造使用脚本生成payload数据的命令并执行:
      # brillo_update_payload generate --payload /tmp/payload-YqkYe1.bin \
      #                                --target_image dist/new.zip \
      #                                --source_image dist/old.zip
      #
      # 1. Generate payload.
      payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
      cmd = ["brillo_update_payload", "generate",
             "--payload", payload_file,
             "--target_image", target_file]
      if source_file is not None:
        cmd.extend(["--source_image", source_file])
      p1 = common.Run(cmd, stdout=subprocess.PIPE)
      p1.wait()
      assert p1.returncode == 0, "brillo_update_payload generate failed"
    
      #
      # 2. 生成payload和metadata数据的哈希值
      #
      # 构造使用脚本从payload数据生成payload哈希和metadata哈希的命令并执行:
      # brillo_update_payload hash --unsigned_payload /tmp/payload-YqkYe1.bin \
      #                            --signature_size 256 \
      #                            --metadata_hash_file /tmp/sig-LDz25q.bin \
      #                            --payload_hash_file /tmp/sig-Cdhb80.bin
      #
      # 2. Generate hashes of the payload and metadata files.
      payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
      metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
      cmd = ["brillo_update_payload", "hash",
             "--unsigned_payload", payload_file,
             "--signature_size", "256",
             "--metadata_hash_file", metadata_sig_file,
             "--payload_hash_file", payload_sig_file]
      p1 = common.Run(cmd, stdout=subprocess.PIPE)
      p1.wait()
      assert p1.returncode == 0, "brillo_update_payload hash failed"
    
      #
      # 3. 对payload哈希和metadata哈希数据进行签名
      # 
    
      # 构造用于对payload哈希和metadata哈希签名的文件
      # 'openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt -out /tmp/key-oQvVbH.key'
      #
      # 3. Sign the hashes and insert them back into the payload file.
      signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-",
                                                    suffix=".bin")
      signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-",
                                                     suffix=".bin")
    
      # 3a. 构造openssl命令使用rsa_key对payload哈希进行签名
      # openssl pkeyutl -sign -inkey /tmp/key-oQvVbH.key \
      #                       -pkeyopt digest:sha256 \
      #                       -in /tmp/sig-Cdhb80.bin \
      #                       -out /tmp/signed-sig-2UOQ1d.bin
      #
      # 3a. Sign the payload hash.
      if OPTIONS.payload_signer is not None:
        cmd = [OPTIONS.payload_signer]
        cmd.extend(OPTIONS.payload_signer_args)
      else:
        cmd = ["openssl", "pkeyutl", "-sign",
               "-inkey", rsa_key,
               "-pkeyopt", "digest:sha256"]
      cmd.extend(["-in", payload_sig_file,
                  "-out", signed_payload_sig_file])
    
      p1 = common.Run(cmd, stdout=subprocess.PIPE)
      p1.wait()
      assert p1.returncode == 0, "openssl sign payload failed"
    
      # 3b. 构造openssl命令使用rsa_key对metadata哈希进行签名
      # openssl pkeyutl -sign -inkey /tmp/key-oQvVbH.key \
      #                       -pkeyopt digest:sha256 \
      #                       -in /tmp/sig-LDz25q.bin \
      #                       -out /tmp/signed-sig-08K2oF.bin
      # 
      # 3b. Sign the metadata hash.
      if OPTIONS.payload_signer is not None:
        cmd = [OPTIONS.payload_signer]
        cmd.extend(OPTIONS.payload_signer_args)
      else:
        cmd = ["openssl", "pkeyutl", "-sign",
               "-inkey", rsa_key,
               "-pkeyopt", "digest:sha256"]
      cmd.extend(["-in", metadata_sig_file,
                  "-out", signed_metadata_sig_file])
      p1 = common.Run(cmd, stdout=subprocess.PIPE)
      p1.wait()
      assert p1.returncode == 0, "openssl sign metadata failed"
    
      # 3c. 构造使用脚本将payload签名和metadata签名写回payload数据的命令并执行
      # brillo_update_payload sign --unsigned_payload /tmp/payload-YqkYe1.bin \
      #                            --payload /tmp/signed-payload-102BNs.bin \
      #                            --signature_size 256 \
      #                            --metadata_signature_file /tmp/signed-sig-08K2oF.bin \
      #                            --payload_signature_file /tmp/signed-sig-2UOQ1d.bin
      #
      # 3c. Insert the signatures back into the payload file.
      signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
                                                suffix=".bin")
      cmd = ["brillo_update_payload", "sign",
             "--unsigned_payload", payload_file,
             "--payload", signed_payload_file,
             "--signature_size", "256",
             "--metadata_signature_file", signed_metadata_sig_file,
             "--payload_signature_file", signed_payload_sig_file]
      p1 = common.Run(cmd, stdout=subprocess.PIPE)
      p1.wait()
      assert p1.returncode == 0, "brillo_update_payload sign failed"
    
      #
      # 4. 提取payload文件的properties数据
      #
      # 构造使用脚本提取payload properties的命令并执行
      # brillo_update_payload properties --payload /tmp/signed-payload-102BNs.bin \
      #                                  --properties_file /tmp/payload-properties-UoBiUx.txt
      # 
      # 4. Dump the signed payload properties.
      properties_file = common.MakeTempFile(prefix="payload-properties-",
                                            suffix=".txt")
      cmd = ["brillo_update_payload", "properties",
             "--payload", signed_payload_file,
             "--properties_file", properties_file]
      p1 = common.Run(cmd, stdout=subprocess.PIPE)
      p1.wait()
      assert p1.returncode == 0, "brillo_update_payload properties failed"
    
      #
      # 向properties文件添加其它属性
      #
      # 这里主要是根据OPTIONS.wipe_user_data选项决定是否往properties添加"POWERWASH=1"
      # OPTIONS.wipe_user_data选项从命令行参数"-w", "--wipe_user_data"解析得到,所以默认情况下为False
      #
      if OPTIONS.wipe_user_data:
        with open(properties_file, "a") as f:
          f.write("POWERWASH=1\n")
        metadata["ota-wipe"] = "yes"
    
      #
      # 将payload和properties以及metadata数据写入output文件中
      #    payload: payload.bin
      # properties: payload_properties.txt
      #   metadata: META-INF/com/android/metadata
      # 
      # Add the signed payload file and properties into the zip.
      common.ZipWrite(output_zip, properties_file, arcname="payload_properties.txt")
      common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin",
                      compress_type=zipfile.ZIP_STORED)
    
      #
      # 向META-INF/com/android/metadata写入metadata数据
      # 在我测试的平台上写入的数据如下:
      # $ cat META-INF/com/android/metadata 
      # ota-required-cache=0
      # ota-type=AB
      # post-build=broadcom/bcm72604usff/bcm72604usff:7.1.1/NMF27D/rg935706151800:userdebug/test-keys
      # post-build-incremental=eng.rg9357.20180615.180010
      # post-timestamp=1529056810
      # pre-build=broadcom/bcm72604usff/bcm72604usff:7.1.1/NMF27D/rg935706151800:userdebug/test-keys
      # pre-build-incremental=eng.rg9357.20180615.180010
      # pre-device=bcm72604usff
      #
      WriteMetadata(metadata, output_zip)
    
      #
      # 将dm-verity相关的care_map数据写入output文件中
      #
      # 我在制作的升级包里面看到的care_map.txt的内容为:
      # $ cat care_map.txt 
      # /dev/block/by-name/system
      #
      # If dm-verity is supported for the device, copy contents of care_map
      # into A/B OTA package.
      if OPTIONS.info_dict.get("verity") == "true":
        target_zip = zipfile.ZipFile(target_file, "r")
        care_map_path = "META/care_map.txt"
        namelist = target_zip.namelist()
        if care_map_path in namelist:
          care_map_data = target_zip.read(care_map_path)
          common.ZipWriteStr(output_zip, "care_map.txt", care_map_data)
        else:
          print "Warning: cannot find care map file in target_file package"
        common.ZipClose(target_zip)
    
      #
      # 使用OPTIONS.package_key对output文件进行签名
      #
      # 签名操作分为两步:
      # 1. 使用openssl命令检查package_key为unencrypted的private key
      # openssl pkcs8 -in build/target/product/security/testkey.pk8 -inform DER -nocrypt
      #
      # 2. 调用signapk.jar对output文件进行签名
      # java -Xmx2048m -Djava.library.path=out/host/linux-x86/lib64 \
      #                -jar out/host/linux-x86/framework/signapk.jar \
      #                -w build/target/product/security/testkey.x509.pem \
      #                build/target/product/security/testkey.pk8 /tmp/tmpNuS8M4 dist/update.zip
      #
      # Sign the whole package to comply with the Android OTA package format.
      common.ZipClose(output_zip)
      SignOutput(temp_zip_file.name, output_file)
      temp_zip_file.close()
    

    WriteABOTAPackageWithBrilloScript()函数逻辑非常清晰,也包含了详细的注释,分析时没有太大难度。该函数包含了A/B系统制作升级包的所有步骤,归纳如下:

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

    整个升级包制作的过程中主要使用了两支key,分别由OPTIONS.package_keypayload_signer指定。

    顾名思义,前者package_key用于对整个升级包update.zip进行签名,后者payload_signer用于对升级数据payload.bin中的payloadmetadata签名。
    在制作升级包时,这两支key均可以通过命令行参数"-k"/"--package_key“或”--payload_signer"设置。

    在没有设置的情况下,二者默认使用系统目录下的"build/target/product/security/testkey"文件作为key的来源。

    2.4 脚本ota_from_target_files总结

    脚本ota_from_target_files的内容大概有2100+行,其中大部分都是制作传统的非A/B系统升级包有关,制作A/B系统的升级包的逻辑非常简单。

    执行升级包制作命令时,main()函数解析命令行参数,然后加载target包并提取相应文件(主要是META/misc_info.txtSYSTEM/build.prop)信息用于创建键值对key/value集合。如果键值对集合中"ab_update"键的值为true,则判断当前为A/B系统制作升级包。随后,将整个升级包的制作都交给函数WriteABOTAPackageWithBrilloScript()处理。后者包含了A/B系统制作升级包的所有步骤:

    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对升级包文件进行签名

    其中,将制作升级包的第3步中对payload数据的处理(3.1, 3.2, 3.4)打包成一些shell命令,交由brillo_update_payload脚本进行处理。

    3. 脚本brillo_update_payload

    脚本brillo_update_payload位于目录system/update_engine/scripts中,定义了很多可供调用的函数,除去这些函数,整个脚本的主要逻辑就显得比较简单,最重要的部分如下:

    case "$COMMAND" in
      generate) validate_generate
                cmd_generate
                ;;
      hash) validate_hash
            cmd_hash
            ;;
      sign) validate_sign
            cmd_sign
            ;;
      properties) validate_properties
                  cmd_properties
                  ;;
    esac
    

    这段代码根据$COMMAND的不同取值,执行不同的操作。每个$COMMAND对应的操作都会调用两个函数,一个是validate_xxx,一个是cmd_xxx。前者主要用于验证命令行是否传递了所需要的参数,后者用于执行对应的操作,所以真正的重点在cmd_xxx函数。

    在升级包的制作过程中,WriteABOTAPackageWithBrilloScript()函数前后共有4次调用brillo_update_payload,对应于上面的4种情况,按顺序分别如下:

    为了见名知意,已经将调用命令中杂乱无章的临时文件名替换为有意义的文件名。

    #
    # 1. 生成payload文件
    #    使用`brillo_update_payload`脚本生成payload数据:
    brillo_update_payload generate --payload /tmp/payload_file.bin \
                                   --target_image dist/new.zip \
                                   --source_image dist/old.zip
    
    #
    # 2. 生成payload和metadata数据的哈希值
    #    使用`brillo_update_payload`脚本从payload数据生成payload哈希和metadata哈希:
    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. 将payload签名和metadata签名写回payload文件
    #    使用`brillo_update_payload`脚本将payload签名和metadata签名写回payload文件
    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
    
    #
    # 4. 提取payload文件的properties数据
    #    构造使用脚本提取payload properties的命令并执行
    brillo_update_payload properties --payload /tmp/signed_payload_file.bin \
                                     --properties_file /tmp/payload_properties_file.txt
    

    下面对这4个调用的命令逐个分析。

    3.1 生成payload文件

    使用brillo_update_payload脚本生成payload数据:

    brillo_update_payload generate --payload /tmp/payload_file.bin \
                                   --target_image dist/new.zip \
                                   --source_image dist/old.zip
    

    对于generate操作,执行case语句中$COMMANDgenerate的分支。

    在该分支中,validate_generate函数用于验证命令行是否设置了payloadtarget_image参数,cmd_generate函数根据传入的参数执行payload.bin的generate工作。

    cmd_generate()函数

    cmd_generate() {
      #
      # 设置payload_type
      #
      # 根据是否传入source_image参数来确定当前是以delta还是full的方式生成payload
      #
      local payload_type="delta"
      if [[ -z "${FLAGS_source_image}" ]]; then
        payload_type="full"
      fi
    
      echo "Extracting images for ${payload_type} update."
    
      #
      # 从target/source的zip包中提取boot/system image
      # 提取到的文件路径存放在 DST_PARTITIONS/SRC_PARTITIONS数组中
      #
      # 关于具体的操作,请参考后随后的extract_image()函数注解
      extract_image "${FLAGS_target_image}" DST_PARTITIONS
      if [[ "${payload_type}" == "delta" ]]; then
        extract_image "${FLAGS_source_image}" SRC_PARTITIONS
      fi
    
      echo "Generating ${payload_type} update."
      
      # 构造指示payload文件的out_file参数,如:-out_file=dist/payload.bin
      # Common payload args:
      GENERATOR_ARGS=( -out_file="${FLAGS_payload}" )
    
      local part old_partitions="" new_partitions="" partition_names=""
      #
      # 循环操作DST_PARTITIONS[boot,system]分区
      # 获取分区名boot, system和对应的target, source包里提取到的image文件名
      # partition_names=boot:system
      # new_partitions=target包中提取的boot和system image的文件名,用':'分隔
      # old_partitions=source包中提取的boot和system image的文件名,用':'分隔
      #
      for part in "${!DST_PARTITIONS[@]}"; do
        # 检查partition_names是否为空
        if [[ -n "${partition_names}" ]]; then
          partition_names+=":"
          new_partitions+=":"
          old_partitions+=":"
        fi
        partition_names+="${part}"
        new_partitions+="${DST_PARTITIONS[${part}]}"
        old_partitions+="${SRC_PARTITIONS[${part}]:-}"
      done
    
      #
      # 构造包含partition_names和new_partitions的参数
      # 如:
      # -out_file=dist/payload.bin \
      # -partition_names=boot:system \
      # -new_partitions=/tmp/boot.img.oiHmvn:/tmp/system.raw.ZkArkk
      #
      # Target image args:
      GENERATOR_ARGS+=(
        -partition_names="${partition_names}"
        -new_partitions="${new_partitions}"
      )
    
      # 如果是delta的payload, 添加old_partitions参数和minor_version/zlib_fingerprint
      # 我测试的zip包中不包含zlib_fingerprint信息。所以这里构造的参数如下:
      # -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
      #
      if [[ "${payload_type}" == "delta" ]]; then
        # Source image args:
        GENERATOR_ARGS+=(
          -old_partitions="${old_partitions}"
        )
        if [[ -n "${FORCE_MINOR_VERSION}" ]]; then
          GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" )
        fi
        if [[ -n "${ZLIB_FINGERPRINT}" ]]; then
          GENERATOR_ARGS+=( --zlib_fingerprint="${ZLIB_FINGERPRINT}" )
        fi
      fi
    
      # 添加major_version参数
      if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then
        GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" )
      fi
    
      # 如果制作脚本有传入metadata_size_file参数,则添加metadata_size_file参数
      # 我测试时没有传入metadata_size_file参数
      if [[ -n "${FLAGS_metadata_size_file}" ]]; then
        GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
      fi
    
      # 如果有指定POSTINSTALL_CONFIG_FILE,则添加new_postinstall_config_file参数
      # 变量POSTINSTALL_CONFIG_FILE默认为空
      if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then
        GENERATOR_ARGS+=(
          --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}"
        )
      fi
    
      #
      # 调用可执行文件delta_generator,传入上面构造的参数GENERATOR_ARGS,用于生成payload.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
      #
      echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}"
      "${GENERATOR}" "${GENERATOR_ARGS[@]}"
    
      echo "Done generating ${payload_type} update."
    }
    

    总结下cmd_generate()里面的操作:

    1. 根据执行时是否传入了source_image参数,确定是生成全量(full)还是增量(delta)方式的payload.bin;
    2. 调用extract_image()解压缩提取target/source的zip包中的"IMAGES/{boot,system}.img"文件到临时文件夹;
    3. 根据解压缩的boot和system image的临时文件路径构造generator的参数;
    4. 调用delta_generator,并传入前面构造的generator参数生成payload.bin;

    所以cmd_generator()将payload.bin的生成再次交给了delta_generator程序:

    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
    

    关于new_partitionsold_partitions参数中的文件名:

    • /tmp/boot.img.oiHmvn/tmp/system.raw.ZkArkk是从new.zip中提取的boot.img和system.img的临时文件名;
    • /tmp/boot.img.cXD4Dt/tmp/system.raw.IlRpgW是从old.zip中提取的boot.img和system.img的临时文件名;

    操作中,/tmp目录下的文件均为升级包制作过程中生成的临时文件,后续不再对命令中的临时文件名进行解释。

    实际上这里称为转发是不准确的,因为在转发前,brillo_update_payload会先提取target和source对应zip包(即new.zip和old.zip)中的boot.img和system.img文件,并进行适当的处理。(所谓的处理就是,如果原来是sparse image, 则转换为raw image)

    后面会对delta_generator的操作进行详细分析。

    extract_image()函数

    # extract_image <image> <partitions_array>
    #
    # Detect the format of the |image| file and extract its updatable partitions
    # into new temporary files. Add the list of partition names and its files to the
    # associative array passed in |partitions_array|.
    extract_image() {
      local image="$1"
    
      #
      # 检查image文件的前4字节确定升级文件类型
      #
      
      #
      # 1. Brillo类型的文件是zip包,所以检查zip文件头部的magic header
      #
      # Brillo images are zip files. We detect the 4-byte magic header of the zip
      # file.
      local magic=$(head --bytes=4 "${image}" | hexdump -e '1/1 "%.2x"')
      if [[ "${magic}" == "504b0304" ]]; then
        echo "Detected .zip file, extracting Brillo image."
        #
        # 调用extract_image_brillo提取zip包文件
        #
        extract_image_brillo "$@"
        return
      fi
    
      #
      # 2. Chrome OS类型的文件是GPT分区, 所以检查头部的cgpt数据
      #
      # Chrome OS images are GPT partitioned disks. We should have the cgpt binary
      # bundled here and we will use it to extract the partitions, so the GPT
      # headers must be valid.
      if cgpt show -q -n "${image}" >/dev/null; then
        echo "Detected GPT image, extracting Chrome OS image."
        #
        # 调用extract_image_cros提取Chrome OS磁盘文件
        #
        extract_image_cros "$@"
        return
      fi
    
      die "Couldn't detect the image format of ${image}"
    }
    

    extract_image()根据传入文件的类型,判断当前待提取文件是Android系统(Brillo)使用的zip包还是Chrome OS系统的磁盘文件,然后调用不同的方法进行处理。对于Android A/B系统使用的zip包,extract_image_brillo()才是提取image文件的执行者。

    extract_image_brillo()函数

    # extract_image_brillo <target_files.zip> <partitions_array>
    #
    # Extract the A/B updated partitions from a Brillo target_files zip file into
    # new temporary files.
    extract_image_brillo() {
      # 获取传入参数
      local image="$1"
      local partitions_array="$2"
    
      #
      # 解压缩zip内的META/ab_partitions.txt文件,并提取分区信息
      #
      # android-7.1.1_r23$ cat dist/new/META/ab_partitions.txt 
      # boot
      # system
      #
      local partitions=( "boot" "system" )
      local ab_partitions_list
      # 生成临时文件"ab_partitions_list.XXXXXX"
      ab_partitions_list=$(create_tempfile "ab_partitions_list.XXXXXX")
      CLEANUP_FILES+=("${ab_partitions_list}")
      # 解压缩zip包的"META/ab_partitions.txt"到临时文件
      if unzip -p "${image}" "META/ab_partitions.txt" >"${ab_partitions_list}"; then
        # 检查文件中是否有包含特殊字符串,分区名不应该包含这样的字符串
        if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then
          die "Invalid partition names found in the partition list."
        fi
        # 提取文件内容作为操作的分区
        partitions=($(cat "${ab_partitions_list}"))
        # 检查分区数
        if [[ ${#partitions[@]} -eq 0 ]]; then
          die "The list of partitions is empty. Can't generate a payload."
        fi
      else
        warn "No ab_partitions.txt found. Using default."
      fi
      echo "List of A/B partitions: ${partitions[@]}"
    
      # All Brillo updaters support major version 2.
      FORCE_MAJOR_VERSION="2"
    
      #
      # 根据当前处理的zip文件是target包还是source包做不同的处理
      #
      # 如果当前zip是source包
      if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then
        # Source image
        local ue_config=$(create_tempfile "ue_config.XXXXXX")
        CLEANUP_FILES+=("${ue_config}")
        # 提取META/update_engine_config.txt信息到临时文件ue_config.XXXXXX中
        if ! unzip -p "${image}" "META/update_engine_config.txt" \
            >"${ue_config}"; then
          warn "No update_engine_config.txt found. Assuming pre-release image, \
    using payload minor version 2"
        fi
        #
        # 读取文件内PAYLOAD_MINOR_VERSION和PAYLOAD_MAJOR_VERSION的内容
        # 测试使用的文件内容如下:
        # android-7.1.1_r23$ cat dist/new/META/update_engine_config.txt 
        # PAYLOAD_MAJOR_VERSION=2
        # PAYLOAD_MINOR_VERSION=3
        #
        # For delta payloads, we use the major and minor version supported by the
        # old updater.
        FORCE_MINOR_VERSION=$(read_option_uint "${ue_config}" \
          "PAYLOAD_MINOR_VERSION" 2)
        FORCE_MAJOR_VERSION=$(read_option_uint "${ue_config}" \
          "PAYLOAD_MAJOR_VERSION" 2)
    
        # 检查PAYLOAD_MINOR_VERSION,
        # 如果<2,退出,因为Brillo要求delta升级方式至少为3
        # Brillo support for deltas started with minor version 3.
        if [[ "${FORCE_MINOR_VERSION}" -le 2 ]]; then
          warn "No delta support from minor version ${FORCE_MINOR_VERSION}. \
    Disabling deltas for this source version."
          exit ${EX_UNSUPPORTED_DELTA}
        fi
    
        # 检查PAYLOAD_MINOR_VERSION,
        # 如果>4,则解压缩"META/zlib_fingerprint.txt"到ZLIB_FINGERPRINT
        if [[ "${FORCE_MINOR_VERSION}" -ge 4 ]]; then
          ZLIB_FINGERPRINT=$(unzip -p "${image}" "META/zlib_fingerprint.txt")
        fi
      else # 当前zip是target包的情况
        # Target image
        local postinstall_config=$(create_tempfile "postinstall_config.XXXXXX")
        CLEANUP_FILES+=("${postinstall_config}")
        # 解压缩"META/postinstall_config.txt"到POSTINSTALL_CONFIG_FILE
        if unzip -p "${image}" "META/postinstall_config.txt" \
            >"${postinstall_config}"; then
          POSTINSTALL_CONFIG_FILE="${postinstall_config}"
        fi
      fi
    
      local part part_file temp_raw filesize
      
      #
      # 对从"META/ab_partitions.txt"提取到的partition逐个操作
      #
      for part in "${partitions[@]}"; do
        part_file=$(create_tempfile "${part}.img.XXXXXX")
        CLEANUP_FILES+=("${part_file}")
        # 将"IMAGES/{boot,system}.img"释放到{boot,system}.img.xxxx临时文件
        unzip -p "${image}" "IMAGES/${part}.img" >"${part_file}"
    
        #
        # 检查{boot,system}.img文件头部的4个字节
        # 如果是"3aff26ed", 说明是sparse image,将其转换回raw image
        #
        # If the partition is stored as an Android sparse image file, we need to
        # convert them to a raw image for the update.
        local magic=$(head --bytes=4 "${part_file}" | hexdump -e '1/1 "%.2x"')
        if [[ "${magic}" == "3aff26ed" ]]; then
          temp_raw=$(create_tempfile "${part}.raw.XXXXXX")
          CLEANUP_FILES+=("${temp_raw}")
          echo "Converting Android sparse image ${part}.img to RAW."
          simg2img "${part_file}" "${temp_raw}"
          # At this point, we can drop the contents of the old part_file file, but
          # we can't delete the file because it will be deleted in cleanup.
          true >"${part_file}"
          part_file="${temp_raw}"
        fi
    
        # delta_generator only supports images multiple of 4 KiB. For target images
        # we pad the data with zeros if needed, but for source images we truncate
        # down the data since the last block of the old image could be padded on
        # disk with unknown data.
        #
        # 获取{boot,system}.img的文件大小
        # 对于source分区,则将其filesize向下截取到4K边界
        # 对于target分区,则将其filesize向上填充到4K边界
        #
        filesize=$(stat -c%s "${part_file}")
        if [[ $(( filesize % 4096 )) -ne 0 ]]; then
          if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then
            echo "Rounding DOWN partition ${part}.img to a multiple of 4 KiB."
            : $(( filesize = filesize & -4096 ))
          else
            echo "Rounding UP partition ${part}.img to a multiple of 4 KiB."
            : $(( filesize = (filesize + 4095) & -4096 ))
          fi
          truncate_file "${part_file}" "${filesize}"
        fi
    
        #
        # 更新传入参数 partitions_array
        # 调用时:
        #        传入 DST_PARTITIONS/SRC_PARTITIONS,用于区分是source还是target;
        # 调用完:
        #        传出 DST_PARTITIONS[boot/system]或SRC_PARTITIONS[boot/system]
        #        用于指示提取的boot.img/system.img的路径
        eval "${partitions_array}[\"${part}\"]=\"${part_file}\""
        echo "Extracted ${partitions_array}[${part}]: ${filesize} bytes"
      done
    }
    

    extract_image_brillo()函数看起来复制,但其所做的操作却比较简单:

    1. 从zip包的"META/ab_partitions.txt"文件中提取分区信息;
    2. 从zip包中解压缩boot/system image文件到临时文件,如果文件是sparse image,则将其转换回raw image;
    3. 以数组方式返回target/source的zip包提取到的boot/system raw image名字;

    一句话,extract_image_brillo()函数提取zip包中的boot/system image文件用于后续处理。

    生成payload文件总结

    brillo_update_payload脚本中,通过函数cmd_generate()生成payload.bin。

    操作上,cmd_generate()调用extract_image()提取zip包中IMAGES目录中的boot.img和system.img,如果提取得到的是sparse image格式文件,则还需要进一步使用simg2img工具将其转换为raw image格式。
    如果指定了target和source制作增量包,则相应会提取到4个image文件(target和source包各自的boot.img/system.img);如果只指定target制作全量包,则得到2个image文件(boot.img/system.img)。
    然后,用提取到的boot.img和system.img的路径构造参数并传递给delta_generator应用程序。

    以增量包为例,cmd_generate()调用delta_generator生成payload.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
    

    所以,最终通过delta_generator去生成payload.bin文件。

    3.2 生成payload数据和metadata数据的哈希值

    使用brillo_update_payload脚本从payload数据生成payload哈希和metadata哈希:

    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
    

    对于hash操作,执行case语句中$COMMANDhash的分支。

    在该分支中,validate_hash()函数用于验证命令行是否设置了signature_size/unsigned_payload/payload_hash_file/metadata_hash_file等参数,然后cmd_hash()函数将这些参数传递给delta_generator去生成payload和metadata的哈希。

    代码非常简单,甚至不需要注释:

    cmd_hash() {
      "${GENERATOR}" \
          -in_file="${FLAGS_unsigned_payload}" \
          -signature_size="${FLAGS_signature_size}" \
          -out_hash_file="${FLAGS_payload_hash_file}" \
          -out_metadata_hash_file="${FLAGS_metadata_hash_file}"
    
      echo "Done generating hash."
    }
    

    以增量包为例,cmd_hash()调用delta_generator生成payload和metadata的哈希命令为:

    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
    

    好吧,最终还是通过delta_generator去生成payload和metadata的哈希。

    3.3 将payload签名和metadata签名写回payload文件

    使用brillo_update_payload脚本将payload签名和metadata签名写回payload文件

    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
    

    对于sign操作,执行case语句中$COMMANDsign的分支。

    在该分支中,validate_sign()函数用于验证命令行是否设置了signature_size/unsigned_payload/payload/payload_signature_file/metadata_signature_file等参数,然后cmd_sign()函数将这些参数传递给delta_generator去生成包含签名的payload.bin文件。

    这里的代码也非常简单,不需要注释:

    cmd_sign() {
      GENERATOR_ARGS=(
        -in_file="${FLAGS_unsigned_payload}"
        -signature_size="${FLAGS_signature_size}"
        -signature_file="${FLAGS_payload_signature_file}"
        -metadata_signature_file="${FLAGS_metadata_signature_file}"
        -out_file="${FLAGS_payload}"
      )
    
      if [[ -n "${FLAGS_metadata_size_file}" ]]; then
        GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
      fi
    
      "${GENERATOR}" "${GENERATOR_ARGS[@]}"
      echo "Done signing payload."
    }
    

    以增量包为例,cmd_sign()调用delta_generator将payload签名和metadata签名写回payload文件的命令为:

    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
    

    虽然这里叫写回,其实并不是在原来的payload.bin文件上操作,而是使用payload.bin, signed_payload_file.binsigned_metadata_sig_file.bin生成了一个新的signed_payload_file.bin文件。

    好吧,最终也还是通过delta_generator去合并上payload哈希和metadata哈希。

    3.4 提取payload文件的properties数据

    构造使用脚本提取payload properties的命令并执行

    brillo_update_payload properties --payload /tmp/signed_payload_file.bin \
                                     --properties_file /tmp/payload_properties_file.txt
    

    对于properties操作,执行case语句中$COMMANDproperties的分支。

    在该分支中,validate_properties()函数用于验证命令行是否设置了payload/properties_file等参数,然后cmd_properties()函数将这些参数传递给delta_generator去提取payload文件的properties数据。

    代码如下:

    cmd_properties() {
      "${GENERATOR}" \
          -in_file="${FLAGS_payload}" \
          -properties_file="${FLAGS_properties_file}"
    }
    

    以增量包为例,cmd_properties()调用delta_generator提取payload文件的properties数据的命令为:

    delta_generator -in_file=/tmp/signed_payload_file.bin \
                    -properties_file=/tmp/payload_properties_file.txt
    

    所以,delta_generator也负责提取payload文件的properties数据的操作。

    3.5 脚本brillo_update_payload总结

    在生成payload.bin时,需要4步操作:

    1. 生成payload文件
    2. 生成payload和metadata数据的哈希值
    3. 将payload签名和metadata签名写回payload文件
    4. 提取payload文件的properties数据

    brillo_update_payload脚本将这4步操作中的命令转发给delta_generator。分别如下:

    • 生成payload文件
    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
    
    • 生成payload和metadata数据的哈希值
    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
    
    • 将payload签名和metadata签名写回payload文件
    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
    
    • 提取payload文件的properties数据
    delta_generator -in_file=/tmp/signed_payload_file.bin \
                    -properties_file=/tmp/payload_properties_file.txt
    

    然后余下的操作都在delta_generator里面了,下一篇将会对delta_generator代码中的这4个操作进行详细分析。

    4. 联系和福利

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

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

      image

    展开全文
  • Android Update Engine分析(五)服务端核心之Action机制 前面四篇分别分析了Makefile,Protobuf和AIDL相关文件, Update Engine的客户端进程update_engine_client以及Update Engine的服务端: Android Update ...

    Android Update Engine分析(五)服务端核心之Action机制

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

    本篇开始分析Update Engine服务端进程的核心,Action机制。

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

    1. 什么是Action机制?

    Action机制是整个Update Engine服务端进程运行的核心,不清楚Action机制,就无法了解整个Update Engine服务端是如何运作的。

    Action机制主要有三个部分组成: Action, ActionProcessor和ActionPipe。

    1.1 Action

    基于Action机制,Update Engine里面的每一个任务都被封装为一个Action,例如下载任务被封装为DownloadAction, 文件系统验证的任务被封装为FilesystemVerifierAction,更新完成后所有收尾动作被封装为PostinstallRunnerAction。

    当然,这个Action机制很灵活,你甚至可以根据需要定义自己特有的Action。例如,将数据解密的操作定义为DecryptAction,在DownloadAction下载完数据后需要先通过DecryptAction将数据解密,再将解密后的数据送往下一个操作。

    总之,Action就是一个基本的任务单元,只干一件事。

    好吧,如果你想定义一个Action来完成多个事情,貌似也是可以的,参考官方Action Twiki的SubAction描述。
    Action机制的实现最初来源于项目 Google Update Engine 项目,该项目基于Mac OS X的框架使用Objectiv-C实现。
    关于Action机制的官方描述,请参考:The Update Engine Action System

    Update Engine代码中,默认定义了4个Action,分别为:InstallPlanAction, DownloadAction, FilesystemVerifierAction, PostinstallRunnerAction。这些Action的名字都很直观,见名知意。

    单个Action执行的时间不是固定的。例如,基于不同的网络状况,有的DownloadAction很快就可以完成,但有的DownloadAction可能需要很久。因此代码上,Action都是异步执行的,对Action调用PerformAction()使其开始工作,但这个函数返回只表明Action开始了,并不代表执行结束(有可能结束,也有可能刚开始)。

    例如DownloadAction,调用PerformAction()对数据下载进行初始化设置,然后底层的数据传输开始工作,函数返回时数据传输并没有完成。在数据传输完成时底层会触发调用TransferComplete()进行通知。

    1.2 Action Processor

    既然Update Engine里定义了多个Action,那这些Action是如何组织运行的呢?此时就需要有一个Action的管理者,这就是ActionProcessor.

    在ActionProcessor里面定义了一个Action的队列, 在Update Engine准备更新时,会根据当前传入的参数构造多个Action并放入ActionProcessor的Action队列。

    除了Action队列,ActionProcessor中还有一个指针,用于指示当前正在运行的Action。

    ActionProcessor通过StartProcessing()操作开始工作,先挑选队列中的第一个Action作为当前Action。然后当前Action调用PerformAction()开始工作,Action结束后会调用ActionProcessor的ActionComplete()接口通知当前Action已经完成。随后ActionProcessor通过StartNextActionOrFinish()挑选队列中的下一个Action进行操作。循环往复,直到队列中的所有Action都完成操作。

    1.3 Action Pipe

    类似于Unix系统的管道,Action机制中,也会通过管道ActionPipe将这些Action链接在一起。上一个Action的输出会作为下一个Action的输入。

    因此在Update Engine中,所有的Action之间有一个先后关系。例如,只有DownloadAction完成操作了,才能开始FilesystemVerifierAction操作;也只有FilesystemVerifierAction结束了,才会开始PostinstallRunnerAction操作。

    2. Action机制实现分析

    2.1 ActionProcessor类

    抛开Action实现的细节,先来看看Action的组织管理者ActionProcessor是如何运作的。

    ActionProcessor工作的中心是管理Action,所以基本上所有操作是围绕Action队列进行的,包括:
    - Action入队操作:EnqueueAction
    - 开始和停止处理:StartProcessing/StopProcessing
    - 暂停和恢复处理:SuspendProcessing/ResumeProcessing
    - 当前Action结束的收尾工作:ActionComplete
    - 选择下一Action操作:StartNextActionOrFinish

    2.1.1 Action入队操作

    Action的入对操作EnqueueAction比较简单,就是将传入的Action添加到管理的队列actions_中,并将将自己设置为Action的管理者。

    void ActionProcessor::EnqueueAction(AbstractAction* action) {
      // 将传入的action添加到队列尾部
      actions_.push_back(action);
      // 将自己设置为Action的Processor
      action->SetProcessor(this);
    }

    update_attempter_调用BuildUpdateActions()操作时,生成多个action并保存到临时队列actions_中,函数的最后通过EnqueueAction操作将临时队列中的action逐个添加到管理者processor_的队列中,如下:

    void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
      // 创建多个action,并将action添加到临时队列actions_中
      ...
    
      // 将临时队列actions_中的action逐个添加到ActionProcessor的队列中
      // Enqueue the actions.
      for (const shared_ptr<AbstractAction>& action : actions_)
        processor_->EnqueueAction(action.get());
    }

    2.1.2 开始和停止处理

    当调用StartProcessing()开始处理工作时,先取得队列前端的Action,并将其存放到指示当前Action的指针current_action_中,然后调用Action对应的PerformAction()开始Action工作。

    void ActionProcessor::StartProcessing() {
      CHECK(!IsRunning());
      // Action队列不为空时,开始工作
      if (!actions_.empty()) {
        // current_action_指向队列中的第一个action
        current_action_ = actions_.front();
        LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
        // 当前工作的action从队列中出列
        actions_.pop_front();
        // 开始当前Action的工作
        current_action_->PerformAction();
      }
    }

    当调用StopProcessing()停止处理工作时,会先通知当前Action停止工作,然后将Action队列清空,并调用ProcessingStopped(this)向外通知ActionProcessor已经停止工作了。

    void ActionProcessor::StopProcessing() {
      // 检查是否有在运行
      CHECK(IsRunning());
      // 如果当前有正在处理的action,则终止当前action的活动
      if (current_action_) {
        current_action_->TerminateProcessing();
        current_action_->SetProcessor(nullptr);
      }
      LOG(INFO) << "ActionProcessor: aborted "
                << (current_action_ ? current_action_->Type() : "")
                << (suspended_ ? " while suspended" : "");
      // 将当前action指针置空,并设置suspended_状态为false
      current_action_ = nullptr;
      suspended_ = false;
      // Delete all the actions before calling the delegate.
      // 队列中的action逐个移除processor设置,不再被当前ActionProcessor管理
      for (auto action : actions_)
        action->SetProcessor(nullptr);
      // 清空action队列
      actions_.clear();
      // 向外通知processor已经停止工作了
      if (delegate_)
        delegate_->ProcessingStopped(this);
    }

    2.1.3 暂停和恢复处理

    调用SuspendProcessing()ResumeProcessing()暂停和恢复当前processor的处理工作。

    void ActionProcessor::SuspendProcessing() {
      // 如果已经是暂停状态或当前没有action在处理(还没开始或已经结束了),此时暂停操作没有意义
      // No current_action_ when not suspended means that the action processor was
      // never started or already finished.
      if (suspended_ || !current_action_) {
        LOG(WARNING) << "Called SuspendProcessing while not processing.";
        return;
      }
      // 设置暂停标识suspended_为true
      suspended_ = true;
    
      // 暂停当前正在运行的action
      // If there's a current action we should notify it that it should suspend, but
      // the action can ignore that and terminate at any point.
      LOG(INFO) << "ActionProcessor: suspending " << current_action_->Type();
      current_action_->SuspendAction();
    }
    
    void ActionProcessor::ResumeProcessing() {
      // 如果不是暂停状态,使用恢复操作也没有意义……
      if (!suspended_) {
        LOG(WARNING) << "Called ResumeProcessing while not suspended.";
        return;
      }
      // 取消暂停标识,将其suspended_设置为false
      suspended_ = false;
      // 如果暂停前当前有Action在操作(即调用了SuspendAction()),则需要继续Action的处理
      if (current_action_) {
        // The current_action_ did not call ActionComplete while suspended, so we
        // should notify it of the resume operation.
        LOG(INFO) << "ActionProcessor: resuming " << current_action_->Type();
        current_action_->ResumeAction();
      } else { 
        // 如果暂停前没有Action在操作(当前Action刚好结束了),那就挑选队列中的下一个Action运行
        // The last action called ActionComplete while suspended, so there is
        // already a log message with the type of the finished action. We simply
        // state that we are resuming processing and the next function will log the
        // start of the next action or processing completion.
        LOG(INFO) << "ActionProcessor: resuming processing";
        StartNextActionOrFinish(suspended_error_code_);
      }
    }

    2.1.4 当前Action结束的收尾工作

    当前Action操作结束后通知ActionProcessor结束了,此时processor会进行一些列的收尾工作。包括通知外部当前Action已经结束,不再管理已经完成的action,挑选队列中的下一个action进行处理等。

    void ActionProcessor::ActionComplete(AbstractAction* actionptr,
                                         ErrorCode code) {
      CHECK_EQ(actionptr, current_action_);
      // 通知外部当前action已经结束了(运行时这里的delegate_就是update_attempter_)
      if (delegate_)
        delegate_->ActionCompleted(this, actionptr, code);
      string old_type = current_action_->Type();
      // 调用action的ActionCompleted操作更新状态
      current_action_->ActionCompleted(code);
      // 将action的processor置空,不再管理这个action了
      current_action_->SetProcessor(nullptr);
      current_action_ = nullptr;
      LOG(INFO) << "ActionProcessor: finished "
                << (actions_.empty() ? "last action " : "") << old_type
                << (suspended_ ? " while suspended" : "")
                << " with code " << utils::ErrorCodeToString(code);
      // 如果当前action_队列中还有待操作的action,但是当前的action又失败了,那就清空队列中的剩余action
      // 因为已经失败了,不用继续搞了啊,收工。
      if (!actions_.empty() && code != ErrorCode::kSuccess) {
        LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
        actions_.clear();
      }
      // 巧了,如果当前action完成了,发现processor也暂停了,那就不再继续执行下一个action了。
      if (suspended_) {
        // If an action finished while suspended we don't start the next action (or
        // terminate the processing) until the processor is resumed. This condition
        // will be flagged by a nullptr current_action_ while suspended_ is true.
        suspended_error_code_ = code;
        return;
      }
      // 当前action完成了,那就继续队列中下一个action的处理吧。
      StartNextActionOrFinish(code);
    }

    2.1.5 选择下一Action操作

    通过StartNextActionOrFinish()来选择队列中的下一个Action,如果队列中不再有待处理的Action,那整个操作就完成了。

    void ActionProcessor::StartNextActionOrFinish(ErrorCode code) {
      // Action队列已经被掏空,好吧,那就通知外部处理已经完成了
      if (actions_.empty()) {
        if (delegate_) {
          // 运行时这里的delegate_就是update_attempter_,所以调用update_attempter_->ProcessingDone
          delegate_->ProcessingDone(this, code);
        }
        return;
      }
      // 来吧,队列中的下一位兄弟,该你上班了
      current_action_ = actions_.front();
      // 队列中的第1位兄弟已经工作了,所以就不要给他留位置了。
      actions_.pop_front();
      LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
      // 当前Action,该干嘛干嘛去吧。
      current_action_->PerformAction();
    }

    2.1.6 ActionProcessor总结

    所以整个ActionProcessor的操作也比较直观,就是不断挑选队列中的Action去干活:
    - 如果当前的Action操作完成,那就告诉外面当前Action已经完成了,并挑选下一个去干活;
    - 如果外面通知要求暂停或终止活动,那就转达告知当前的Action暂停或取消活动。

    2.2 Action类

    Action类的关系几句话说不清楚,那就上图吧:

    Action类之间的继承关系

    要点如下:

    • AbstractAction
      • 整个Action的抽象基类,提供各种Action调用的公共接口
    • Action
      • 继承自 AbstractAction,是一个模板类,实现了管道操作的接口,因此其子类支持ActionPipe的管道操作
    • InstallPlanAction
      • Update Engine系统中,所有具体Action任务类的基类,其成员install_plan_包含了升级需要的元数据信息
    • DownloadAction
      • 载数据的具体任务类
    • FilesystemVerifierAction
      • 数据下载完成后进行数据验证的具体任务类
    • PostinstallRunnerAction
      • 安装收尾工作的具体任务类

    所以想了解哪个阶段的细节,就去debug相应阶段的代码就好了。例如想知道如何下载的,那就去看看DownloadAction的实现代码;想知道下载完成后数据是如何验证的,那就去看看FilesystemVerifierAction的实现代码。

    后面也打算专门针对每一个Action进行详细分析,看看这些Action是如何工作的。

    2.3 ActionPipe类

    ActionPipe就是将两个Action粘接在一起,前一个Action的output连接上后一个Action的input形成管道。ActionPipe基于Action类的层面,所以所有Action类的子类都支持管道特性。

    ActionPipe类的代码比较简单,但也非常抽象,不容易有直观的认识,因此我们重点看看UpdateAttempterAndroid是如何将各个管道粘接在一起的:

    void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
      ...
    
      // 生成install_plan_action
      // Actions:
      shared_ptr<InstallPlanAction> install_plan_action(
          new InstallPlanAction(install_plan_));
    
      ...
      // 生成download_action
      shared_ptr<DownloadAction> download_action(new DownloadAction(
          prefs_,
          boot_control_,
          hardware_,
          nullptr,                                        // system_state, not used.
          new MultiRangeHttpFetcher(download_fetcher)));  // passes ownership
      // 生成dst_filesystem_verifier_action
      shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
          new FilesystemVerifierAction(boot_control_,
                                       VerifierMode::kVerifyTargetHash));
    
      // postinstall_runner_action
      shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
          new PostinstallRunnerAction(boot_control_, hardware_));
    
      ...
    
      // 这里调用BondActions()操作将前面生成的4个Action粘接在一起,形成管道
      // Bond them together. We have to use the leaf-types when calling
      // BondActions().
      BondActions(install_plan_action.get(), download_action.get());
      BondActions(download_action.get(), dst_filesystem_verifier_action.get());
      BondActions(dst_filesystem_verifier_action.get(),
                  postinstall_runner_action.get());
    
      ...
    }

    到这里,Action机制的三大组件Action, ActionProcessor, ActionPipe都做过介绍了,下面我们来看看UpdateAttempterAndroid类基于Action机制是如何工作的。

    3. UpdateAttempterAndroid类

    上一篇说到,Update Engine服务端除去回调操作外,基本上所有调用最后都会交由DaemonStateAndroid类的私有成员update_attempter_处理,所以update_attempter_是Update Engine服务端的核心对象。其对应的类UpdateAttempterAndroid也就是整个Update Engine的核心类。

    UpdateAttempterAndroid类自身来说,最重要的功能是将客户端传递过来的升级请求打包生成各种Action任务,然后交由ActionProcessor进行管理。ActionProcessor管理各任务Action的调度和执行,并在适当的时间向UpdateAttempterAndroid类报告Action任务的执行结果。例如,报告当前某个Action执行完成了,又或者报告当前所有Action都执行完成了。

    沿着这条主线,我们看看UpdateAttempterAndroid是如何操作的。

    3.1 函数ApplyPayload()

    Update Engine的客户端发起升级请求后,所有请求的参数通过Binder服务,最后通过ApplyPayload()接口传递给UpdateAttempterAndroid类。

    ApplyPayload()函数的有110行,咋一看还有点云里雾里的,你可能以为客户端发起升级请求后,ApplyPayload解析升级参数,然后通过各种搞搞搞就完成了升级。因为曾经有个哥们问我说,ApplyPayload中还没看到哪里接收数据,咋就在最后调用UpdateBootFlags()操作去更新boot flag了啊?他以为UpdateBootFlags()是更新完成后的操作,其实我一开始也是这么认为的。额,其实不是的,因为ApplyPayload是异步执行的,ApplyPayload返回只是表明升级开始了~

    ApplyPayload做的事情前后主要有以下几件:
    1. 判断升级条件,决定是否要升级
    2. 解析传入参数
    3. 使用传入的参数构建install_plan_
    4. 构建打包升级的各种Action
    5. 设置下载升级参数
    6. 通过回调通知升级进度并更新启动标识

    以下是对ApplyPayload函数的详细注释:

    bool UpdateAttempterAndroid::ApplyPayload(
        const string& payload_url,
        int64_t payload_offset,
        int64_t payload_size,
        const vector<string>& key_value_pair_headers,
        brillo::ErrorPtr* error) {
      //
      // 1. 判断升级条件,决定是否要升级
      //
      // 刚完成升级,目前需要重启,不需要升级
      if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) {
        return LogAndSetError(
            error, FROM_HERE, "An update already applied, waiting for reboot");
      }
      // 升级正在进行中,所以不需要再次重新升级
      if (ongoing_update_) {
        return LogAndSetError(
            error, FROM_HERE, "Already processing an update, cancel it first.");
      }
      // 确保当前是IDLE状态
      DCHECK(status_ == UpdateStatus::IDLE);
    
      //
      // 2. 解析传入参数
      //
      std::map<string, string> headers;
      for (const string& key_value_pair : key_value_pair_headers) {
        string key;
        string value;
        if (!brillo::string_utils::SplitAtFirst(
                key_value_pair, "=", &key, &value, false)) {
          return LogAndSetError(
              error, FROM_HERE, "Passed invalid header: " + key_value_pair);
        }
        if (!headers.emplace(key, value).second)
          return LogAndSetError(error, FROM_HERE, "Passed repeated key: " + key);
      }
    
      //
      // 3. 使用传入的参数构建install_plan_
      //    我现在一点都不想去关心到底设置了哪些字段
      //
      // Unique identifier for the payload. An empty string means that the payload
      // can't be resumed.
      string payload_id = (headers[kPayloadPropertyFileHash] +
                           headers[kPayloadPropertyMetadataHash]);
    
      // Setup the InstallPlan based on the request.
      install_plan_ = InstallPlan();
    
      install_plan_.download_url = payload_url;
      install_plan_.version = "";
      base_offset_ = payload_offset;
      install_plan_.payload_size = payload_size;
      if (!install_plan_.payload_size) {
        if (!base::StringToUint64(headers[kPayloadPropertyFileSize],
                                  &install_plan_.payload_size)) {
          install_plan_.payload_size = 0;
        }
      }
      install_plan_.payload_hash = headers[kPayloadPropertyFileHash];
      if (!base::StringToUint64(headers[kPayloadPropertyMetadataSize],
                                &install_plan_.metadata_size)) {
        install_plan_.metadata_size = 0;
      }
      install_plan_.metadata_signature = "";
      // The |public_key_rsa| key would override the public key stored on disk.
      install_plan_.public_key_rsa = "";
    
      install_plan_.hash_checks_mandatory = hardware_->IsOfficialBuild();
      install_plan_.is_resume = !payload_id.empty() &&
                                DeltaPerformer::CanResumeUpdate(prefs_, payload_id);
      if (!install_plan_.is_resume) {
        if (!DeltaPerformer::ResetUpdateProgress(prefs_, false)) {
          LOG(WARNING) << "Unable to reset the update progress.";
        }
        if (!prefs_->SetString(kPrefsUpdateCheckResponseHash, payload_id)) {
          LOG(WARNING) << "Unable to save the update check response hash.";
        }
      }
      // The |payload_type| is not used anymore since minor_version 3.
      install_plan_.payload_type = InstallPayloadType::kUnknown;
    
      install_plan_.source_slot = boot_control_->GetCurrentSlot();
      install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
    
      int data_wipe = 0;
      install_plan_.powerwash_required =
          base::StringToInt(headers[kPayloadPropertyPowerwash], &data_wipe) &&
          data_wipe != 0;
    
      NetworkId network_id = kDefaultNetworkId;
      if (!headers[kPayloadPropertyNetworkId].empty()) {
        if (!base::StringToUint64(headers[kPayloadPropertyNetworkId],
                                  &network_id)) {
          return LogAndSetError(
              error,
              FROM_HERE,
              "Invalid network_id: " + headers[kPayloadPropertyNetworkId]);
        }
        if (!network_selector_->SetProcessNetwork(network_id)) {
          LOG(WARNING) << "Unable to set network_id, continuing with the update.";
        }
      }
    
      LOG(INFO) << "Using this install plan:";
      install_plan_.Dump();
    
      //
      // 4. 构建打包升级的各种Action
      //
      BuildUpdateActions(payload_url);
      //
      // 5. 设置下载升级参数
      //
      SetupDownload();
      // Setup extra headers.
      HttpFetcher* fetcher = download_action_->http_fetcher();
      if (!headers[kPayloadPropertyAuthorization].empty())
        fetcher->SetHeader("Authorization", headers[kPayloadPropertyAuthorization]);
      if (!headers[kPayloadPropertyUserAgent].empty())
        fetcher->SetHeader("User-Agent", headers[kPayloadPropertyUserAgent]);
    
      //
      // 6. 通过回调通知升级进度并更新启动标识
      //
      cpu_limiter_.StartLimiter();
      // 回调通知客户端当前进度为0
      SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
      // 设置当前状态为升级中
      ongoing_update_ = true;
    
      // 更新启动标识
      // Just in case we didn't update boot flags yet, make sure they're updated
      // before any update processing starts. This will start the update process.
      UpdateBootFlags();
      return true;
    }

    3.2 函数BuildUpdateActions()

    ApplyPayload()的代码中,最重要的就是调用BuildUpdateActions()构建升级中的各种Action。我们不妨看看到底都需要哪些Action,这些Action都是如何构建的。

    从比较粗的粒度上看BuildUpdateActions(),构建Action的活动包括:
    1. 构建InstallPlanAction: install_plan_action
    2. 构建DownloadAction: download_action
    3. 构建FilesystemVerifierAction: dst_filesystem_verifier_action
    4. 构建PostinstallRunnerAction: postinstall_runner_action
    5. 使用ActionPipe将4个Action连接起来
    6. 将Action添加到ActionProcessor的管理队列中

    void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
      // 检查ActionProcessor是否已经处于Running状态
      CHECK(!processor_->IsRunning());
      // 将自己设置为ActionProcessor的代理对象,ActionProcessor只需要通过代理对象就可以向外发送通知
      processor_->set_delegate(this);
    
      //
      // 1. 构建install_plan_action
      //
      // Actions:
      shared_ptr<InstallPlanAction> install_plan_action(
          new InstallPlanAction(install_plan_));
    
      // 检查数据下载的传输地址协议
      HttpFetcher* download_fetcher = nullptr;
      // 如果是"file:///"就使用FileFetcher进行下载
      if (FileFetcher::SupportedUrl(url)) {
        DLOG(INFO) << "Using FileFetcher for file URL.";
        download_fetcher = new FileFetcher();
      } else { // 如果是其它的协议,则使用libcurl库进行下载;
    #ifdef _UE_SIDELOAD
        LOG(FATAL) << "Unsupported sideload URI: " << url;
    #else
        LibcurlHttpFetcher* libcurl_fetcher =
            new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
        libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
        download_fetcher = libcurl_fetcher;
    #endif  // _UE_SIDELOAD
      }
      //
      // 2. 构建download_action
      //
      shared_ptr<DownloadAction> download_action(new DownloadAction(
          prefs_,
          boot_control_,
          hardware_,
          nullptr,                                        // system_state, not used.
          new MultiRangeHttpFetcher(download_fetcher)));  // passes ownership
      //
      // 3. 构建dst_filesystem_verifier_action
      //
      shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
          new FilesystemVerifierAction(boot_control_,
                                       VerifierMode::kVerifyTargetHash));
    
      //
      // 4. 构建postinstall_runner_action
      //
      shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
          new PostinstallRunnerAction(boot_control_, hardware_));
    
      download_action->set_delegate(this);
      download_action_ = download_action;
      postinstall_runner_action->set_delegate(this);
    
      // 将前面构建的4个Action添加到actions_向量中
      actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
      actions_.push_back(shared_ptr<AbstractAction>(download_action));
      actions_.push_back(
          shared_ptr<AbstractAction>(dst_filesystem_verifier_action));
      actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
    
      //
      // 5. 使用ActionPipe将4个Action连接起来
      //
      // Bond them together. We have to use the leaf-types when calling
      // BondActions().
      BondActions(install_plan_action.get(), download_action.get());
      BondActions(download_action.get(), dst_filesystem_verifier_action.get());
      BondActions(dst_filesystem_verifier_action.get(),
                  postinstall_runner_action.get());
    
      // 
      // 6. 将Action添加到ActionProcessor的管理队列中
      //
      // Enqueue the actions.
      for (const shared_ptr<AbstractAction>& action : actions_)
        processor_->EnqueueAction(action.get());
    }

    3.3 函数UpdateBootFlags()

    回到ApplyPayload()函数,调用BuildUpdateActions()构建升级的Action后,使用SetupDownload()设置详细的数据下载细节,这一切完成后就通过SetStatusAndNotify()向客户端发起回调通知当前的下载进度(如果是全新升级,那这里的进度就是0;如果是继续之前未完成的下载,那就是实际的下载进度)。

    最后一步就是调用UpdateBootFlags(),从字面上看是更新启动标识,注意在这个函数之前,还没有地方指示去开始升级,所以千万不要以为这里是升级结束更新启动标识。

    那到底是什么呢?其实,在这里更新的启动标识只是将当前的分区标记为成功启动。因为在升级中,另外一个升级的分区会被设置为不可启动,这样确保即使升级失败,那下次启动会进入到当前成功启动的分区,而不是去启动升级失败的分区,代码如下:

    void UpdateAttempterAndroid::UpdateBootFlags() {
      // updated_boot_flags_默认为false,表示没有更新过启动标识
      if (updated_boot_flags_) {
        LOG(INFO) << "Already updated boot flags. Skipping.";
        // 如果已经更新过启动标识,那就直接开始升级
        CompleteUpdateBootFlags(true);
        return;
      }
    
      // 如果还没有更新过启动标识,那这里就先调用boot_control_更新启动标识
      // 实际上在MarkBootSuccessfulAsync()函数主要是标记当前分区为成功启动的分区
      // This is purely best effort.
      LOG(INFO) << "Marking booted slot as good.";
      // 不清楚这里为什么要采用异步方式?
      if (!boot_control_->MarkBootSuccessfulAsync(
              Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
                   base::Unretained(this)))) {
        LOG(ERROR) << "Failed to mark current boot as successful.";
        CompleteUpdateBootFlags(false);
      }
    }

    这里最大的疑问是,不清楚为什么要采用异步的方式MarkBootSuccessfulAsync()来标记分区?谁来解释下?

    这里最具有迷惑性的函数就是CompleteUpdateBootFlags()了,这个函数命名就是指示ActionProcessor开始升级操作,但不清楚为什么偏偏要命名为CompleteUpdateBootFlags,难道是指更新完启动标识后的收尾动作吗?

    以下是CompleteUpdateBootFlags()的相关实现:

    void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
      updated_boot_flags_ = true;
      ScheduleProcessingStart();
    }
    
    void UpdateAttempterAndroid::ScheduleProcessingStart() {
      LOG(INFO) << "Scheduling an action processor start.";
      brillo::MessageLoop::current()->PostTask(
          FROM_HERE, Bind([this] { this->processor_->StartProcessing(); }));
    }

    这里可见,调用CompleteUpdateBootFlags()最终是让ActionProcessor执行StartProcessing(),后者意味着升级的Action队列开始工作了。

    所以,真正的升级,从这里才开始。明白了为什么ApplyPayload()为什么是异步的了吗?

    3.4 其它函数

    前面已经分析了UpdateAttempterAndroid在功能上最重要的函数,下面来看看其它的函数。

    1. Init()UpdateCompletedOnThisBoot()

    Init()函数主要检查当前是否刚完成过升级,如果刚完成升级,那就将系统设置为需要重启的状态。

    void UpdateAttempterAndroid::Init() {
      // In case of update_engine restart without a reboot we need to restore the
      // reboot needed state.
      if (UpdateCompletedOnThisBoot())
        SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
      else
        SetStatusAndNotify(UpdateStatus::IDLE);
    }
    
    ...
    
    bool UpdateAttempterAndroid::UpdateCompletedOnThisBoot() {
      // In case of an update_engine restart without a reboot, we stored the boot_id
      // when the update was completed by setting a pref, so we can check whether
      // the last update was on this boot or a previous one.
      string boot_id;
      TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
    
      string update_completed_on_boot_id;
      return (prefs_->Exists(kPrefsUpdateCompletedOnBootId) &&
              prefs_->GetString(kPrefsUpdateCompletedOnBootId,
                                &update_completed_on_boot_id) &&
              update_completed_on_boot_id == boot_id);
    }

    其中,UpdateCompletedOnThisBoot()主要是从磁盘文件上读取boot id,然后同更新完成需要重启的boot id(来自kPrefsUpdateCompletedOnBootId)进行比较,如果二者相等,则说明刚完成过升级。

    2. SuspendUpdate(), ResumeUpdate()CancelUpdate()ResetStatus()

    关于更新的SuspendResumeCancel操作,Update Engine的Binder服务类BinderUpdateEngineAndroidService通过service_delegate_成员传递给UpdateAttempterAndroid类,后者在收到请求后再次将其转发给了Action队列的管理者ActionProcessor,由ActionProcessor对当前正在执行的Action采取相应的操作。

    bool UpdateAttempterAndroid::SuspendUpdate(brillo::ErrorPtr* error) {
      if (!ongoing_update_)
        return LogAndSetError(error, FROM_HERE, "No ongoing update to suspend.");
      processor_->SuspendProcessing();
      return true;
    }
    
    bool UpdateAttempterAndroid::ResumeUpdate(brillo::ErrorPtr* error) {
      if (!ongoing_update_)
        return LogAndSetError(error, FROM_HERE, "No ongoing update to resume.");
      processor_->ResumeProcessing();
      return true;
    }
    
    bool UpdateAttempterAndroid::CancelUpdate(brillo::ErrorPtr* error) {
      if (!ongoing_update_)
        return LogAndSetError(error, FROM_HERE, "No ongoing update to cancel.");
      processor_->StopProcessing();
      return true;
    }

    至于ResetStatus操作,目的就是将系统恢复到UpdateStatus::IDLE状态。因此会根据当前Update Engine系统的状态,采取一些额外的措施使系统处于UpdateStatus::IDLE状态:

    bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) {
      LOG(INFO) << "Attempting to reset state from "
                << UpdateStatusToString(status_) << " to UpdateStatus::IDLE";
    
      // 根据系统当前的状态决定切换到UpdateStatus::IDLE状态要做什么操作
      switch (status_) {
        // 本来就是UpdateStatus::IDLE,所以什么都不用做
        case UpdateStatus::IDLE:
          return true;
    
        // 如果是刚升完级处于需要REBOOT的状态,那就清理升级的痕迹,把当前分区恢复为活动分区
        case UpdateStatus::UPDATED_NEED_REBOOT:  {
          // 清理通过Perfs方式存储的kPrefsUpdateCompletedOnBootId
          // Remove the reboot marker so that if the machine is rebooted
          // after resetting to idle state, it doesn't go back to
          // UpdateStatus::UPDATED_NEED_REBOOT state.
          bool ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId);
    
          // 将当前分区设置为正常的活动分区
          // Update the boot flags so the current slot has higher priority.
          if (!boot_control_->SetActiveBootSlot(boot_control_->GetCurrentSlot()))
            ret_value = false;
    
          if (!ret_value) {
            return LogAndSetError(
                error,
                FROM_HERE,
                "Failed to reset the status to ");
          }
    
          // 更新系统状态为UpdateStatus::IDLE
          SetStatusAndNotify(UpdateStatus::IDLE);
          LOG(INFO) << "Reset status successful";
          return true;
        }
    
        // 其它状态都处于升级中,不能直接调用reset操作,需要先取消升级
        default:
          return LogAndSetError(
              error,
              FROM_HERE,
              "Reset not allowed in this state. Cancel the ongoing update first");
      }
    }
    

    3. ProcessingDone(), ProcessingStopped()ActionCompleted()

    这3个操作都是由ActionProcessor发起调用的。

    当ActionProcessor处理完Action队列中的所有Action后,通过delegate_->ProcessingDone()的方式通知UpdateAttempterAndroid类所有Action操作都完成了。此时就需要检查Action完成的退出状态,看看是否有错,并进行相应的处理:

    void UpdateAttempterAndroid::ProcessingDone(const ActionProcessor* processor,
                                                ErrorCode code) {
      LOG(INFO) << "Processing Done.";
    
      switch (code) {
        // 一切都好,升级顺利完成
        case ErrorCode::kSuccess:
          // 在磁盘上写入更新成功标记(实际上是设置kPrefsUpdateCompletedOnBootId)
          // Update succeeded.
          WriteUpdateCompletedMarker();
          prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
          DeltaPerformer::ResetUpdateProgress(prefs_, false);
          LOG(INFO) << "Update successfully applied, waiting to reboot.";
          break;
    
        // 我去,更新中出现了各种错误,那就复位各种状态吧
        case ErrorCode::kFilesystemCopierError:
        case ErrorCode::kNewRootfsVerificationError:
        case ErrorCode::kNewKernelVerificationError:
        case ErrorCode::kFilesystemVerifierError:
        case ErrorCode::kDownloadStateInitializationError:
          // Reset the ongoing update for these errors so it starts from the
          // beginning next time.
          DeltaPerformer::ResetUpdateProgress(prefs_, false);
          LOG(INFO) << "Resetting update progress.";
          break;
    
        default:
          // Ignore all other error codes.
          break;
      }
    
      // 结束升级,通知客户端和其它模块当前的升级状态
      TerminateUpdateAndNotify(code);
    }

    UpdateAttempterAndroid收到CancelUpdate通知,会将其转给ActionProcessor处理。ActionProcessor在完成操作后会反过来让UpdateAttempterAndroid使用ProcessingStopped去通知客户端和其它模块升级处于kUserCanceled状态:

    void UpdateAttempterAndroid::ProcessingStopped(
        const ActionProcessor* processor) {
      TerminateUpdateAndNotify(ErrorCode::kUserCanceled);
    }

    Action队列中的每个Action处理结束后,ActionProcessor调用ActionCompleted()通知当前Action已经执行完成,UpdateAttempterAndroid据此做一些系统更新工作,包括数据下载进度更新和客户端即其它模块的通知。

    void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor,
                                                 AbstractAction* action,
                                                 ErrorCode code) {
      // Reset download progress regardless of whether or not the download
      // action succeeded.
      const string type = action->Type();
      // 如果当前是DownloadAction,那就重新把download_progress_设置为0
      if (type == DownloadAction::StaticType()) {
        download_progress_ = 0;
      }
      if (code != ErrorCode::kSuccess) {
        // If an action failed, the ActionProcessor will cancel the whole thing.
        return;
      }
      // 通知所有客户端当前的状态
      if (type == DownloadAction::StaticType()) {
        SetStatusAndNotify(UpdateStatus::FINALIZING);
      }
    }

    4. BytesReceived(), ShouldCancel(), DownloadComplete()ProgressUpdate()

    这四个都是基于DownloadAction的动作,每次有数据到来时都需要根据情况使用ProgressUpdate()更新数据下载的进度,然后通知客户端。其中ShouldCancel()DownloadComplete()都是空操作,你也可以在这里自定义针对这些操作的行为。

    void UpdateAttempterAndroid::BytesReceived(uint64_t bytes_progressed,
                                               uint64_t bytes_received,
                                               uint64_t total) {
      double progress = 0;
      if (total)
        progress = static_cast<double>(bytes_received) / static_cast<double>(total);
      if (status_ != UpdateStatus::DOWNLOADING || bytes_received == total) {
        download_progress_ = progress;
        SetStatusAndNotify(UpdateStatus::DOWNLOADING);
      } else {
        ProgressUpdate(progress);
      }
    }
    
    bool UpdateAttempterAndroid::ShouldCancel(ErrorCode* cancel_reason) {
      // TODO(deymo): Notify the DownloadAction that it should cancel the update
      // download.
      return false;
    }
    
    void UpdateAttempterAndroid::DownloadComplete() {
      // Nothing needs to be done when the download completes.
    }
    
    void UpdateAttempterAndroid::ProgressUpdate(double progress) {
      // Self throttle based on progress. Also send notifications if progress is
      // too slow.
      if (progress == 1.0 ||
          progress - download_progress_ >= kBroadcastThresholdProgress ||
          TimeTicks::Now() - last_notify_time_ >=
              TimeDelta::FromSeconds(kBroadcastThresholdSeconds)) {
        download_progress_ = progress;
        SetStatusAndNotify(status_);
      }
    }

    5. SetupDownload()

    SetupDownload()根据ApplyPayload()接收到的参数计算下载数据(偏移量和大小等),等到后续分析DownloadAction时再详细分析。

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

    4. 总结

    至此,我们看到Update Engine的核心是Action机制,同时也在比较粗的粒度上分析了UpdateAttempterAndroid类的函数和功能,简单总结如下:

    • 通过ApplyPayload接收到升级请求时,创建一个ActionProcessor,并根据升级的各种参数构建了4个Action,然后将这4个Action交由ActionProcessor管理。ActionProcessor对内部Action队列的任务进行调度,每执行完一个Action,都会向UpdateAttempterAndroid类发送通知汇报状态。

    • Update Engine的Binder服务收到客户端的suspend, resume, cancelresetStatus请求时,将其交由UpdateAttempterAndroid处理。除了resetStatus操作外,其余的三个都会再交由ActionProcessor去执行,执行完成后通知UpdateAttempterAndroid类,然后后者通过回调函数通知客户端结果。

    一句话,UpdateAttempterAndroid类负责创建Action任务并交由ActionProcessor管理,再向客户端反馈系统和各种Action执行的状态。

    啰啰嗦嗦说了半天,最后发现,看完UpdateAttempterAndroid也还没有讲到系统到底是如何下载数据,如何验证,如何升级的。你说闹不闹心?

    不过有点安慰的是,知道整个Update Engine系统是按照任务组织的,包括Download, Verify和PostInstall三个阶段,如果哪个阶段有疑问,那就去相应Action对应的代码去看看吧:

    • DownloadAction: ./payload_consumer/download_action.cc
    • FilesystemVerifierAction: ./payload_consumer/filesystem_verifier_action.cc
    • PostinstallRunnerAction: ./payload_consumer/postinstall_runner_action.cc

    5. 联系和福利

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

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

      image

    展开全文
  • ANDROID UPDATE PROJECT

    千次阅读 2013-08-02 02:39:44
    在用ndk编译应用程序时,可以在外面ndk的根目录运行make APP=xxx,这些都可以在ndk的doc里面找到方法,但是这样只会编译出动态库,那java层怎么办呢?...对于第二种方法,需要先用android update project -p /d

    在用ndk编译应用程序时,可以在外面ndk的根目录运行make APP=xxx,这些都可以在ndk的doc里面找到方法,但是这样只会编译出动态库,那java层怎么办呢?

    1 用eclipse

    2 用ant

    对于 第一种方法,发现编译出来的动态库无法打包进apk中,需要时候手动拷贝进手机里:(,目前没有好的办法

    对于第二种方法,需要先用android update project -p /dir/to/ur/project

    然后进入project目录 执行ant release即可。

    如有BUILD FAILED,可能使用过别的版本的sdk toolupdate过project。解决方法是删除local.properties和build.xml然后再update一下project即可

     

    嗨,花了大半天的时间才发现这个问题。。。时光如梭啊 

    loop's blog
    展开全文
  • Android Update Engine分析(三)客户端update_engine_client 技术文章直入主题,展示结论,容易让人知其然,不知其所以然。 我个人更喜欢在文章中展示如何阅读代码,逐步分析解决问题的思路和过程。这样的思考...
  • 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 update-binary

    千次阅读 2015-02-03 10:27:10
    update-binary的源码位置 确认为下面目录 bootable\recovery\updater\ 确认过程 Android.mk 执行mm -B -j16编译一下,看看输出到了哪里 找到这个updater,然后解压zip包 unzip m201-ota-20150131....
  • Android Update Engine分析(一)Makefile

    千次阅读 多人点赞 2017-08-28 16:28:21
    写完《Android AB System OTA分析》系列后打算一口气将Update Engine也写了的,但一直由于各种借口,最终没有完成。后来6月份的时候陆陆续续读了Update Engine部分代码,记了点笔记,本打算等彻底读完再分享的,但...
  • ![图片说明](https://img-ask.csdn.net/upload/201601/05/1451974790_382106.png) 根据报错的意思好像是这个“make otapackage”命令没有定义,我在mk文件中查看好像确实没有,请问这个需要怎么定义?
  • android update project -p .

    千次阅读 2015-04-24 21:36:07

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 171,663
精华内容 68,665
关键字:

安卓update