Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 5.x OTA Update官方文檔(九、Sideloading-側面安裝機制)

Android 5.x OTA Update官方文檔(九、Sideloading-側面安裝機制)

編輯:關於Android編程

寫在前面:

本篇博文漏譯了很多,英文功底比較好的同學可以選擇閱讀原版文檔,如果方便請分享翻譯後文檔給大家,謝謝。

recovery有一個側面安裝(sideloading)機制來方便我們手動的安裝更新而不在使用OTA的方式。這種機制對於調試和維護是很有幫助的,特別是當我們無法正常啟動我們的系統的時候。

目前來說我們,有了這種機制,我們可以在設備的SD卡中載入更新包。在沒有啟動設備的情況下,我們可以通過電腦將更新包拷貝到SD卡上,然後再將SD卡插入到設備中進行側面安裝。而且如果Android設備當前並沒有可移動存儲設備(SD卡),這種sideloading機制同樣提供了另外兩種方案:從cache分區加載更新包、通過USB連接使用adb命令加載。

那麼我們如何使用這三種sideloading機制呢,我們來了解一下Device:InvokeMenuItem()函數。

 

APPLY_EXT.sideloading機制允許我們使用外部存儲設備來安裝更新。這就要求我們在recovery.fstab中必須定義“/sdcard”這個掛載點。這裡不推薦使用符號鏈接的方式在/data分區下模擬一個SD卡,因為通常情況下/data分區對recovery來說是不可見的,因為/data分區很有可能會被加密。我們在使用APPLY_EXT時,RecoveryUI會顯示出一個類似於文件管理器的界面來讓用戶選擇對應的更新包(僅限於.zip文件)。APPLY_CACHE.這種方式與APPLY_EXT比較相似。通常來說,/cache分區對recovery是可見的,因此我們可以選擇在安裝更新的時候從/cache分區下加載更新包。但是這種方式的實用性是很有限的,因為在實際運行的android系統中,/cache分區僅僅是部分有特殊權限進程才可以訪問的到,如果設備沒有啟動,/cache分區肯本無法進行讀寫。APPLY_ADB_SIDELOAD.這種方式允許用戶使用adb通過USB發送更新包給我們的設備。在recovery中有一套迷你版的adbd程序來幫助我們通過adb與主機建立連接,這套迷你版的adbd守護進程只支持一條adb命令:adb sideload filename.通過這條命令我們就可以進行更新包的校驗與安裝了。

 

 

關於APPLY_ADB_SIDELOAD的幾點說明:

 

僅支持USB傳輸當我們使用adb sideload mode時,接收更新包完成之後會重啟設備,且在該模式下除了fideload可用,其他所有命令均無效(logcat,reboot,push,pull,shell等)。如果在使用adb sideload mode時想要退出的話,可以選擇發送無效的更新包來中斷操作。因為如果revovery驗證更新包失敗的時候會終止安裝程序,重新進入RecoveryUI,那麼接下來我們可以進行一系列的按鍵操作。

 

下面為原文:

 

 

Updater


 

You can use device-specific code in the installation of the update package by providing your own extension functions that can be called from within your updater script. Here's a sample function for the tardis device:

device/yoyodyne/tardis/recovery/recovery_updater.c

#include 
#include 

#include "edify/expr.h"

Every extension function has the same signature. The arguments are the name by which the function was called, a State* cookie, the number of incoming arguments, and an array of Expr* pointers representing the arguments. The return value is a newly-allocated Value*.

Value* ReprogramTardisFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 2) {
        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
    }

Your arguments have not been evaluated at the time your function is called—your function's logic determines which of them get evaluated and how many times. Thus, you can use extension functions to implement your own control structures. Call Evaluate() to evaluate an Expr* argument, returning a Value*. If Evaluate() returns NULL, you should free any resources you're holding and immediately return NULL (this propagates aborts up the edify stack). Otherwise, you take ownership of the Value returned and are responsible for eventually callingFreeValue() on it.

