Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1306894
  • 博文数量: 478
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4833
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-28 11:12
文章分类

全部博文(478)

文章存档

2019年(1)

2018年(27)

2017年(21)

2016年(171)

2015年(258)

我的朋友

分类: Android平台

2015-12-10 12:59:21

写在前面:

    本篇博文漏译了很多,英文功底比较好的同学可以选择阅读原版文档,如果方便请分享翻译后文档给大家,谢谢。

    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, filenamedata) 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.
阅读(1116) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~