如果你認為本系列文章對你有所幫助,請大家有錢的捧個錢場,點選此處贊助,贊助額1元起步,多少随意
鋒影
email:[email protected]
1 總述
為了友善客戶日後的固件更新,本周研究了一下android的recovery模式。網上有不少這類的資料,但都比較繁雜,沒有一個系統的介紹與認識,在這裡将網上所找到的和自己通過查閱代碼所掌握的東西整理出來,給大家一個參考!
2 Android啟動過程
在這裡有必要理一下android的啟動過程:
圖1 android啟動過程
系統上電之後,首先是完成一系列的初始化過程,如cpu、序列槽、中斷、timer、DDR等等硬體裝置,然後接着加載boot default environmet,為後面核心的加載作好準備。在一些系統啟動必要的初始完成之後,将判斷是否要進入recovery模式,從圖1中可以看出,進入recovery模式有兩種情況。一種是檢測到有組合按鍵按下時;另一種是檢測到cache/recovery目錄下有command這個檔案,這個檔案有内容有它特定的格式,将在後面講到。
3 Uboot啟動
下面來看看uboot中lib_arm/board.c這個檔案中的start_armboot這個函數,這個函數在start.s這個彙編檔案中完成堆棧等一些基礎動作之後被調用,進入到c的代碼中,start_armboot部分代碼如下:
void start_armboot (void)
{
.
.
.
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
#ifdef CONFIG_ANDROID_RECOVERY
check_recovery_mode();
#endif
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
}
init_sequence是一個函數結構體指針,裡面存放的是一些必備的初始化函數,其代碼如下:
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
board_init, /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
#endif
timer_init, /* initialize timer */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
arm_pci_init,
#endif
display_dram_config,
NULL,
};
我們來看看env_init這個函數,其代碼如下:
int env_init(void)
{
/* use default */
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
#ifdef CONFIG_DYNAMIC_MMC_DEVNO
extern int get_mmc_env_devno(void);
mmc_env_devno = get_mmc_env_devno();
#else
mmc_env_devno = CONFIG_SYS_MMC_ENV_DEV;
#endif
return 0;
}
可以看出在這裡将default_environment加載進入系統,default_environment對應的部分代碼如下:
uchar default_environment[] = {
.
.
.
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
};
而CONFIG_EXTRA_ENV_SETTINGS則是在我們對應的BSP的頭檔案中定義了,如下:
#define CONFIG_EXTRA_ENV_SETTINGS \
"netdev=eth0\0" \
"ethprime=FEC0\0" \
"bootfile=uImage\0" \
"loadaddr=0x70800000\0" \
"rd_loadaddr=0x70D00000\0" \
"bootargs=console=ttymxc0 init=/init " \
"androidboot.console=ttymxc0 video=mxcdi1fb:RGB666,XGA " \
"ldb=di1 di1_primary pmem=32M,64M fbmem=5M gpu_memory=64M\0" \
"bootcmd_SD=mmc read 0 ${loadaddr} 0x800 0x2000;" \
"mmc read 0 ${rd_loadaddr} 0x3000 0x300\0" \
"bootcmd=run bootcmd_SD; bootm ${loadaddr} ${rd_loadaddr}\0" \
再來看看check_recovery_mode這個函數中的代碼,具體代碼如下:
/* export to lib_arm/board.c */
void check_recovery_mode(void)
{
if (check_key_pressing())
setup_recovery_env();
else if (check_recovery_cmd_file()) {
puts("Recovery command file founded!\n");
setup_recovery_env();
}
}
可以看到在這裡通過check_key_pressing這個函數來檢測組合按鍵,當有對應的組合按鍵按下時,将會進入到recovery模式,這也正是各大android論壇裡講到刷機時都會提到的power+音量加鍵進入recovery模式的原因。那麼check_recovery_cmd_file又是在什麼情況下執行的呢?這個也正是這篇文章所要講的内容之處。
先來看看check_recovery_cmd_file這個函數中的如下這段代碼:
int check_recovery_cmd_file(void)
{
.
.
.
switch (get_boot_device()) {
case MMC_BOOT:
case SD_BOOT:
{
for (i = 0; i < 2; i++) {
block_dev_desc_t *dev_desc = NULL;
struct mmc *mmc = find_mmc_device(i);
dev_desc = get_dev("mmc", i);
if (NULL == dev_desc) {
printf("** Block device MMC %d not supported\n", i);
continue;
}
mmc_init(mmc);
if (get_partition_info(dev_desc, CONFIG_ANDROID_CACHE_PARTITION_MMC,
&info)) {
printf("** Bad partition %d **\n",CONFIG_ANDROID_CACHE_PARTITION_MMC);
continue;
}
part_length = ext2fs_set_blk_dev(dev_desc, CONFIG_ANDROID_CACHE_PARTITION_MMC);
if (part_length == 0) {
printf("** Bad partition - mmc %d:%d **\n", i, CONFIG_ANDROID_CACHE_PARTITION_MMC);
ext2fs_close();
continue;
}
if (!ext2fs_mount(part_length)) {
printf("** Bad ext2 partition or "
"disk - mmc %d:%d **\n",
i, CONFIG_ANDROID_CACHE_PARTITION_MMC);
ext2fs_close();
continue;
}
filelen = ext2fs_open(CONFIG_ANDROID_RECOVERY_CMD_FILE);
ext2fs_close();
break;
}
}
break;
.
.
.
}
主要來看看下面這個ext2fs_open所打開的内容,CONFIG_ANDROID_RECOVERY_CMD_FILE,這個正是上面所提到的rocovery cmd file的宏定義,内容如下:
#define CONFIG_ANDROID_RECOVERY_CMD_FILE "/recovery/command"
當檢測到有這個檔案存在時,将會進入到setup_recovery_env這個函數中,其相應的代碼如下:
void setup_recovery_env(void)
{
char *env, *boot_args, *boot_cmd;
int bootdev = get_boot_device();
boot_cmd = supported_reco_envs[bootdev].cmd;
boot_args = supported_reco_envs[bootdev].args;
if (boot_cmd == NULL) {
printf("Unsupported bootup device for recovery\n");
return;
}
printf("setup env for recovery..\n");
env = getenv("bootargs_android_recovery");
/* Set env to recovery mode */
/* Only set recovery env when these env not exist, give user a
* chance to change their recovery env */
if (!env)
setenv("bootargs_android_recovery", boot_args);
env = getenv("bootcmd_android_recovery");
if (!env)
setenv("bootcmd_android_recovery", boot_cmd);
setenv("bootcmd", "run bootcmd_android_recovery");
}
在這裡主要是将bootcmd_android_recovery這個環境變量加到uboot啟動的environment中,這樣當系統啟動加載完root fs之後将不會進入到android的system中,而是進入到了recovery這個輕量級的小UI系統中。
下面我們來看看為什麼在uboot的啟動環境變量中加入bootcmd_android_recovery這些啟動參數的時候,系統就會進入到recovery模式下而不是android system,先看看bootcmd_android_recovery相應的參數:
#define CONFIG_ANDROID_RECOVERY_BOOTARGS_MMC \
"setenv bootargs ${bootargs} init=/init root=/dev/mmcblk1p4" \
"rootfs=ext4 video=mxcdi1fb:RGB666,XGA ldb=di1 di1_primary"
#define CONFIG_ANDROID_RECOVERY_BOOTCMD_MMC \
"run bootargs_android_recovery;" \
"mmc read 0 ${loadaddr} 0x800 0x2000;bootm"
可以看到在進入recovery模式的時候這裡把root的分區設定成了/dev/mmcblk1p4,再來看看在系統燒錄的時候對整個SD卡的分區如下:
sudo mkfs.vfat -F 32 ${NODE}${PART}1 -n sdcards
sudo mkfs.ext4 ${NODE}${PART}2 -O ^extent -L system
sudo mkfs.ext4 ${NODE}${PART}4 -O ^extent -L recovery
sudo mkfs.ext4 ${NODE}${PART}5 -O ^extent -L data
sudo mkfs.ext4 ${NODE}${PART}6 -O ^extent -L cache
這裡NODE = /dev/mmcblk1為挂載點,PART = p或者為空,作為分區的檢測。可以看出上面在給recovery分區的時候,用的是/dev/mmcblk1p4這個分區,是以當設定了recovery啟動模式的時候,root根目錄就被挂載到/dev/mmcblk1p4這個recovery分區中來,進而進入recovery模式。
4 recovery
關于android的recovery網上有各種版本的定義,這裡我總結一下:所謂recovery是android下加入的一種特殊工作模式,有點類似于windows下的gost,系統進入到這種模式下時,可以在這裡通過按鍵選擇相應的操作菜單實作相應的功能,比如android系統和資料區的快速格式化(wipe);系統和使用者資料的備份和恢複;通過sd卡重新整理的rom等等。典型的recovery界面如下:
圖2 recovery界面
Recovery的源代碼在bootable/recovery這個目錄下面,主要來看看recovery.c這個檔案中的main函數:
Int main(int argc, char **argv) {
.
.
.
ui_init();
ui_set_background(BACKGROUND_ICON_INSTALLING);
load_volume_table();
.
.
.
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 'p': previous_runs = atoi(optarg); break;
case 's': send_intent = optarg; break;
case 'u': update_package = optarg; break;
case 'w': wipe_data = wipe_cache = 1; break;
case 'c': wipe_cache = 1; break;
case 'e': encrypted_fs_mode = optarg; toggle_secure_fs = 1; break;
case 't': ui_show_text(1); break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}
device_recovery_start();
.
.
.
if (update_package)
{
// For backwards compatibility on the cache partition only, if
// we're given an old 'root' path "CACHE:foo", change it to
// "/cache/foo".
if (strncmp(update_package, "CACHE:", 6) == 0)
{
int len = strlen(update_package) + 10;
char* modified_path = malloc(len);
strlcpy(modified_path, "/cache/", len);
strlcat(modified_path, update_package+6, len);
printf("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
//for update from "/mnt/sdcard/update.zip",but at recovery system is "/sdcard" so change it to "/sdcard"
//ui_print("before:[%s]\n",update_package);
if (strncmp(update_package, "/mnt", 4) == 0)
{
//jump the "/mnt"
update_package +=4;
}
ui_print("install package from[%s]\n",update_package);
}
printf("\n");
property_list(print_property, NULL);
printf("\n");
int status = INSTALL_SUCCESS;
.
.
.
// Recovery strategy: if the data partition is damaged, disable encrypted file systems.
// This preventsthe device recycling endlessly in recovery mode.
.
.
.
if (update_package != NULL)
{
status = install_package(update_package);
if (status != INSTALL_SUCCESS)
ui_print("Installation aborted.\n");
else
{
erase_volume("/data");
erase_volume("/cache");
}
} else if (wipe_data) {
if (device_wipe_data()) status = INSTALL_ERROR;
if (erase_volume("/data")) status = INSTALL_ERROR;
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n");
} else if (wipe_cache) {
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n");
} else {
status = INSTALL_ERROR; // No command specified
}
if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
//Xandy modify for view the install infomation
//if (status != INSTALL_SUCCESS || ui_text_visible())
if(status != INSTALL_SUCCESS)
{
prompt_and_wait();
}
// Otherwise, get ready to boot the main system...
finish_recovery(send_intent);
ui_print("Rebooting...\n");
sync();
reboot(RB_AUTOBOOT);
return EXIT_SUCCESS;
}
在這裡首先完成recovery模式輕量級的UI系統初始化,設定背景圖檔,然後對輸入的參數格式化,最後根據輸入的參數進行相應的操作,如:安裝新的ROM、格式化(wipe)data及cache分區等等;值得注意的是重新整理ROM的時候,要制作相應的update.zip的安裝包,這個在最後一章節講述,這裡遇到的一個問題是在recovery模式下sd卡的挂載點為/sdcard而不是android系統下的/mnt/sdcard,是以我在這裡通過:
//for update from "/mnt/sdcard/update.zip",but at recovery system is "/sdcard" so change it to "/sdcard"
//ui_print("before:[%s]\n",update_package);
if (strncmp(update_package, "/mnt", 4) == 0)
{
//jump the "/mnt"
update_package +=4;
}
這樣的操作跳過了上層傳過來的/mnt這四個字元。另外一個值得一提的是,傳入這裡的這些參數都是從/cache/recovery/command這個檔案中提取。具體對command檔案的解析過程這裡不再講述,可能通過檢視recovery.c這個檔案中的get_args函數。
那麼command這個檔案是在什麼情況下建立的呢?下面我們就來看看吧!
5 恢複出廠設定和固件更新
在android的系統裝置中進入“隐私權->恢複出廠設定->重置手機”将為進入到恢複出廠設定的狀态,這時将會清除data、cache分區中的所有使用者資料,使得系統重新開機後和剛刷機時一樣了。另外為了友善操作我們還可在“隐私權->固件更新->重新整理ROM”這裡加入了固件更新這一項。
在講述這些内容之前,我們有必要來看看/cache/recovery/command這個檔案相應的一些recovery指令,這些指令都由android系統寫入。所有的指令如下:
* --send_intent=anystring ―― write the text out to recovery.intent
* --update_package=root:path —— verify install an OTA package file
* --wipe_data —— erase user data (and cache), then reboot
* --wipe_cache —— wipe cache (but not user data), then reboot
5.1 恢複出廠設定
在frameworks/base/services/java/com/android/server/masterClearReceiver.java
這個檔案中有如下代碼:
public class MasterClearReceiver extends BroadcastReceiver {
private static final String TAG = "MasterClear";
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
if (!"google.com".equals(intent.getStringExtra("from"))) {
Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");
return;
}
}
Slog.w(TAG, "!!! FACTORY RESET !!!");
// The reboot call is blocking, so we need to do it on another thread.
Thread thr = new Thread("Reboot") {
@Override
public void run() {
try {
if (intent.hasExtra("enableEFS")) {
RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false));
} else {
RecoverySystem.rebootWipeUserData(context);
}
Log.wtf(TAG, "Still running after master clear?!");
} catch (IOException e) {
Slog.e(TAG, "Can't perform master clear/factory reset", e);
}
}
};
thr.start();
}
}
當app中操作了“恢複出廠設定”這一項時,将發出廣播,這個廣播将在這裡被監聽,然後進入到恢複出廠設定狀态,我們來看看rebootWipeUserData這個方法的代碼:
public static void rebootWipeUserData(Context context) throws IOException {
final ConditionVariable condition = new ConditionVariable();
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
context.sendOrderedBroadcast(intent, android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
condition.open();
}
}, null, 0, null, null);
// Block until the ordered broadcast has completed.
condition.block();
bootCommand(context, "--wipe_data");
}
我們可以看到在這裡參入了“--wipe_data”這個參數,并把這條指令寫入到command這個檔案中去了,在進入recovery模式的時候解析到這條指令時就會清除data和cache中的資料了。
再來看看bootCommand這個方法裡的代碼:
private static void bootCommand(Context context, String arg) throws IOException {
RECOVERY_DIR.mkdirs(); // In case we need it
COMMAND_FILE.delete(); // In case it's not writable
LOG_FILE.delete();
FileWriter command = new FileWriter(COMMAND_FILE);
try {
command.write(arg);
command.write("\n");
} finally {
command.close();
}
// Having written the command file, go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot("recovery");
throw new IOException("Reboot failed (no permissions?)");
}
其中COMMAND_FILE這個成員的定義如下:
/** Used to communicate with recovery. See bootable/recovery/recovery.c. */
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
至此恢複出廠設定的指令就寫入了recovery cmd file中去了,通過pm.reboot(“recovery”);重新開機系統,系統就自動進入到recovery模式自動清除使用者資料後再重新開機系統。
5.2 固件更新
固件更新的流程和恢複出廠設定差不多,不同之處是入command這個檔案中寫入的指令不一樣,下面是恢複出廠設定時的寫指令的代碼:
public static void installPackage(Context context, File packageFile)
throws IOException {
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
String arg = "--update_package=" + filename;
bootCommand(context, arg);
}
這裡的packageFile是由上層app傳入的,内容如下:
File packageFile = new File("/sdcard/update.zip");
RecoverySystem.installPackage(context, packageFile);
這樣當系統重新開機進入到recovery模式時将會自動查找sdcard的根目錄下是否有update.zip這個檔案,如果有将會進入到update狀态,否則會提示無法找到update.zip!
至此我們已經明白了android的整個recovery流程,下面将講講update.zip也就是各大論壇裡講到的ROM的制作過程。
6 ROM的制作
我們解壓update.zip這個檔案,可發現它一般打包了如下這幾個檔案:
圖3 ROM包中的内容
或者沒有updates而是system這個目錄,不同的原因是我這裡在updates裡放置的是system.img等鏡像檔案,這些檔案都由源碼編譯而來。而如果是system目錄,這裡一般放的是android系統的system目錄下的内容,可以是整個android系統的system目錄,也可以是其中的一部分内容,如一些so庫等等,這樣為更新檔的釋出提供了一個很好的解決辦法,不需要更新整個系統,隻需要更新一部分内容就可以了!
來看看META-INF/com/google/android這個目錄下的内容,在這裡就兩個檔案,一個是可執行的exe檔案update-binary,這個檔案在進入update狀态的用于控制ROM的燒入,具體的代碼在recovery下的install.c檔案中的try_update_binary這個函數中;另一個是updater-script,這個檔案裡是一些腳本程式,具體的代碼如下:
# Mount system for check figurepoint etc.
# mount("ext4", "EMMC","/dev/block/mmcblk0p2", "/system");
# Make sure Check system image figurepoint first.
# uncomment below lines to check
# assert(file_getprop("/system/build.prop", "ro.build.fingerprint") == "freescale/imx53_evk/imx53_evk/imx53_evk:2.2/FRF85B/eng.b33651.20100914.145340:eng/test-keys");
# assert(getprop("ro.build.platform) == "imx5x");
# unmount("/system");
show_progress(0.1, 5);
package_extract_dir("updates", "/tmp");
#Format system/data/cache partition
ui_print("Format disk...");
format("ext4","EMMC","/system");
format("ext4","EMMC","/data");
format("ext4","EMMC","/cache");
show_progress(0.2, 10);
# Write u-boot to 1K position.
# u-boot binary should be a no padding uboot!
# For eMMC(iNand) device, needs to unlock boot partition.
ui_print("writting u-boot...");
sysfs_file_write(" /sys/class/mmc_host/mmc0/mmc0:0001/boot_config", "1");
package_extract_file("files/u-boot.bin", "/tmp/u-boot.bin");
#ui_print("Clean U-Boot environment...");
show_progress(0.2, 5);
#simple_dd("/dev/zero","/dev/block/mmcblk0",2048);
simple_dd("/tmp/u-boot.bin", "/dev/block/mmcblk0", 2048);
#access user partition,and enable boot partion1 to boot
sysfs_file_write("/sys/class/mmc_host/mmc0/mmc0:0001/boot_config", "8");
#Set boot width is 8bits
sysfs_file_write("/sys/class/mmc_host/mmc0/mmc0:0001/boot_bus_config", "2");
show_progress(0.2, 5);
ui_print("extract kernel image...");
package_extract_file("files/uImage", "/tmp/uImage");
# Write uImage to 1M position.
ui_print("writting kernel image");
simple_dd("/tmp/uImage", "/dev/block/mmcblk0", 1048576);
ui_print("extract uramdisk image...");
package_extract_file("files/uramdisk.img", "/tmp/uramdisk.img");
# Write uImage to 1M position.
ui_print("writting uramdisk image");
simple_dd("/tmp/uramdisk", "/dev/block/mmcblk0", 6291456);
show_progress(0.2, 50);
# You can use two way to update your system which using ext4 system.
# dd hole system.img to your mmcblk0p2 partition.
package_extract_file("files/system.img", "/tmp/system.img");
ui_print("upgrading system partition...");
simple_dd("/tmp/system.img", "/dev/block/mmcblk0p2", 0);
show_progress(0.1, 5);
相應的腳本指令可在說明可對應源碼可在recovery包中的install.c這個檔案中找到。
在bootable/recovery/etc下有原始版的腳本代碼update-script,但在recovery下的updater.c這個檔案中有如下定義:
// Where in the package we expect to find the edify script to execute.
// (Note it's "updateR-script", not the older "update-script".)
#define SCRIPT_NAME "META-INF/com/google/android/updater-script"
所在使用這個原版的腳本的時候要将update-script更成updater-script,需要注意!
我們可以發現在bootable/recovery/etcMETA-INFO/com/google/android目錄下少了一個update-binary的執行檔案,在out/target/product/YOU_PRODUCT/system/bin下面我們可以找到updater,隻要将其重名字為update-binary就可以了!
有了這些準備工作,我們就可以開始制作一個我們自己的ROM了,具體步驟如下:
* [email protected]:~$ mkdir recovery
* [email protected]:~$ cd recovery 然後将上面提到的bootable/recovery/etc下的所有内容拷貝到目前目錄下并删掉init.rc這個檔案
* 編譯./META-INF/com/google/android/updater-script這個檔案使達到我們想要的燒寫控制,如果是燒寫system.img這樣的鏡像檔案,可以直接用我上面提到的updater-script這個腳本代碼。
* 拷貝相應的需要制作成ROM的android檔案到updates目錄或者system目錄下,這個得根據系統的需要決定。
* [email protected]:~/recovery$ mkdir res
* [email protected]:~/recovery$ ~/myandroid/out/host/linux-x86/framework/dumpkey.jar ~/myandroid/build/target/product/security/testkey.x509.pem > res/keys 這裡建立一個目錄用于存儲系統的key值
* zip /tmp/recovery.zip -r ./META-INF ./updates ./res 将所有檔案打包
* java -jar ./tools/signapk.jar -w ./tools/testkey.x509.pem ./tools/testkey.pk8 /tmp/recovery.zip update.zip 我在recovery目錄下建立了一個tools目錄,裡面放置了sygnapk.jar、testkey.pk8、testkey.x509.pem這幾個檔案用于java簽名時用
經過上面這幾步之後就會在recovery目錄生成一個update.zip的檔案,這個就是我們自己制作的ROM檔案,将它拷到sdcard的根目錄下,在系統設定裡操作進入到“固件更新狀态”,等到系統重新開機時,就會看到已經開始自行格式化data和cache分區,稍後就開始出現進度條向相應分區裡燒寫uboot、kernel、android system的檔案了!
圖4 燒入新ROM