全部博文(478)
分类: Android平台
2015-12-10 12:59:21
写在前面:
本篇博文漏译了很多,英文功底比较好的同学可以选择阅读原版文档,如果方便请分享翻译后文档给大家,谢谢。
recovery有一个侧面安装(sideloading)机制来方便我们手动的安装更新而不在使用OTA的方式。这种机制对于调试和维护是很有帮助的,特别是当我们无法正常启动我们的系统的时候。
目前来说我们,有了这种机制,我们可以在设备的SD卡中载入更新包。在没有启动设备的情况下,我们可以通过电脑将更新包拷贝到SD卡上,然后再将SD卡插入到设备中进行侧面安装。而且如果Android设备当前并没有可移动存储设备(SD卡),这种sideloading机制同样提供了另外两种方案:从cache分区加载更新包、通过USB连接使用adb命令加载。
那么我们如何使用这三种sideloading机制呢,我们来了解一下Device:InvokeMenuItem()函数。
关于APPLY_ADB_SIDELOAD的几点说明:
下面为原文:
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.
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.
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"));""")
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 a single info object that contains various useful items:
For details on the info object, refer to the Python Software Foundation documentation for ZIP archives.
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.
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:
A few caveats: