恢復系統包括多個用於插入裝置特定代碼的掛鉤,以便 OTA 更新還可以更新 Android 系統以外的裝置部分(例如基頻或無線電處理器)。
以下部分和範例客製了yoyodyne供應商生產的tardis設備。
分區圖
從 Android 2.3 開始,平台支援 eMMc 快閃裝置以及在這些裝置上運行的 ext4 檔案系統。它還支援記憶體技術設備 (MTD) 閃存設備和舊版本的 yaffs2 檔案系統。
分區映射檔案由TARGET_RECOVERY_FSTAB指定;該檔案由恢復二進位檔案和套件建置工具使用。您可以在 BoardConfig.mk 的 TARGET_RECOVERY_FSTAB 中指定映射檔案的名稱。
範例分區映射檔案可能如下所示:
device/yoyodyne/tardis/recovery.fstab
# mount point fstype device [device2] [options (3.0+ only)] /sdcard vfat /dev/block/mmcblk0p1 /dev/block/mmcblk0 /cache yaffs2 cache /misc mtd misc /boot mtd boot /recovery emmc /dev/block/platform/s3c-sdhci.0/by-name/recovery /system ext4 /dev/block/platform/s3c-sdhci.0/by-name/system length=-4096 /data ext4 /dev/block/platform/s3c-sdhci.0/by-name/userdata
除了可選的/sdcard
之外,必須定義此範例中的所有安裝點(設備還可以添加額外的分割區)。有五種受支援的檔案系統類型:
- yaffs2
- MTD 快閃記憶體裝置上的 yaffs2 檔案系統。 「device」必須是 MTD 分割區的名稱,且必須出現在
/proc/mtd
中。 - MTD
- 原始 MTD 分割區,用於可開機分割區,例如開機和復原。 MTD其實並沒有被掛載,而是以掛載點作為定位分割區的關鍵。 「device」必須是
/proc/mtd
中 MTD 分割區的名稱。 - 外部4
- eMMc 快閃記憶體裝置上的 ext4 檔案系統。 「device」必須是區塊裝置的路徑。
- 埃姆卡
- 原始 eMMc 區塊設備,用於可引導分割區,例如引導和復原。與 mtd 類型類似,eMMc 從未實際掛載,但掛載點字串用於在表中定位裝置。
- 脂肪組織
- 位於區塊裝置之上的 FAT 檔案系統,通常用於 SD 卡等外部儲存。該設備是塊設備; device2 是系統在安裝主裝置失敗時嘗試安裝的第二個區塊裝置(為了與可能使用或不使用分割表格式化的 SD 卡相容)。
所有分區必須安裝在根目錄中(即安裝點值必須以斜線開頭且沒有其他斜線)。此限制僅適用於復原中掛載檔案系統;主系統可以自由地將它們安裝在任何地方。目錄
/boot
、/recovery
和/misc
應該是原始類型(mtd 或 emmc),而目錄/system
、/data
、/cache
和/sdcard
(如果可用)應該是檔案系統類型(yaffs2、ext4 或脂肪)。
從 Android 3.0 開始,recovery.fstab 檔案獲得了一個額外的可選欄位options 。目前唯一定義的選項是length ,它允許您明確指定分割區的長度。此長度在重新格式化分割區時使用(例如,在資料清除/恢復原廠設定作業期間用於使用者資料分割區,或在安裝完整 OTA 套件期間用於系統分割區)。如果長度值為負數,則透過將長度值新增至真實分割區大小來取得要格式化的大小。例如,設定“length=-16384”表示當該分割區重新格式化時,該分割區的最後16k不會被覆蓋。這支援諸如用戶資料分割加密等功能(其中加密元資料儲存在不應被覆蓋的分割區末尾)。
注意: device2和options欄位是可選的,會在解析中產生歧義。如果該行第四個欄位中的條目以「/」字元開頭,則將其視為device2條目;如果條目不以“/”字元開頭,則將其視為選項欄位。
開機動畫
裝置製造商能夠自訂 Android 裝置啟動時顯示的動畫。為此,請建立一個根據bootanimation 格式的規範進行組織和定位的 .zip 檔案。
對於Android Things設備,您可以在 Android Things 控制台中上傳壓縮文件,以便將映像包含在所選產品中。
注意:這些圖像必須符合 Android品牌準則。
恢復介面
為了支援具有不同可用硬體(實體按鈕、LED、螢幕等)的設備,您可以自訂恢復介面以顯示狀態並存取每個設備的手動操作隱藏功能。
您的目標是使用幾個 C++ 物件建立一個小型靜態庫,以提供特定於裝置的功能。預設使用bootable/recovery/default_device.cpp
文件,在為您的裝置編寫該文件的版本時,它是一個很好的複製起點。
注意:您可能會在此處看到一條訊息,指出「無命令」 。若要切換文本,請在按下音量增大按鈕的同時按住電源按鈕。如果您的裝置沒有這兩個按鈕,請長按任何按鈕來切換文字。
device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h> #include "common.h" #include "device.h" #include "screen_ui.h"
標題和項目功能
Device 類別需要傳回隱藏恢復選單中出現的標頭和項目的函數。標題說明如何操作選單(即變更/選擇突出顯示項目的控制項)。
static const char* HEADERS[] = { "Volume up/down to move highlight;", "power button to select.", "", NULL }; static const char* ITEMS[] = {"reboot system now", "apply update from ADB", "wipe data/factory reset", "wipe cache partition", NULL };
注意:長行會被截斷(而不是換行),因此請記住裝置螢幕的寬度。
自訂CheckKey
接下來,定義裝置的 RecoveryUI 實作。此範例假設tardis設備有螢幕,因此您可以從內建 ScreenRecoveryUIimplementation 繼承(請參閱沒有螢幕的裝置的說明。)從 ScreenRecoveryUI 自訂的唯一函數是CheckKey()
,它執行初始非同步鍵處理:
class TardisUI : public ScreenRecoveryUI { public: virtual KeyAction CheckKey(int key) { if (key == KEY_HOME) { return TOGGLE; } return ENQUEUE; } };
關鍵常數
KEY_* 常數在linux/input.h
中定義。無論復原過程中發生什麼情況,都會呼叫CheckKey()
:選單關閉時、選單開啟時、軟體包安裝期間、使用者資料擦除期間等。它可以傳回四個常數之一:
- 切換。開啟或關閉選單和/或文字登入的顯示
- 重啟。立即重新啟動設備
- 忽略。忽略這個按鍵
- 入隊。將此按鍵入隊伍以同步使用(即,如果啟用了顯示,則由恢復選單系統使用)
每次按鍵按下事件後面跟著同一個按鍵的按鍵彈起事件時,都會呼叫CheckKey()
。 (事件順序 A-down B-down B-up A-up 僅導致呼叫CheckKey(B)
。) CheckKey()
可以呼叫IsKeyPressed()
,以找出是否按下了其他鍵。 (在上面的按鍵事件序列中,如果CheckKey(B)
呼叫IsKeyPressed(A)
它將傳回 true。)
CheckKey()
可以維護其類別中的狀態;這對於檢測按鍵序列很有用。此範例顯示了稍微複雜的設定:透過按住電源並按音量增大鍵來切換顯示屏,並且可以透過連續按電源按鈕五次(沒有其他中間鍵)立即重新啟動裝置:
class TardisUI : public ScreenRecoveryUI { private: int consecutive_power_keys; public: TardisUI() : consecutive_power_keys(0) {} virtual KeyAction CheckKey(int key) { if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) { return TOGGLE; } if (key == KEY_POWER) { ++consecutive_power_keys; if (consecutive_power_keys >= 5) { return REBOOT; } } else { consecutive_power_keys = 0; } return ENQUEUE; } };
螢幕恢復UI
當在 ScreenRecoveryUI 中使用您自己的圖像(錯誤圖示、安裝動畫、進度列)時,您可以設定變數animation_fps
來控制動畫的每秒幀數 (FPS) 速度。
注意:目前的interlace-frames.py
腳本允許您將animation_fps
資訊儲存在圖像本身中。在早期版本的 Android 中,需要自行設定animation_fps
。
若要設定變數animation_fps
,請覆寫子類別中的ScreenRecoveryUI::Init()
函數。設定該值,然後呼叫parent Init()
函數完成初始化。預設值(20 FPS)對應預設恢復影像;使用這些圖像時,您不需要提供Init()
函數。有關圖像的詳細信息,請參閱恢復 UI 圖像。
設備類別
實作 RecoveryUI 後,定義您的裝置類別(內建裝置類別的子類別)。它應該創建 UI 類別的單一實例並從GetUI()
函數傳回該實例:
class TardisDevice : public Device { private: TardisUI* ui; public: TardisDevice() : ui(new TardisUI) { } RecoveryUI* GetUI() { return ui; }
開始恢復
StartRecovery()
方法在復原開始時、UI 初始化之後以及參數解析之後、但在執行任何操作之前呼叫。預設實作不執行任何操作,因此如果您無事可做,則無需在子類別中提供此實作:
void StartRecovery() { // ... do something tardis-specific here, if needed .... }
提供和管理恢復選單
系統呼叫兩個方法來取得標題行列表和項目清單。在此實作中,它會傳回在文件頂部定義的靜態數組:
const char* const* GetMenuHeaders() { return HEADERS; } const char* const* GetMenuItems() { return ITEMS; }
句柄選單鍵
接下來,提供一個HandleMenuKey()
函數,該函數接受按鍵操作和目前選單可見性,並決定採取什麼操作:
int HandleMenuKey(int key, int visible) { if (visible) { switch (key) { case KEY_VOLUMEDOWN: return kHighlightDown; case KEY_VOLUMEUP: return kHighlightUp; case KEY_POWER: return kInvokeItem; } } return kNoAction; }
此方法採用鍵碼(先前已由 UI 物件的CheckKey()
方法處理並排隊)以及選單/文字日誌可見性的目前狀態。傳回值是一個整數。如果該值為 0 或更高,則將其視為選單項目的位置,立即呼叫該選單項目(請參閱下方的InvokeMenuItem()
方法)。否則它可以是以下預定義常數之一:
- k突出顯示。將選單反白顯示移至上一個項目
- kHighlightDown 。將選單反白顯示移至下一個項目
- kInvokeItem 。呼叫當前突出顯示的項目
- k無動作。按此按鍵不執行任何動作
正如可見參數所暗示的那樣,即使選單不可見,也會呼叫HandleMenuKey()
。與CheckKey()
不同,當恢復執行某些操作(例如擦除資料或安裝軟體包)時不會調用它 - 僅當恢復空閒並等待輸入時才調用它。
軌跡球機制
如果您的裝置具有類似軌跡球的輸入機制(產生類型為 EV_REL 和代碼 REL_Y 的輸入事件),則只要類似軌跡球的輸入裝置報告 Y 軸上的移動,恢復就會合成 KEY_UP 和 KEY_DOWN 按鍵。您所需要做的就是將 KEY_UP 和 KEY_DOWN 事件對應到選單操作。 CheckKey()
不會發生此映射,因此您不能使用軌跡球運動作為重新啟動或切換顯示的觸發器。
修改鍵
若要檢查是否按下修飾鍵,請呼叫您自己的 UI 物件的IsKeyPressed()
方法。例如,在某些裝置上,無論選單是否可見,在復原過程中按 Alt-W 都會啟動資料擦除。你可以這樣實現:
int HandleMenuKey(int key, int visible) { if (ui->IsKeyPressed(KEY_LEFTALT) && key == KEY_W) { return 2; // position of the "wipe data" item in the menu } ... }
注意:如果visible為假,則傳回操作選單的特殊值(移動突出顯示,呼叫突出顯示的項目)是沒有意義的,因為使用者看不到突出顯示。但是,如果需要,您可以傳回這些值。
呼叫選單項
接下來,提供一個InvokeMenuItem()
方法,該方法將GetMenuItems()
傳回的項目陣列中的整數位置對應到操作。對於 tardis 範例中的項目數組,請使用:
BuiltinAction InvokeMenuItem(int menu_position) { switch (menu_position) { case 0: return REBOOT; case 1: return APPLY_ADB_SIDELOAD; case 2: return WIPE_DATA; case 3: return WIPE_CACHE; default: return NO_ACTION; } }
此方法可以傳回BuiltinAction 枚舉的任何成員來告訴系統採取該操作(如果您希望系統不執行任何操作,則傳回NO_ACTION 成員)。這是提供系統之外的額外恢復功能的地方:在選單中為其添加一個項目,在調用該選單項目時在此處執行它,並返回 NO_ACTION,以便系統不執行任何其他操作。
builtinAction 包含以下值:
- 無操作。沒做什麼。
- 重啟。退出恢復並正常重新啟動設備。
- APPLY_EXT、APPLY_CACHE、APPLY_ADB_SIDELOAD 。從不同的地方安裝更新包。有關詳細信息,請參閱旁載。
- WIPE_CACHE 。僅重新格式化快取分割區。無需確認,因為這相對無害。
- 抹掉數據。重新格式化使用者資料和快取分割區,也稱為恢復出廠設定。在繼續之前,系統會要求使用者確認此操作。
最後一個方法WipeData()
是可選的,每當啟動資料擦除操作時(無論是透過選單恢復還是當使用者選擇從主系統執行出廠資料重置時)都會呼叫它。在擦除使用者資料和快取分區之前呼叫此方法。如果您的裝置將使用者資料儲存在這兩個分割區以外的任何位置,則應在此處將其刪除。您應該傳回 0 來指示成功,並傳回另一個值來指示失敗,儘管當前傳回值被忽略。無論返回成功還是失敗,用戶資料和快取分區都會被擦除。
int WipeData() { // ... do something tardis-specific here, if needed .... return 0; }
製作設備
最後,在 recovery_ui.cpp 文件的末尾包含make_device()
函數的一些樣板文件,該函數創建並返回 Device 類別的實例:
class TardisDevice : public Device { // ... all the above methods ... }; Device* make_device() { return new TardisDevice(); }
建置並連結到設備恢復
完成 recovery_ui.cpp 檔案後,建立它並將其連結到裝置上的恢復。在 Android.mk 中,建立一個僅包含此 C++ 檔案的靜態函式庫:
device/yoyodyne/tardis/recovery/Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES += bootable/recovery LOCAL_SRC_FILES := recovery_ui.cpp # should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk LOCAL_MODULE := librecovery_ui_tardis include $(BUILD_STATIC_LIBRARY)
然後,在此裝置的板配置中,將靜態庫指定為 TARGET_RECOVERY_UI_LIB 的值。
device/yoyodyne/tardis/BoardConfig.mk [...] # device-specific extensions to the recovery UI TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis
恢復 UI 影像
恢復使用者介面由影像組成。理想情況下,使用者永遠不會與 UI 互動:在正常更新期間,手機會啟動進入復原模式,填入安裝進度條,然後啟動回新系統,而無需使用者輸入。如果出現系統更新問題,使用者唯一可以採取的操作就是致電客戶服務中心。
僅圖像介面無需本地化。但是,從 Android 5.0 開始,更新可以顯示一串文字(例如「安裝系統更新...」)以及圖像。有關詳細信息,請參閱本地化恢復文字。
安卓5.0以上版本
Android 5.0 及更高版本的恢復 UI 使用兩個主要圖像:錯誤圖像和安裝動畫。
安裝動畫表示為單一 PNG 影像,其中動畫的不同畫面按行交錯(這就是圖 2 顯得被壓扁的原因)。例如,對於 200x200 的七幀動畫,建立單一 200x1400 影像,其中第一幀為第 0、7、14、21 行…;第二幀是第 1、8、15、22、... 行;組合圖像包括指示動畫幀數和每秒幀數 (FPS) 的文字區塊。 bootable/recovery/interlace-frames.py
工具取得一組輸入影格並將它們組合成恢復所需的合成影像。
預設影像有不同的密度,位於bootable/recovery/res-$DENSITY/images
中(例如bootable/recovery/res-hdpi/images
)。要在安裝過程中使用靜態圖像,您只需提供 icon_installing.png 圖像並將動畫的幀數設為 0(錯誤圖標不是動畫的;它始終是靜態圖像)。
Android 4.x 及更早版本
Android 4.x 及更早版本的恢復 UI 使用錯誤圖像(如上所示)和安裝動畫以及幾個覆蓋圖像:
在安裝過程中,透過繪製 icon_installing.png 影像,然後以適當的偏移在其頂部繪製覆蓋框架之一來建立螢幕顯示。在這裡,疊加了一個紅色框以突出顯示疊加層放置在基本圖像頂部的位置:
透過僅在已有影像之上繪製下一個覆蓋影像來顯示後續影格;基礎圖像不會被重繪。
動畫中的幀數、所需的速度以及覆蓋層相對於基礎的 x 和 y 偏移量由 ScreenRecoveryUI 類別的成員變數設定。當使用自訂圖像而不是預設圖像時,請重寫子類別中的Init()
方法以更改自訂圖像的這些值(有關詳細信息,請參閱ScreenRecoveryUI )。腳本bootable/recovery/make-overlay.py
可以幫助將一組影像幀轉換為恢復所需的「基礎影像+覆蓋影像」形式,包括計算必要的偏移量。
預設影像位於bootable/recovery/res/images
。要在安裝過程中使用靜態圖像,您只需提供 icon_installing.png 圖像並將動畫的幀數設為 0(錯誤圖標不是動畫的;它始終是靜態圖像)。
在地化恢復文本
Android 5.x 顯示一串文字(例如,「安裝系統更新...」)以及圖像。當主系統啟動進入恢復狀態時,它將使用者目前的區域設定作為命令列選項傳遞給恢復。對於要顯示的每個訊息,恢復包括第二個合成圖像,其中包含每個區域設定中該訊息的預渲染文字字串。
恢復文字字串的範例圖像:
恢復文字可以顯示以下訊息:
- 正在安裝系統更新...
- 錯誤!
- 正在擦除...(執行資料擦除/恢復出廠設定時)
- 無命令(當使用者手動啟動進入恢復時)
bootable/recovery/tools/recovery_l10n/
中的 Android 應用程式渲染訊息的本地化並建立合成圖像。有關使用此應用程式的詳細信息,請參閱bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
中的註釋。
當使用者手動啟動進入恢復時,區域設定可能不可用且不顯示任何文字。不要讓簡訊對恢復過程至關重要。
注意:顯示日誌訊息並允許使用者從選單中選擇操作的隱藏介面僅提供英文版本。
進度條
進度條可以出現在主圖像(或動畫)下方。進度條是透過組合兩個輸入圖像製成的,這兩個圖像必須具有相同的大小:
填滿影像的左端顯示在空白影像的右端旁邊,以形成進度條。兩個影像之間的邊界位置會改變以指示進度。例如,對於上面的輸入影像對,顯示:
您可以將這些映像放入(在本例中) device/yoyodyne/tardis/recovery/res/images
提供這些映像的特定於裝置的版本。檔案名稱必須與上面列出的檔案名稱相符;當在該目錄中找到檔案時,建置系統會優先使用該檔案而不是對應的預設映像。僅支援具有 8 位元顏色深度的 RGB 或 RGBA 格式的 PNG。
注意:在 Android 5.x 中,如果區域設定已知可恢復並且是從右到左 (RTL) 語言(阿拉伯語、希伯來語等),則進度條將從右到左填充。
沒有螢幕的設備
並非所有 Android 裝置都有螢幕。如果您的裝置是無頭裝置或具有純音訊接口,您可能需要對復原 UI 進行更廣泛的自訂。不要建立 ScreenRecoveryUI 的子類,而是直接對其父類 RecoveryUI 進行子類化。
RecoveryUI 具有處理較低層級 UI 操作的方法,例如「切換顯示」、「更新進度條」、「顯示選單」、「變更選單選擇」等。您可以覆寫這些方法以提供適當的介面為您的裝置。也許您的裝置具有 LED,您可以使用不同的顏色或閃爍模式來指示狀態,或者您可以播放音訊。 (也許您根本不想支援選單或「文字顯示」模式;您可以使用CheckKey()
和HandleMenuKey()
實作來阻止存取它們,這些實作永遠不會開啟顯示或選擇選單項目。在這種情況下,您需要提供的許多RecoveryUI 方法可以只是空存根。)
請參閱bootable/recovery/ui.h
以了解 RecoveryUI 的聲明,以了解您必須支援哪些方法。 RecoveryUI 是抽象的——有些方法是純虛擬的,必須由子類別提供——但它確實包含處理關鍵輸入的程式碼。如果您的裝置沒有密鑰或您想以不同的方式處理它們,您也可以覆蓋它。
更新程式
您可以透過提供可以從更新程式腳本中呼叫的您自己的擴充函數,在更新套件的安裝中使用特定於裝置的程式碼。以下是 tardis 設備的範例函數:
device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h> #include <string.h> #include "edify/expr.h"
每個擴充函數都有相同的簽名。參數是呼叫函數的名稱、 State*
cookie、傳入參數的數量、表示參數的Expr*
指標陣列。傳回值是新指派的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); }
您的參數在呼叫函數時尚未被評估 - 函數的邏輯決定了其中哪些參數被評估以及評估次數。因此,您可以使用擴充函數來實作您自己的控制結構。 Call Evaluate()
來計算Expr*
參數,回傳Value*
。如果Evaluate()
傳回 NULL,您應該釋放您持有的所有資源並立即傳回 NULL(這會中止 edify 堆疊)。否則,您將擁有傳回值的所有權,並負責最終對其呼叫FreeValue()
。
假設函數需要兩個參數:一個字串值鍵和一個 blob 值影像。你可以這樣閱讀論證:
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; }
對於多個參數來說,檢查 NULL 並釋放先前計算的參數可能會變得乏味。 ReadValueArgs()
函數可以讓這件事變得更容易。您可以編寫以下程式碼,而不是上面的程式碼:
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()
不執行類型檢查,因此您必須在此處執行此操作;使用一個if語句來完成此操作會更方便,但代價是在失敗時會產生一條不太具體的錯誤訊息。但ReadValueArgs()
確實會處理每個參數的求值,並在任何求值失敗時釋放所有先前求值的參數(以及設定有用的錯誤訊息)。您可以使用ReadValueVarArgs()
便利函數來計算可變數量的參數(它會傳回Value*
陣列)。
評估參數後,執行函數的工作:
// 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 ...
返回值必須是Value*
物件;該物件的所有權將傳遞給呼叫者。呼叫者獲得此Value*
指向的任何資料的所有權—特別是資料成員。
在本例中,您希望傳回 true 或 false 值來指示成功。請記住空字串為false而所有其他字串為true的約定。您必須使用要傳回的常數字串的 malloc 副本來 malloc 一個 Value 對象,因為呼叫者將free()
兩者。不要忘記對透過評估參數而獲得的物件呼叫FreeValue()
!
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; }
便捷函數StringValue()
將字串包裝到新的 Value 物件中。使用上面的程式碼寫得更簡潔:
FreeValue(key); FreeValue(image); return StringValue(strdup(successful ? "t" : "")); }
要將函數掛鉤到 edify 解釋器中,請提供函數Register_ foo
,其中foo是包含此程式碼的靜態函式庫的名稱。呼叫RegisterFunction()
來註冊每個擴充函數。依照約定,將device . whatever
努力避免與未來添加的內建功能發生衝突。
void Register_librecovery_updater_tardis() { RegisterFunction("tardis.reprogram", ReprogramTardisFn); }
現在您可以配置 makefile 以使用您的程式碼建立靜態庫。 (這與上一節中用於自訂恢復 UI 的 makefile 相同;您的裝置可能在此處定義了兩個靜態庫。)
device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS) LOCAL_SRC_FILES := recovery_updater.c LOCAL_C_INCLUDES += bootable/recovery
靜態函式庫的名稱必須與其中包含的Register_ libname
函數的名稱相符。
LOCAL_MODULE := librecovery_updater_tardis include $(BUILD_STATIC_LIBRARY)
最後,配置恢復的建置以拉入您的庫。將您的庫新增至 TARGET_RECOVERY_UPDATER_LIBS(其中可能包含多個庫;它們都會被註冊)。如果您的程式碼依賴本身不是 edify 擴充功能的其他靜態庫(即,它們沒有Register_ libname
函數),您可以在TARGET_RECOVERY_UPDATER_EXTRA_LIBS 中列出這些靜態庫,以將它們連結到更新程序,而無需呼叫它們(不存在的)註冊函數。例如,如果您的裝置特定程式碼想要使用 zlib 來解壓縮數據,則可以在此處包含 libz。
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 +=
OTA 套件中的更新程式腳本現在可以像呼叫任何其他函數一樣呼叫您的函數。若要重新編程您的 tardis 設備,更新腳本可能包含: tardis.reprogram("the-key", package_extract_file("tardis-image.dat"))
。這使用內建函數package_extract_file()
的單參數版本,該函數將從更新包中提取的檔案內容作為 blob 返回,以產生新擴充函數的第二個參數。
OTA包生成
最後一個元件是讓 OTA 套件產生工具了解您的裝置特定資料並發出更新程式腳本,其中包括對擴充函數的呼叫。
首先,讓建置系統了解特定於設備的資料塊。假設您的資料檔案位於device/yoyodyne/tardis/tardis.dat
中,請在裝置的 AndroidBoard.mk 中聲明以下內容:
device/yoyodyne/tardis/AndroidBoard.mk
[...] $(call add-radio-file,tardis.dat)
您也可以將其放入 Android.mk 中,但必須透過裝置檢查來保護它,因為無論建置什麼設備,都會載入樹中的所有 Android.mk 檔案。 (如果您的樹包含多個設備,則您只需要在建置 tardis 設備時新增 tardis.dat 檔案。)
device/yoyodyne/tardis/Android.mk
[...] # an alternative to specifying it in AndroidBoard.mk ifeq (($TARGET_DEVICE),tardis) $(call add-radio-file,tardis.dat) endif
由於歷史原因,這些文件被稱為無線電文件;它們可能與設備無線電(如果存在)無關。它們只是建構系統複製到 OTA 生成工具使用的目標檔案 .zip 中的不透明資料塊。當您進行建置時,tardis.dat 將作為RADIO/tardis.dat
儲存在 target-files.zip 中。您可以多次呼叫add-radio-file
以新增任意數量的檔案。
Python模組
要擴充發布工具,請編寫一個Python模組(必須命名為releasetools.py),工具可以呼叫(如果存在)。例子:
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"));""")
一個單獨的函數處理產生增量 OTA 套件的情況。對於此範例,假設僅當 tardis.dat 檔案在兩個版本之間發生變更時才需要重新編程 tardis。
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"));""")
模組功能
您可以在模組中提供以下功能(僅實現您需要的功能)。
-
FullOTA_Assertions()
- 在產生完整 OTA 開始時呼叫。這是發出有關設備當前狀態的斷言的好地方。不要發出對設備進行更改的腳本命令。
-
FullOTA_InstallBegin()
- 在有關設備狀態的所有斷言都已通過之後但在進行任何更改之前調用。您可以發出特定於裝置的更新命令,這些更新必須在裝置上的其他任何內容發生變更之前執行。
-
FullOTA_InstallEnd()
- 在發出更新引導和系統分區的腳本命令後,在腳本生成結束時調用。您也可以發出其他命令以進行特定於裝置的更新。
-
IncrementalOTA_Assertions()
- 與
FullOTA_Assertions()
類似,但在產生增量更新套件時呼叫。 -
IncrementalOTA_VerifyBegin()
- 在有關設備狀態的所有斷言都已通過之後但在進行任何更改之前調用。您可以發出特定於裝置的更新命令,這些更新必須在裝置上的其他任何內容發生變更之前執行。
-
IncrementalOTA_VerifyEnd()
- 在驗證階段結束時調用,當腳本完成確認它將要接觸的檔案是否具有預期的起始內容時調用。此時設備上的任何內容都沒有改變。您也可以發出代碼以進行其他特定於設備的驗證。
-
IncrementalOTA_InstallBegin()
- 在要修補的檔案已被驗證為具有預期的之前狀態之後但在進行任何更改之前調用。您可以發出特定於裝置的更新命令,這些更新必須在裝置上的其他任何內容發生變更之前執行。
-
IncrementalOTA_InstallEnd()
- 與完整的 OTA 套件對應部分類似,這在腳本產生結束時、在發出更新引導和系統分區的腳本命令之後調用。您也可以發出其他命令以進行特定於裝置的更新。
注意:如果設備斷電,OTA 安裝可能會從頭開始。準備好應對已全部或部分運行這些命令的設備。
將函數傳遞給訊息對象
將函數傳遞給包含各種有用項目的單一資訊物件:
- 訊息.input_zip 。 (僅限完整 OTA)輸入目標檔案 .zip 的
zipfile.ZipFile
物件。 - 資訊.source_zip 。 (僅限增量 OTA)來源目標檔案 .zip 的
zipfile.ZipFile
物件(安裝增量套件時裝置上已存在該版本)。 - 訊息.target_zip 。 (僅限增量 OTA)目標目標檔案 .zip 的
zipfile.ZipFile
物件(增量套件放在裝置上的建置)。 - 訊息.output_zip 。正在創建包;開啟用於寫入的
zipfile.ZipFile
物件。使用 common.ZipWriteStr(info.output_zip, filename , data ) 將檔案加入套件。 - 資訊腳本。您可以向其附加命令的腳本物件。呼叫
info.script.AppendExtra( script_text )
將文字輸出到腳本中。確保輸出文字以分號結尾,這樣就不會遇到隨後發出的命令。
有關 info 物件的詳細信息,請參閱ZIP 存檔的 Python Software Foundation 文件。
指定模組位置
在 BoardConfig.mk 檔案中指定裝置的 releasetools.py 腳本的位置:
device/yoyodyne/tardis/BoardConfig.mk
[...] TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis
如果未設定 TARGET_RELEASETOOLS_EXTENSIONS,則預設為$(TARGET_DEVICE_DIR)/../common
目錄(本例device/yoyodyne/common
)。最好明確定義 releasetools.py 腳本的位置。在建置 tardis 設備時,releasetools.py 腳本包含在 target-files .zip 檔案 ( META/releasetools.py
) 中。
當您執行發布工具( img_from_target_files
或ota_from_target_files
)時,目標檔案 .zip 中的releasetools.py 腳本(如果存在)優先於 Android 原始程式碼樹中的腳本。您也可以使用-s
(或--device_specific
)選項明確指定裝置特定擴充的路徑,該選項具有最高優先權。這使您能夠修正錯誤並在發布工具擴充功能中進行更改,並將這些變更套用到舊的目標檔案。
現在,當您執行ota_from_target_files
時,它會自動從 target_files .zip 檔案中選取裝置特定的模組,並在產生 OTA 套件時使用它:
./build/make/tools/releasetools/ota_from_target_files \
-i PREVIOUS-tardis-target_files.zip \
dist_output/tardis-target_files.zip \
incremental_ota_update.zip
或者,您可以在執行ota_from_target_files
時指定裝置特定的擴充。
./build/make/tools/releasetools/ota_from_target_files \
-s device/yoyodyne/tardis \
-i PREVIOUS-tardis-target_files.zip \
dist_output/tardis-target_files.zip \
incremental_ota_update.zip
注意:有關選項的完整列表,請參閱build/make/tools/releasetools/ota_from_target_files
中的ota_from_target_files
註釋。
側載機制
Recovery 具有側載機制,可手動安裝更新包,而無需由主系統以無線方式下載。旁加載對於在無法啟動主系統的裝置上進行偵錯或更改非常有用。
從歷史上看,側面加載是透過從設備的 SD 卡加載包來完成的。如果是非啟動設備,可以使用其他電腦將程式包放到 SD 卡上,然後將 SD 卡插入設備。為了適應沒有可移動外部儲存的 Android 設備,恢復支援兩種額外的側面加載機制:從快取分區加載包,以及使用 adb 透過 USB 加載它們。
要呼叫每個側面載入機制,裝置的Device::InvokeMenuItem()
方法可以傳回以下BuiltinAction 值:
- 應用_外部。 sideload來自外部儲存(
/sdcard
目錄)的更新包。您的恢復。fstab必須定義/sdcard
安裝點。這在模擬具有符號連結/data
(或某些類似機制)的SD卡的裝置上不可用。/data
通常不可用於恢復,因為可能會加密。恢復UI在/sdcard
中顯示.zip檔案的選單,並允許使用者選擇一個。 - apply_cache 。類似於從
/sdcard
載入軟體包,但使用/cache
目錄(始終可用於恢復)。從常規系統中,/cache
僅由特權使用者寫作,如果裝置不可啟動,則/cache
目錄完全無法寫入(這使得這種有限的實用程式機制)。 - apply_adb_sideload 。允許用戶透過USB電纜和ADB開發工具將軟體包發送到設備。呼叫此機制後,恢復啟動了自己的ADBD守護程序的迷你版本,讓ADB在連接的主機上與之交談。此迷你版本僅支援一個指令:
adb sideload filename
。命名檔案是從主機電腦發送到裝置的,然後將其驗證並安裝它,就像它已經在本機儲存中一樣。
一些警告:
- 僅支援USB運送。
- 如果您的復原正常執行ADBD(通常適用於使用者Debug和Eng建置),則該裝置處於ADB sideload模式時將關閉,並且當ADB Sideload完成接收軟體包時,將重新啟動。在ADB SIDELODOD模式下,除了
sideload
Work(logcat
,reboot
,push
,Push,pull
,shell
等)以外,沒有其他ADB指令。都失敗了)。 - 您無法在裝置上退出ADB SIDELODOD模式。若要中止,您可以將
/dev/null
(或其他無效軟體包)作為軟體包發送,然後該裝置將無法驗證它並停止安裝過程。 Recoverui實作的CheckKey()
方法將繼續用於按鍵,因此您可以提供重新啟動裝置並在ADB SIDELODOD模式下工作的鍵序列。