1. 程式人生 > >Android6.0 Framework分析——應用程式APP的安裝過程

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]);
};


未完待續,有不對的地方,請指正。