Android6.0 Framework分析——應用程式APP的安裝過程
應用程式的安裝是通過包管理服務PackageManagerService完成的,常見的安裝方式有以下幾種:
① 內建APP隨著系統啟動PMS而安裝。
② 使用adb install命令安裝。
③ 通過系統內建的PackageInstaller應用安裝。
④ 在一些手機廠商內建的應用商店下載,然後靜默安裝。
分析原始碼之後,會發現,其實只有兩種方式,
一是系統內建應用通過PackageManagerService使用scanDirLi掃描指定目錄下的apk安裝。
二是第三方apk通過PackageManager直接或者間接的呼叫PMS的installPackageAsUser介面安裝。
安裝位置有兩種:內建儲存器和SD卡(Android6.0需要將SD卡轉換為內建儲存器才行)。
下文將介紹系統應用掃描安裝、adb安裝到內部儲存器、apk安裝到SD卡等情況的安裝流程。
首先,我們看一下PackageManager的類關係:
PackageManagerService執行在system程序,為使其他程序使用,用到了binder通訊,提供PackageManager介面供呼叫。
然後, 我們看一下系統APP的安裝,之前說到系統APP安裝是在系統啟動PMS的時候,所以我們從SystemServer啟動PMS開始看,下面是大概時序圖
step2~step10, SystemServer在啟動PMS之前,先啟動了Installer這個SystemService,Installer新建一個InstallerConnection物件與installd的一個連線,通過socket通訊,PMS每次執行安裝、dexoat、解除安裝等操作時,需要呼叫Installer介面,然後Installer將封裝後的命令通過socket傳送給installd執行。
step17, 指定目錄的路徑傳遞給scanDirLi。
step18, 對目錄下的apk檔案逐個呼叫scanPackageLI方法,進行掃描解析。
step19, apk的解析是通過scanPackageLI新建的PackageParser物件完成的。
step20~step26, 主要是對AndroidManifest.xml檔案的解析。
step27, 對簽名檔案的解析。
step28~step41, apk的安裝,包含資料目錄的建立/data/data/<packagename>,apk的dexopt優化,安裝之後將apk的資訊記錄到packages.xml、packages.list;
接下來,簡介adb安裝apk到內建儲存器的過程:
adb安裝apk的主要過程如下:
1. 首先 apk會被push到手機的/data/local/tmp/目錄(如果安裝到sd卡,則會push到/sdcard/tmp/)。
2. 執行pm命令,呼叫PMS的installPackageAsUser介面,工作轉移到PMS中。
3. 將apk的安裝任務(HanderParams)交給工作執行緒PackageHandler,第三方apk的安裝工作都是由PackageHandler完成的,PackageHandler的初始化是在PMS的構造方法中,準備將apk寶貝到/data/app/<pkgname>下,傳送INIT_COPY訊息。
4. INIT_COPY主要工作是連線DefaultContainerService服務,將安裝任務(InstallParams)加到mPendingInstalls安裝列表,等待服務連線成功。
5. 服務連線成功後,傳送MCS_BOUND訊息,取出安裝任務(InstallParams),開始拷貝,新建安裝引數(InstallArgs),拷貝時,是先新建一個臨時目錄,copy成功之後,將臨時目錄改成包名-<idx>的形式
6. 拷貝成功後,新建一個執行緒,去解析apk執行<PMS啟動圖>step20類似的動作,同時工作執行緒返回去執行下一個安裝任務,如果沒有安裝任務了,就斷開之前連線的服務。
最後,看一下通過PackageInstaller將apk安裝到SD卡的過程:
從圖中流程可以看出,與剛才介紹的adb將apk安裝到內部儲存器的流程是差不多的(包括apk移動操作),只是安裝位置不一樣,安裝引數(InstallArgs)的實現不一樣。
MeasureParams: 計算APP佔用儲存空間時使用。
InstallParams: apk安裝時使用。
FileInstallArgs:apk安裝到內部儲存器時使用。
AsecInstallArgs:apk安裝到SD卡時使用或者安裝FORWARDLOCK型別的apk。
MoveInstallArgs:apk從內部儲存器移動到SD卡或者SD卡移動到內部儲存器時使用。
InstallParams的handleStartCopy根據條件新建不同的InstallArgs物件:
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
} else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);
} else {
return new FileInstallArgs(params);
}
}
好了,說到這裡了,apk的更新與解除安裝,可以參照流程,繼續學習。
再介紹一下installd程序,installd是個本地程序,程式碼在frameworks/native/cmds/installd目錄,啟動是在init程序中,
service installd /system/bin/installd
class main
socket installd stream 600 system system
直接看installd.cpp的main函式,
int main(const int argc __unused, char *argv[]) {
char buf[BUFFER_MAX];
struct sockaddr addr;
socklen_t alen;
int lsocket, s;
int selinux_enabled = (is_selinux_enabled() > 0);
setenv("ANDROID_LOG_TAGS", "*:v", 1);
android::base::InitLogging(argv);
ALOGI("installd firing up\n");
union selinux_callback cb;
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
if (initialize_globals() < 0) { //初始化幾個路徑相關的全域性變數
ALOGE("Could not initialize globals; exiting.\n");
exit(1);
}
if (initialize_directories() < 0) { //建立目錄
ALOGE("Could not create directories; exiting.\n");
exit(1);
}
if (selinux_enabled && selinux_status_open(true) < 0) {
ALOGE("Could not open selinux status; exiting.\n");
exit(1);
}
lsocket = android_get_control_socket(SOCKET_PATH);//獲得socket,"installd"
if (lsocket < 0) {
ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
exit(1);
}
if (listen(lsocket, 5)) { //開始監聽socket
ALOGE("Listen on socket failed: %s\n", strerror(errno));
exit(1);
}
fcntl(lsocket, F_SETFD, FD_CLOEXEC);
for (;;) { //進入迴圈
alen = sizeof(addr);
s = accept(lsocket, &addr, &alen);//收到InstallerConnection傳送的資料
if (s < 0) {
ALOGE("Accept failed: %s\n", strerror(errno));
continue;
}
fcntl(s, F_SETFD, FD_CLOEXEC);
ALOGI("new connection\n");
for (;;) {
unsigned short count;
if (readx(s, &count, sizeof(count))) { //讀取資料的長度
ALOGE("failed to read size\n");
break;
}
if ((count < 1) || (count >= BUFFER_MAX)) {
ALOGE("invalid size %d\n", count);
break;
}
if (readx(s, buf, count)) {//讀取資料的內容
ALOGE("failed to read command\n");
break;
}
buf[count] = 0;
if (selinux_enabled && selinux_status_updated() > 0) {
selinux_android_seapp_context_reload();
}
if (execute(s, buf)) break; //解析命令,與cmds陣列定義的命令匹配
}
ALOGI("closing connection\n");
close(s);
}
struct cmdinfo cmds[] = {
{ "ping", 0, do_ping },//測試socket是否連線成功
{ "install", 5, do_install },//建立app資料目錄,如/data/data/vmdl<sessionid>.tmp
{ "dexopt", 9, do_dexopt },//對apk優化,呼叫的是dex2oat程式
{ "markbootcomplete", 1, do_mark_boot_complete },//好像沒用到
{ "movedex", 3, do_move_dex },//classes.dex檔案移動
{ "rmdex", 2, do_rm_dex },//classed.dex刪除
{ "remove", 3, do_remove },//apk解除安裝
{ "rename", 2, do_rename },//目錄重新命名
{ "fixuid", 4, do_fixuid },//uid變動
{ "freecache", 2, do_free_cache },//釋放app快取空間
{ "rmcache", 3, do_rm_cache },//刪除cache目錄內容
{ "rmcodecache", 3, do_rm_code_cache },//刪除code_cache目錄內容
{ "getsize", 8, do_get_size },//獲得指定app的大小
{ "rmuserdata", 3, do_rm_user_data },//刪除使用者資料
{ "cpcompleteapp", 6, do_cp_complete_app },
{ "movefiles", 0, do_movefiles },
{ "linklib", 4, do_linklib },
{ "mkuserdata", 5, do_mk_user_data },
{ "mkuserconfig", 1, do_mk_user_config },
{ "rmuser", 2, do_rm_user },
{ "idmap", 3, do_idmap },
{ "restorecondata", 4, do_restorecon_data },
{ "createoatdir", 2, do_create_oat_dir },
{ "rmpackagedir", 1, do_rm_package_dir },
{ "linkfile", 3, do_link_file }
};
struct cmdinfo的原型:
struct cmdinfo {
const char *name;
unsigned numargs;
int (*func)(char **arg, char reply[REPLY_MAX]);
};
未完待續,有不對的地方,請指正。