Suppose the function needs two arguments: a string-valued key and a blob-valued image. You could read arguments like this:

   Value* key = EvaluateValue(state, argv[0]);
    if (key == NULL) {
        return NULL;
    }
    if (key->type != VAL_STRING) {
        ErrorAbort(state, "first arg to %s() must be string", name);
        FreeValue(key);
        return NULL;
    }
    Value* image = EvaluateValue(state, argv[1]);
    if (image == NULL) {
        FreeValue(key);    // must always free Value objects
        return NULL;
    }
    if (image->type != VAL_BLOB) {
        ErrorAbort(state, "second arg to %s() must be blob", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

Checking for NULL and freeing previously evaluated arguments can get tedious for multiple arguments. TheReadValueArgs() function can make this easier. Instead of the code above, you could have written this:

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

ReadValueArgs() doesn't do type-checking, so you must do that here; it's more convenient to do it with one ifstatement at the cost of producing a somewhat less specific error message when it fails. But ReadValueArgs()does handle evaluating each argument and freeing all the previously-evaluated arguments (as well as setting a useful error message) if any of the evaluations fail. You can use a ReadValueVarArgs() convenience function for evaluating a variable number of arguments (it returns an array of Value*).

After evaluating the arguments, do the work of the function:

   // key->data is a NUL-terminated string
    // image->data and image->size define a block of binary data
    //
    // ... some device-specific magic here to
    // reprogram the tardis using those two values ...

The return value must be a Value* object; ownership of this object will pass to the caller. The caller takes ownership of any data pointed to by this Value*—specifically the datamember.

In this instance, you want to return a true or false value to indicate success. Remember the convention that the empty string is false and all other strings are true. You must malloc a Value object with a malloc'd copy of the constant string to return, since the caller will free() both. Don't forget to call FreeValue() on the objects you got by evaluating your arguments!

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

The convenience function StringValue() wraps a string into a new Value object. Use to write the above code more succinctly:

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

To hook functions into the edify interpreter, provide the function Register_foo where foo is the name of the static library containing this code. Call RegisterFunction() to register each extension function. By convention, name device-specific functions device.whatever to avoid conflicts with future built-in functions added.

void Register_librecovery_updater_tardis() {
    RegisterFunction("tardis.reprogram", ReprogramTardisFn);
}

You can now configure the makefile to build a static library with your code. (This is the same makefile used to customize the recovery UI in the previous section; your device may have both static libraries defined here.)

device/yoyodyne/tardis/recovery/Android.mk

include $(CLEAR_VARS)
LOCAL_SRC_FILES := recovery_updater.c
LOCAL_C_INCLUDES += bootable/recovery

The name of the static library must match the name of the Register_libname function contained within it.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Finally, configure the build of recovery to pull in your library. Add your library to TARGET_RECOVERY_UPDATER_LIBS (which may contain multiple libraries; they all get registered). If your code depends on other static libraries that are not themselves edify extensions (i.e., they don't have aRegister_libname function), you can list those in TARGET_RECOVERY_UPDATER_EXTRA_LIBS to link them to updater without calling their (non-existent) registration function. For example, if your device-specific code wanted to use zlib to decompress data, you would include libz here.

device/yoyodyne/tardis/BoardConfig.mk

 [...]

# add device-specific extensions to the updater binary
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_tardis
TARGET_RECOVERY_UPDATER_EXTRA_LIBS +=

The updater scripts in your OTA package can now call your function as any other. To reprogram your tardis device, the update script might contain: tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . This uses the single-argument version of the built-in function package_extract_file(), which returns the contents of a file extracted from the update package as a blob to produce the second argument to the new extension function.

OTA package generation


The final component is getting the OTA package generation tools to know about your device-specific data and emit updater scripts that include calls to your extension functions.

First, get the build system to know about a device-specific blob of data. Assuming your data file is indevice/yoyodyne/tardis/tardis.dat, declare the following in your device's AndroidBoard.mk:

device/yoyodyne/tardis/AndroidBoard.mk

  [...]

$(call add-radio-file,tardis.dat)

You could also put it in an Android.mk instead, but then it must to be guarded by a device check, since all the Android.mk files in the tree are loaded no matter what device is being built. (If your tree includes multiple devices, you only want the tardis.dat file added when building the tardis device.)

device/yoyodyne/tardis/Android.mk

  [...]

# an alternative to specifying it in AndroidBoard.mk
ifeq (($TARGET_DEVICE),tardis)
  $(call add-radio-file,tardis.dat)
endif

These are called radio files for historical reasons; they may have nothing to do with the device radio (if present). They are simply opaque blobs of data the build system copies into the target-files .zip used by the OTA generation tools. When you do a build, tardis.dat is stored in the target-files.zip as RADIO/tardis.dat. You can call add-radio-file multiple times to add as many files as you want.

Python module

To extend the release tools, write a Python module (must be named releasetools.py) the tools can call into if present. Example:

device/yoyodyne/tardis/releasetools.py

import common

def FullOTA_InstallEnd(info):
  # copy the data into the package.
  tardis_dat = info.input_zip.read("RADIO/tardis.dat")
  common.ZipWriteStr(info.output_zip, "tardis.dat", tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

A separate function handles the case of generating an incremental OTA package. For this example, suppose you need to reprogram the tardis only when the tardis.dat file has changed between two builds.

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Module functions

You can provide the following functions in the module (implement only the ones you need).

FullOTA_Assertions()
Called near the start of generating a full OTA. This is a good place to emit assertions about the current state of the device. Do not emit script commands that make changes to the device.FullOTA_InstallBegin()
Called after all the assertions about the device state have passed but before any changes have been made. You can emit commands for device-specific updates that must run before anything else on the device has been changed.FullOTA_InstallEnd()
Called at the end of the script generation, after the script commands to update the boot and system partitions have been emitted. You can also emit additional commands for device-specific updates.IncrementalOTA_Assertions()
Similar to FullOTA_Assertions() but called when generating an incremental update package.IncrementalOTA_VerifyBegin()
Called after all assertions about the device state have passed but before any changes have been made. You can emit commands for device-specific updates that must run before anything else on the device has been changed.IncrementalOTA_VerifyEnd()
Called at the end of the verification phase, when the script has finished confirming the files it is going to touch have the expected starting contents. At this point nothing on the device has been changed. You can also emit code for additional device-specific verifications.IncrementalOTA_InstallBegin()
Called after files to be patched have been verified as having the expected before state but before any changes have been made. You can emit commands for device-specific updates that must run before anything else on the device has been changed.IncrementalOTA_InstallEnd()
Similar to its full OTA package counterpart, this is called at the end of the script generation, after the script commands to update the boot and system partitions have been emitted. You can also emit additional commands for device-specific updates.

Note: If the device loses power, OTA installation may restart from the beginning. Be prepared to cope with devices on which these commands have already been run, fully or partially.

Pass functions to info objects

Pass functions to a single info object that contains various useful items:

  • info.input_zip. (Full OTAs only) The zipfile.ZipFile object for the input target-files .zip.
  • info.source_zip. (Incremental OTAs only) The zipfile.ZipFile object for the source target-files .zip (the build already on the device when the incremental package is being installed).
  • info.target_zip. (Incremental OTAs only) The zipfile.ZipFile object for the target target-files .zip (the build the incremental package puts on the device).
  • info.output_zip. Package being created; a zipfile.ZipFile object opened for writing. Use common.ZipWriteStr(info.output_zip, filename, data) to add a file to the package.
  • info.script. Script object to which you can append commands. Call info.script.AppendExtra(script_text) to output text into the script. Make sure output text ends with a semicolon so it does not run into commands emitted afterwards.

    For details on the info object, refer to the Python Software Foundation documentation for ZIP archives.

    Specify module location

    Specify the location of your device's releasetools.py script in your BoardConfig.mk file:

    device/yoyodyne/tardis/BoardConfig.mk

     [...]
    
    TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

    If TARGET_RELEASETOOLS_EXTENSIONS is not set, it defaults to the $(TARGET_DEVICE_DIR)/../common directory (device/yoyodyne/common in this example). It's best to explicitly define the location of the releasetools.py script. When building the tardis device, the releasetools.py script is included in the target-files .zip file (META/releasetools.py ).

    When you run the release tools (either img_from_target_files or ota_from_target_files), the releasetools.py script in the target-files .zip, if present, is preferred over the one from the Android source tree. You can also explicitly specify the path to the device-specific extensions with the -s (or --device_specific) option, which takes the top priority. This enables you to correct errors and make changes in the releasetools extensions and apply those changes to old target-files.

    Now, when you run ota_from_target_files, it automatically picks up the device-specific module from the target_files .zip file and uses it when generating OTA packages:

    % ./build/tools/releasetools/ota_from_target_files \
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip incremental_ota_update.zip
    unzipping target target-files...
    using device-specific extensions from target_files
    unzipping source target-files...
       [...]
    done.

    Alternatively, you can specify device-specific extensions when you run ota_from_target_files.

    % ./build/tools/releasetools/ota_from_target_files \
        -s device/yoyodyne/tardis \  # specify the path to device-specific extensions
        -i PREVIOUS-tardis-target_files.zip \
        dist_output/tardis-target_files.zip incremental_ota_update.zip
    unzipping target target-files...
    loaded device-specific extensions from device/yoyodyne/tardis
    unzipping source target-files...
       [...]
    done.

    Note: For a complete list of options, refer to the ota_from_target_files comments inbuild/tools/releasetools/ota_from_target_files.

    Sideloading


    Recovery has a sideloading mechanism for manually installing an update package without downloading it over-the-air by the main system. Sideloading is useful for debugging or making changes on devices where the main system can't be booted.

    Historically, sideloading has been done through loading packages off the device's SD card; in the case of a non-booting device, the package can be put onto the SD card using some other computer and then the SD card inserted into the device. To accommodate Android devices without removable external storage, recovery supports two additional mechanisms for sideloading: loading packages from the cache partition, and loading them over USB using adb.

    To invoke each sideload mechanism, your device's Device::InvokeMenuItem() method can return the following values of BuiltinAction:

    • APPLY_EXT. Sideload an update package from external storage ( /sdcard directory). Your recovery.fstab must define the /sdcard mount point. This is not usable on devices that emulate an SD card with a symlink to/data (or some similar mechanism). /data is typically not available to recovery because it may be encrypted. The recovery UI displays a menu of .zip files in /sdcard and allows the user to select one.
    • APPLY_CACHE. Similar to loading a package from /sdcard except that the /cache directory (which is always available to recovery) is used instead. From the regular system, /cache is only writable by privileged users, and if the device isn't bootable then the /cache directory can't be written to at all (which makes this mechanism of limited utility).
    • APPLY_ADB_SIDELOAD. Allows user to send a package to the device via a USB cable and the adb development tool. When this mechanism is invoked, recovery starts up its own mini version of the adbd daemon to let adb on a connected host computer talk to it. This mini version supports only a single command: adb sideloadfilename. The named file is sent from the host machine to the device, which then verifies and installs it just as if it had been on local storage.

      A few caveats:

      • Only USB transport is supported.
      • If your recovery runs adbd normally (usually true for userdebug and eng builds), that will be shut down while the device is in adb sideload mode and will be restarted when adb sideload has finished receiving a package. While in adb sideload mode, no adb commands other than sideload work ( logcat, reboot, push, pull , shell, etc. all fail).
      • You cannot exit adb sideload mode on the device. To abort, you can send /dev/null (or anything else that's not a valid package) as the package, and then the device will fail to verify it and stop the installation procedure. The RecoveryUI implementation's CheckKey() method will continue to be called for keypresses, so you can provide a key sequence that reboots the device and works in adb sideload mode.
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved