android ota升級原理3
背景圖來源:click
又是一週,工作996,給小糰子芳寫的信斷斷續續也一直在進行,很快又是她生日了。本篇繼續來完成ota升級原理。
1。
android ota升級理論1。
2。
android ota升級實踐2。
3。
(很重要-糰子也值得一看)如何用android:sharedUserId屬性生成帶有系統許可權的apk?。
ota理論介紹了,ota系統升級有兩種方法:
手機升級,在正常使用手機時,會收到系統更新的通知,可以點選直接下載和安裝。一般多見於谷歌自己的nexus裝置和官方行貨機器。
刷機升級,手機破解後可以刷入其他第三方的ROM,ROM對應的系統版本升級了相應你的手機系統也升級了。
前者多用於手機廠商的客戶端線上升級,後者多用於開發和測試人員。但不管哪種,原理都是一樣的,都要在
recovery模式
下進行升級。
android中進行ota升級,首先需要使用原始碼中ota升級包打包工具
build/tools/releasetools/ota_from_target_files
生成ota有兩類:
整包
和
差分包
,在android系統編譯環境下,在終端下使用
make otapackage
生成,具體可以參考ota實踐中內容。
android平臺使用了提供了
frameworks/base/core/java/android/os/RecoverySystem。java
工具類支援升級,需要新增
android。Manifest。permission#REBOOT permission
,而REBOOT許可權需要新增
android:shareUserId=“android。uid。system”
,賦予升級平臺許可權後,才能支援升級,或是升級app在系統原始碼編譯環境中編譯後才能使用該功能。
下面是ota升級的一個流程,對應於ota實踐[圖4]的流程圖,我們就從這個流程來分析升級的原理。
[x] 1、獲取升級包,可以從服務端下載,也可以直接複製到SD卡中
[x] 2、獲取升級包路徑,驗證簽名,透過installPackage介面升級
[x] 3、系統重啟進入Recovery模式
[x] 4、在install。cpp進行升級操作
[x] 5、try_update_binary執行升級指令碼
[ ] 6、finish_recovery,重啟
**版權宣告CopyRight:
本內容作者:sunst,轉載或引用請
標明出處
,違者追究法律責任!!!
一:獲取升級包
獲取
update。zip
升級包,可以從服務端線上下載,也可以直接複製到SD卡中,也可以實踐2中自己製作,push到手機sdcard中。
ota線上下載(一般下載到/CACHE分割槽),手動複製到SD卡中,兩種方式獲得的
update。zip
包,在進入
Recovery模式
前,都未對這個zip包做處理。只是在重啟之前將zip包的路徑告訴了Recovery服務
二:獲取升級包路徑,驗證簽名,透過installPackage介面升級
RecoverySystem
提供了三個靜態方法
static
void
installPackage
(
Context
context
,
File
packageFile
)
//重啟裝置,安裝一個更新包
static
void
rebootWipeUserData
(
Context
context
)
//重啟裝置,清除使用者資料分割槽類似恢復出廠設定
static
void
verifyPackage
(
File
packageFile
,
RecoverySystem
。
ProgressListener
listener
,
File
deviceCertsZipFile
)
//
驗證加密簽名
1、 進行簽名驗證
/**
* Used to communicate with recovery。 See bootable/recovery/recovery。c。 * 透過/cache/recovery/command,與系統通訊,接收升級命令指令,詳細可以檢視原始碼: bootable/recovery/recovery。c。 */
private
static
File
RECOVERY_DIR
=
new
File
(
“/cache/recovery”
);
private
static
File
COMMAND_FILE
=
new
File
(
RECOVERY_DIR
,
“command”
);
/*
*@param packageFile 待驗證的升級包
*@param listener 升級進度監聽器,驗證的進度
*@param deviceCertsZipFile 驗證證書檔案,如果為null,系統預設的是“/system/etc/security/otacerts。zip”
* */
public
static
void
verifyPackage
(
File
packageFile
,
ProgressListener
listener
,
File
deviceCertsZipFile
)
throws
IOException
,
GeneralSecurityException
{
long
fileLen
=
packageFile
。
length
();
RandomAccessFile
raf
=
new
RandomAccessFile
(
packageFile
,
“r”
);
try
{
int
lastPercent
=
0
;
long
lastPublishTime
=
System
。
currentTimeMillis
();
if
(
listener
!=
null
)
{
listener
。
onProgress
(
lastPercent
);
}
raf
。
seek
(
fileLen
-
6
);
byte
[]
footer
=
new
byte
[
6
];
raf
。
readFully
(
footer
);
//升級包是否含有系統簽名信息-sunst2019-6-8註釋
if
(
footer
[
2
]
!=
(
byte
)
0xff
||
footer
[
3
]
!=
(
byte
)
0xff
)
{
throw
new
SignatureException
(
“no signature in file (no footer)”
);
}
int
commentSize
=
(
footer
[
4
]
&
0xff
)
|
((
footer
[
5
]
&
0xff
)
<<
8
);
//簽名的起始位置-sunst2019-6-8註釋
int
signatureStart
=
(
footer
[
0
]
&
0xff
)
|
((
footer
[
1
]
&
0xff
)
<<
8
);
Log
。
v
(
TAG
,
String
。
format
(
“comment size %d; signature start %d”
,
commentSize
,
signatureStart
));
byte
[]
eocd
=
new
byte
[
commentSize
+
22
];
raf
。
seek
(
fileLen
-
(
commentSize
+
22
));
raf
。
readFully
(
eocd
);
if
(
eocd
[
0
]
!=
(
byte
)
0x50
||
eocd
[
1
]
!=
(
byte
)
0x4b
||
eocd
[
2
]
!=
(
byte
)
0x05
||
eocd
[
3
]
!=
(
byte
)
0x06
)
{
throw
new
SignatureException
(
“no signature in file (bad footer)”
);
}
for
(
int
i
=
4
;
i
<
eocd
。
length
-
3
;
++
i
)
{
if
(
eocd
[
i
]
==
(
byte
)
0x50
&&
eocd
[
i
+
1
]
==
(
byte
)
0x4b
&&
eocd
[
i
+
2
]
==
(
byte
)
0x05
&&
eocd
[
i
+
3
]
==
(
byte
)
0x06
)
{
throw
new
SignatureException
(
“EOCD marker found after start of EOCD”
);
}
}
// The following code is largely copied from
// JarUtils。verifySignature()。 We could just *call* that
// method here if that function didn‘t read the entire
// input (ie, the whole OTA package) into memory just to
// compute its message digest。
BerInputStream
bis
=
new
BerInputStream
(
new
ByteArrayInputStream
(
eocd
,
commentSize
+
22
-
signatureStart
,
signatureStart
));
ContentInfo
info
=
(
ContentInfo
)
ContentInfo
。
ASN1
。
decode
(
bis
);
SignedData
signedData
=
info
。
getSignedData
();
if
(
signedData
==
null
)
{
throw
new
IOException
(
“signedData is null”
);
}
Collection
encCerts
=
signedData
。
getCertificates
();
if
(
encCerts
。
isEmpty
())
{
throw
new
IOException
(
“encCerts is empty”
);
}
// Take the first certificate from the signature (packages
// should contain only one)。
Iterator
it
=
encCerts
。
iterator
();
X509Certificate
cert
=
null
;
if
(
it
。
hasNext
())
{
cert
=
new
X509CertImpl
((
org
。
apache
。
harmony
。
security
。
x509
。
Certificate
)
it
。
next
());
}
else
{
throw
new
SignatureException
(
“signature contains no certificates”
);
}
//獲取到包中所有的簽名證書資訊-sunst2019-6-8註釋
List
sigInfos
=
signedData
。
getSignerInfos
();
SignerInfo
sigInfo
;
if
(!
sigInfos
。
isEmpty
())
{
sigInfo
=
(
SignerInfo
)
sigInfos
。
get
(
0
);
}
else
{
throw
new
IOException
(
“no signer infos!”
);
}
// Check that the public key of the certificate contained
// in the package equals one of our trusted public keys。
HashSet
<
Certificate
>
trusted
=
getTrustedCerts
(
deviceCertsZipFile
==
null
?
DEFAULT_KEYSTORE
:
deviceCertsZipFile
);
PublicKey
signatureKey
=
cert
。
getPublicKey
();
boolean
verified
=
false
;
//簽名驗證key比較-sunst2019-6-8註釋
for
(
Certificate
c
:
trusted
)
{
if
(
c
。
getPublicKey
()。
equals
(
signatureKey
))
{
verified
=
true
;
break
;
}
}
if
(!
verified
)
{
throw
new
SignatureException
(
“signature doesn’t match any trusted key”
);
}
String
da
=
sigInfo
。
getdigestAlgorithm
();
String
dea
=
sigInfo
。
getDigestEncryptionAlgorithm
();
String
alg
=
null
;
if
(
da
==
null
||
dea
==
null
)
{
// fall back to the cert algorithm if the sig one
// doesn‘t look right。
alg
=
cert
。
getSigAlgName
();
}
else
{
alg
=
da
+
“with”
+
dea
;
}
Signature
sig
=
Signature
。
getInstance
(
alg
);
sig
。
initVerify
(
cert
);
// The signature covers all of the OTA package except the
// archive comment and its 2-byte length。
// 對整個升級包開始驗證-sunst2019-6-8註釋
long
toRead
=
fileLen
-
commentSize
-
2
;
long
soFar
=
0
;
raf
。
seek
(
0
);
byte
[]
buffer
=
new
byte
[
4096
];
boolean
interrupted
=
false
;
while
(
soFar
<
toRead
)
{
interrupted
=
Thread
。
interrupted
();
if
(
interrupted
)
break
;
int
size
=
buffer
。
length
;
if
(
soFar
+
size
>
toRead
)
{
size
=
(
int
)
(
toRead
-
soFar
);
}
int
read
=
raf
。
read
(
buffer
,
0
,
size
);
sig
。
update
(
buffer
,
0
,
read
);
soFar
+=
read
;
if
(
listener
!=
null
)
{
long
now
=
System
。
currentTimeMillis
();
int
p
=
(
int
)
(
soFar
*
100
/
toRead
);
if
(
p
>
lastPercent
&&
now
-
lastPublishTime
>
PUBLISH_PROGRESS_INTERVAL_MS
)
{
lastPercent
=
p
;
lastPublishTime
=
now
;
//返回驗證進度,使用者上層ProgressListener監聽回撥-sunst2019-6-8註釋
listener
。
onProgress
(
lastPercent
);
}
}
}
if
(
listener
!=
null
)
{
listener
。
onProgress
(
100
);
// 驗證進度完畢
}
if
(
interrupted
)
{
throw
new
SignatureException
(
“verification was interrupted”
);
}
if
(!
sig
。
verify
(
sigInfo
。
getEncryptedDigest
()))
{
throw
new
SignatureException
(
“signature digest verification failed”
);
}
}
finally
{
raf
。
close
();
}
}
Tips:
只有當簽名驗證正確才返回,否則將丟擲異常。
在Recovery模式下進行升級時候也是會進行簽名驗證的,如果這裡先不進行驗證也不會有什麼問題。但是我們建議在重啟前,先驗證,以便及早發現問題。
如果簽名驗證沒有問題,就執行installPackage開始升級。
2。透過系統簽名驗證通過後,進行安裝
/**
* Reboots the device in order to install the given update * Requires the {@link android。Manifest。permission#REBOOT} permission。
* @param context the Context to use
* @param packageFile OTA升級包路徑 ,必須在可恢復的掛載區,一般在/cache and /data 都是安全的
*/
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
);
//呼叫bootCommand進行升級包安裝
}
private
static
void
bootCommand
(
Context
context
,
String
。。。
args
)
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
{
for
(
String
arg
:
args
)
{
if
(!
TextUtils
。
isEmpty
(
arg
))
{
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
(
PowerManager
。
REBOOT_RECOVERY
);
throw
new
IOException
(
“Reboot failed (no permissions?)”
);
}
這裡定義了兩個引數,
bootCommand():在這個函式中才是Main System在重啟前真正做的準備。主要做了以下事情,首先建立/cache/recovery/目錄,刪除這個目錄下的command和log(可能不存在)檔案在sqlite資料庫中的備份。然後將–update_package=CACHE:ota。zip或–update_package=SDCARD:update。zip命令寫入到/cache/recovery/command檔案中,最終會被寫入到BCB中主要目的是將zip包的路徑告訴Recovery服務。下一步就是真正重啟了。接下來看一下在重啟函式reboot中所做的事情。
pm。reboot():重啟之前先獲得了PowerManager(電源管理)並進一步獲得其系統服務。然後呼叫了pm。reboot(“recovery”)函式。他就是/device/bionic/libc/unistd/reboot。c中的reboot函式。這個函式實際上是一個系統呼叫,即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,mode,NULL);從這個函式我們可以看出前兩個引數就代表了我們的組合鍵,mode就是我們傳過來的“recovery”。再進一步跟蹤就到了彙編程式碼了,我們無法直接檢視它的具體實現細節。但可以肯定的是 這個函式只將“recovery”引數傳遞過去了,之後將“boot-recovery”寫入到了MISC分割槽的BCB資料塊的command域中。這樣在重啟之後Bootloader才知道要進入Recovery模式。
在這裡我們無法肯定Main System在重啟之前對BCB的recovery域是否進行了操作。其實在重啟前是否更新BCB的recovery域是不重要的,因為進入Recovery服務後,Recovery會自動去/cache/recovery/command中讀取要進行的操作然後寫入到BCB的recovery域中。
至此,Main System就開始重啟並進入Recovery模式。在這之前Main System做的最實質的就是兩件事,一是將“boot-recovery”寫入BCB的command域,二是將–update_package=/cache/update。zip”或則“–update_package=/sdcard/update。zip”寫入/cache/recovery/command檔案中。
下面的部分就開始重啟並進入Recovery模式了==
三、系統重啟進入Recovery模式
系統重啟時會判斷/cache/recovery目錄下是否有command檔案,如果存在就進入recovery模式,否則就正常啟動。
進入到Recovery模式下,將執行recovery。cpp的main函式,下面貼出關鍵程式碼片段,
int arg;
while ((arg = getopt_long(argc, argv, “”, OPTIONS, NULL)) != -1) {
switch (arg) {
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 ‘t’: show_text = 1; break;
case ‘x’: just_exit = true; break;
case ‘l’: locale = optarg; break;
case ‘g’: {
if (stage == NULL || *stage == ‘\0’) {
char buffer[20] = “1/”;
strncat(buffer, optarg, sizeof(buffer)-3);
stage = strdup(buffer);
}
break;
}
case ‘p’: shutdown_after = true; break;
case ‘r’: reason = optarg; break;
case ‘?’:
LOGE(“Invalid command argument\n”);
continue;
}
這是一個While迴圈,用來讀取recovery的command引數,OPTIONS的不同選項定義如下,
static const struct option OPTIONS[] = {
{ “send_intent”, required_argument, NULL, ‘s’ },
{ “update_package”, required_argument, NULL, ‘u’ },
{ “wipe_data”, no_argument, NULL, ‘w’ },
{ “wipe_cache”, no_argument, NULL, ‘c’ },
{ “show_text”, no_argument, NULL, ‘t’ },
{ “just_exit”, no_argument, NULL, ‘x’ },
{ “locale”, required_argument, NULL, ‘l’ },
{ “stages”, required_argument, NULL, ‘g’ },
{ “shutdown_after”, no_argument, NULL, ‘p’ },
{ “reason”, required_argument, NULL, ‘r’ },
{ NULL, 0, NULL, 0 },
};
根據第二步寫入的命令檔案內容,將為update_package 賦值
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 = (char*)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;
}
}
相容性處理。
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);
if (status == INSTALL_SUCCESS && wipe_cache) {
if (erase_volume(“/cache”)) {
LOGE(“Cache wipe (requested by package) failed。”);
}
}
if (status != INSTALL_SUCCESS) {
ui->Print(“Installation aborted。\n”);
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error // message is visible。 char buffer[PROPERTY_VALUE_MAX+1];
property_get(“ro。build。fingerprint”, buffer, “”);
if (strstr(buffer, “:userdebug/”) || strstr(buffer, “:eng/”)) {
ui->ShowText(true);
}
}
} else if (wipe_data) {
if (device->WipeData()) status = INSTALL_ERROR;
if (erase_volume(“/data”)) status = INSTALL_ERROR;
if (wipe_cache && erase_volume(“/cache”)) status = INSTALL_ERROR;
if (erase_persistent_partition() == -1 ) 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 if (!just_exit) {
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
}
update_package不為空,執行install_package方法。
我們也可以看到擦除資料、快取的實現也是在這個裡執行的,這裡就不展開了。
四、在install。cpp進行升級操作
具體的升級過程都是在install。cpp中執行的,先看install_package方法,
int
install_package(const char* path, int* wipe_cache, const char* install_file,
bool needs_mount)
{
FILE* install_log = fopen_path(install_file, “w”);
if (install_log) {
fputs(path, install_log);
fputc(’\n‘, install_log);
} else {
LOGE(“failed to open last_install: %s\n”, strerror(errno));
}
int result;
if (setup_install_mounts() != 0) {
LOGE(“failed to set up expected mounts for install; aborting\n”);
result = INSTALL_ERROR;
} else {
result = really_install_package(path, wipe_cache, needs_mount);
}
if (install_log) {
fputc(result == INSTALL_SUCCESS ? ’1‘ : ’0‘, install_log);
fputc(’\n‘, install_log);
fclose(install_log);
}
return result;
}
這個方法中首先建立了log檔案,升級過程包括出錯的資訊都會寫到這個檔案中,便於後續的分析工作。繼續跟進,really_install_package,
static int
really_install_package(const char *path, int* wipe_cache, bool needs_mount)
{
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
ui->Print(“Finding update package。。。\n”);
// Give verification half the progress bar。。。
ui->SetProgressType(RecoveryUI::DETERMINATE);
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
LOGI(“Update location: %s\n”, path);
// Map the update package into memory。
ui->Print(“Opening update package。。。\n”);
if (path && needs_mount) {
if (path[0] == ’@‘) {
ensure_path_mounted(path+1);
} else {
ensure_path_mounted(path);
}
}
MemMapping map;
if (sysMapFile(path, &map) != 0) {
LOGE(“failed to map file\n”);
return INSTALL_CORRUPT;
}
// 裝入簽名檔案
int numKeys;
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
if (loadedKeys == NULL) {
LOGE(“Failed to load keys\n”);
return INSTALL_CORRUPT;
}
LOGI(“%d key(s) loaded from %s\n”, numKeys, PUBLIC_KEYS_FILE);
ui->Print(“Verifying update package。。。\n”);
// 驗證簽名
int err;
err = verify_file(map。addr, map。length, loadedKeys, numKeys);
free(loadedKeys);
LOGI(“verify_file returned %d\n”, err);
// 簽名失敗的處理
if (err != VERIFY_SUCCESS) {
LOGE(“signature verification failed\n”);
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
/* Try to open the package。
*/ // 開啟升級包 ZipArchive zip;
err = mzOpenZipArchive(map。addr, map。length, &zip);
if (err != 0) {
LOGE(“Can’t open %s\n(%s)\n”, path, err != -1 ? strerror(err) : “bad”);
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
/* Verify and install the contents of the package。
*/ ui->Print(“Installing update。。。\n”);
ui->SetEnableReboot(false);
// 執行升級指令碼檔案,開始升級
int result = try_update_binary(path, &zip, wipe_cache);
ui->SetEnableReboot(true);
ui->Print(“\n”);
sysReleaseMap(&map);
return result;
}
該方法主要做了三件事
1、驗證簽名
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
if (loadedKeys == NULL) {
LOGE(“Failed to load keys\n”);
return INSTALL_CORRUPT;
}
裝載簽名檔案,如果為空 ,終止升級;
int err;
err = verify_file(map。addr, map。length, loadedKeys, numKeys);
free(loadedKeys);
LOGI(“verify_file returned %d\n”, err);
// 簽名失敗的處理
if (err != VERIFY_SUCCESS) {
LOGE(“signature verification failed\n”);
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
呼叫verify_file進行簽名驗證,這個方法定義在verifier。cpp檔案中,此處不展開,如果驗證失敗立即終止升級。
2、讀取升級包資訊
ZipArchive zip;
err = mzOpenZipArchive(map。addr, map。length, &zip);
if (err != 0) {
LOGE(“Can‘t open %s\n(%s)\n”, path, err != -1 ? strerror(err) : “bad”);
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
執行mzOpenZipArchive方法,開啟升級包並掃描,將包的內容複製到變數zip中,該變數將作為引數用來執行升級指令碼。
3、執行升級指令碼檔案,開始升級
int result = try_update_binary(path, &zip, wipe_cache);
try_update_binary方法用來處理升級包,執行製作升級包中的指令碼檔案update_binary,進行系統更新。
五、try_update_binary執行升級指令碼
// If the package contains an update binary, extract it and run it。
static int
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
// 檢查update-binary是否存在
const ZipEntry* binary_entry =
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
if (binary_entry == NULL) {
mzCloseZipArchive(zip);
return INSTALL_CORRUPT;
}
const char* binary = “/tmp/update_binary”;
unlink(binary);
int fd = creat(binary, 0755);
if (fd < 0) {
mzCloseZipArchive(zip);
LOGE(“Can’t make %s\n”, binary);
return INSTALL_ERROR;
}
// update-binary複製到“/tmp/update_binary”
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
close(fd);
mzCloseZipArchive(zip);
if (!ok) {
LOGE(“Can‘t copy %s\n”, ASSUMED_UPDATE_BINARY_NAME);
return INSTALL_ERROR;
}
// 建立管道,用於下面的子程序和父程序之間的通訊
int pipefd[2];
pipe(pipefd);
// When executing the update binary contained in the package, the
// arguments passed are: // // - the version number for this interface // // - an fd to which the program can write in order to update the // progress bar。 The program can write single-line commands: // // progress
const char** args = (const char**)malloc(sizeof(char*) * 5);
args[0] = binary;
args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android。mk
char* temp = (char*)malloc(10);
sprintf(temp, “%d”, pipefd[1]);
args[2] = temp;
args[3] = (char*)path;
args[4] = NULL;
// 建立子程序。負責執行binary指令碼
pid_t pid = fork();
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(binary, (char* const*)args);// 執行binary指令碼
fprintf(stdout, “E:Can’t run %s (%s)\n”, binary, strerror(errno));
_exit(-1);
}
close(pipefd[1]);
*wipe_cache = 0;
// 父程序負責接受子程序傳送的命令去更新ui顯示
char buffer[1024];
FILE* from_child = fdopen(pipefd[0], “r”);
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
char* command = strtok(buffer, “ \n”);
if (command == NULL) {
continue;
} else if (strcmp(command, “progress”) == 0) {
char* fraction_s = strtok(NULL, “ \n”);
char* seconds_s = strtok(NULL, “ \n”);
float fraction = strtof(fraction_s, NULL);
int seconds = strtol(seconds_s, NULL, 10);
ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
} else if (strcmp(command, “set_progress”) == 0) {
char* fraction_s = strtok(NULL, “ \n”);
float fraction = strtof(fraction_s, NULL);
ui->SetProgress(fraction);
} else if (strcmp(command, “ui_print”) == 0) {
char* str = strtok(NULL, “\n”);
if (str) {
ui->Print(“%s”, str);
} else {
ui->Print(“\n”);
}
fflush(stdout);
} else if (strcmp(command, “wipe_cache”) == 0) {
*wipe_cache = 1;
} else if (strcmp(command, “clear_display”) == 0) {
ui->SetBackground(RecoveryUI::NONE);
} else if (strcmp(command, “enable_reboot”) == 0) {
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for // debugging packages that don‘t exit)。 ui->SetEnableReboot(true);
} else {
LOGE(“unknown command [%s]\n”, command);
}
}
fclose(from_child);
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOGE(“Error in %s\n(Status %d)\n”, path, WEXITSTATUS(status));
return INSTALL_ERROR;
}
return INSTALL_SUCCESS;
}
try_update_binary函式,是真正實現讀取升級包中的指令碼檔案並執行相應的函式的地方。在此函式中,透過呼叫fork函式創建出一個子程序,在子程序中開始讀取並執行升級指令碼檔案。在此需要注意的是函式fork的用法,fork被呼叫一次,將做兩次返回,在父程序中返回的是子程序的程序ID,為正數;而在子程序中,則返回0。子程序建立成功後,開始執行升級程式碼,並透過管道與父程序互動,父程序則透過讀取子程序傳遞過來的資訊更新UI。
六、finish_recovery,重啟
上一步完成之後,回到main函式
// Save logs and clean up before rebooting or shutting down。
finish_recovery(send_intent);
儲存升級過程中的log,清除臨時檔案,包括command檔案(不清除的話,下次重啟還會進入recovery模式),最後重啟。
透過以上ota 4篇內容介紹,相信你也會利用程式碼給系統升級了吧,到這裡
ota升級
的內容,從理論到原理終於全部介紹完了。
當然本篇只是從
ota升級流程
出發,本質是透過
RecoverySystem
升級原理的剖析。在我做系統ota升級功能的時候,查了不少資料,如果想對ota升級有更新更深的理解,這裡推薦下面幾篇還不錯的文章參考。
OTA本質與實現流程分析。--含系統分割槽的內容/
Android OTA系統的更新升級。--伺服器下載模式/
Android OTA升級原理和流程分析。--zip目錄結構偏底層C語言/
Android Recovery升級原理。--含Android啟動模式等概念/
哦,對了補充:
手動升級的流程也基本差不多,透過power key + volume上鍵組合,進入
recovery模式
,進入prompt_and_wait函式等待使用者按鍵事件。當用戶從sdcard選擇zip包後,同樣也是執行install_package函式,然後就與前面自動升級的流程是一樣的了。
圖1-android系統recovery模式
請尊重勞動成果,注意文中
版權宣告
,Android專欄不定時更新,可以點選關注我知乎。也可以同時關注人工智慧專欄,本內容作者
sunst
,有問題請溝通
qyddai@gmail。com
sunst 2019-06-23 17:05
@知乎小管家 本文以及前面一文為markdown匯入, 但格式及其難看,希望這個功能有待改進,一般都是markdown在本地編寫。