Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android7.0 PackageManagerService (3) APK安裝

Android7.0 PackageManagerService (3) APK安裝

編輯:關於Android編程

在本篇博客中,我們分析一下Android中的APK是如何安裝的,以及PKMS在這個過程中進行了哪些工作。

APK的安裝方式有很多,我們先來看看如何用adb命令進行安裝。
我們從adb install開始分析,該命令有多個參數,這裡僅考慮最基本的adb install xxxx.apk。

一、adb命令
看看system/core/adb/commandline.cpp中的adb_commandline函數:

int adb_commandline(int argc, const char **argv) {
    ...........
    else if (!strcmp(argv[0], "install")) {
        if (argc < 2) return usage();
        FeatureSet features;
        std::string error;
        if (!adb_get_feature_set(&features, &error)) {
            fprintf(stderr, "error: %s\n", error.c_str());
            return 1;
        }

        if (CanUseFeature(features, kFeatureCmd)) {
            //支持FeatureCmd時調用install_app
            return install_app(transport_type, serial, argc, argv);
        }
        //否則,利用install_app_legacy
        return install_app_legacy(transport_type, serial, argc, argv);
    }
    ...........
}

1、install_app_legacy
我看先看看傳統的install_app_legacy:

static int install_app_legacy(TransportType transport, const char* serial, int argc, const char** argv) {
    //待安裝的APK目前還在源機器上,現在需要把APK的文件復制到手機裡
    //如果安裝在手機內部存儲,那麼目的地址為DATA_DEST
    //如果安裝在SD卡上,則目的地址為SD_DEST
    static const char *const DATA_DEST = "/data/local/tmp/%s";
    static const char *const SD_DEST = "/sdcard/tmp/%s";
    .........
    //默認安裝到手機內部
    const char* where = DATA_DEST;
    for (i = 1; i < argc; i++) {
        //攜帶參數-s時,才安裝到SD卡
        if (!strcmp(argv[i], "-s")) {
            where = SD_DEST;
        }
    }

    //解析參數,判斷adb命令中是否攜帶了有效的apk文件名
    ...........

    //取出apk名
    std::vector apk_file = {argv[last_apk]};
    //構造apk目的地址
    std::string apk_dest = android::base::StringPrintf(
            where, adb_basename(argv[last_apk]).c_str());

    //do_sync_push將此APK文件傳輸到手機的目標路徑,失敗的話將跳轉到clenaup_apk
    if (!do_sync_push(apk_file, apk_dest.c_str())) goto cleanup_apk;

    //執行pm_command
    result = pm_command(transport, serial, argc, argv);

cleanup_apk:
    //刪除剛才傳輸的文件
    //PKMS在安裝過程中會將該APK復制一份到/data/app目錄下,所有data/local/tmp目錄下對應的文件可以刪除
    delete_file(transport, serial, apk_dest);
    return result;
}

從代碼來看,傳統的安裝方式就是將源機器中的APK文件拷貝到目的手機的tmp目錄下,然後調用pm_command進行處理。

2、install_app
我們再看看支持FeatureCmd的機器,如何安裝APK:

static int install_app(TransportType transport, const char* serial, int argc, const char** argv) {
    //利用參數創建出本地文件的名稱
    const char* file = argv[argc - 1];

    //解析參數,判斷adb命令中是否攜帶了有效的apk文件名
    .........

    //adb_open中將創建出這個file對應的文件
    int localFd = adb_open(file, O_RDONLY);
    ............
    std::string cmd = "exec:cmd package";
    //添加cmd參數
    ............
    //連接源端,獲取源APK文件的描述符
    int remoteFd = adb_connect(cmd, &error);
    ............
    //將remoteFd中的數據寫入到localFd
    copy_to_file(localFd, remoteFd);
    //得到結果
    read_status_line(remoteFd, buf, sizeof(buf));

    adb_close(localFd);
    adb_close(remoteFd);
    ..........
    return 0;
}

從代碼來看install_app就是將源機器的文件復制到了目的機器中,並沒有進行額外的操作。猜想可能是支持特殊FeatureCmd的機器,PKMS能夠監聽到這個拷貝,然後觸發後續的掃描工作。這個過程沒有研究過對應代碼,暫時不做深入分析。

對於傳統的安裝方式,我們需要繼續往下看看pm_command。

二、pm_command
我們先看看pm_command函數:

static int pm_command(TransportType transport, const char* serial, int argc, const char** argv) {
    std::string cmd = "pm";

    //構造pm cmd
    while (argc-- > 0) {
        cmd += " " + escape_arg(*argv++);
    }

    //發送shell命令給adbd
    return send_shell_command(transport, serial, cmd, false);
}

我們跟進下send_shell_command:

// Connects to the device "shell" service with |command| and prints the
// resulting output.
static int send_shell_command(TransportType transport_type, const char* serial,
                              const std::string& command,
                              bool disable_shell_protocol,
                              std::string* output=nullptr,
                              std::string* err=nullptr) {
    ...........
    while (true) {
        bool attempt_connection = true;

        // Use shell protocol if it's supported and the caller doesn't explicitly disable it.
        if (!disable_shell_protocol) {
            .......
            if (adb_get_feature_set(&features, &error)) {
                //如果定義了feature,則替換shell protocol
                use_shell_protocol = CanUseFeature(features, kFeatureShell2);
            } else {
                // Device was unreachable.
                attempt_connection = false;
            }
        }

        if (attempt_connection) {
            std::string error;
            //此時command中攜帶的就是以pm開頭的命令
            std::string service_string = ShellServiceString(use_shell_protocol, "", command);

            //向shell服務發送命令
            fd = adb_connect(service_string, &error);
            if (fd >= 0) {
                break;
            }
        }
        ............
    }

    //讀取返回結果
    int exit_code = read_and_dump(fd, use_shell_protocol, output, err);
    if (adb_close(fd) < 0) {
        ..........
    }

    return int exit_code;
}

從上面的代碼來看,pm_command就是向shell服務發送pm命令。

pm是一個可執行腳本,我們在終端上調用adb shell,然後執行pm,可以得到以下結果:

root:/ # pm
usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]
       pm list permission-groups
       pm list permissions [-g] [-f] [-d] [-u] [GROUP]
       pm list instrumentation [-f] [TARGET-PACKAGE]
..........

pm腳本定義在frameworks/base/cmds/pm中:

base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"

在編譯system.img時,會根據Android.mk將該腳本復制到system/bin目錄下。
從腳本的內容來看,當調用pm時,將向app_process目錄的main函數傳入Pm對應的參數:

我們看看對應的定義於app_main.cpp的main函數(前面的博客分析過,這個其實也是zygote啟動的函數):

//app_process的main函數
int main(int argc, char* const argv[]) {
    ........
    //解析參數
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            //此時我們有參數,進入該分支設置className
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }
    ...........
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        //此時不再是啟動zygote,而是啟動className對應的類
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        .........
    }
    ........
}

我們跟進AndroidRuntime.cpp的start函數:

void AndroidRuntime::start(const char* className, const Vector& options, bool zygote)
{
    ..........
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
    if (startMeth == NULL) {
        ALOGE("JavaVM unable to find main() in '%s'\n", className);
    } else {
        //反射調用main函數,從native層進入java世界
        env->CallStaticVoidMethod(startClass, startMeth, strArray);
    }
    .........
}

於是流程會進入到RuntimeInit的main函數:

public static final void main(String[] argv) {
    ........
    //進行一些常規的初始化工作
    commonInit();
    /*
    * Now that we're running in interpreted code, call back into native code
    * to run the system.
    */
    nativeFinishInit();
    .........
}

native函數定義在framework/base/core/jni/AndroidRuntime.cpp中,對應的函數為:

static void com_android_internal_os_RuntimeInit_nativeFinishInit(JNIEnv* env, jobject clazz)
{
    //gCurRuntime保存AndroidRuntime,實際上是AndroidRuntime的子類
    gCurRuntime->onStarted();
}

App_main.cpp中定義的AppRuntime繼承AndroidRuntime,實現了onStarted函數:

virtual void onStarted()
{
    //binder通信相關的
    sp proc = ProcessState::self();
    ALOGV("App process: starting thread pool.\n");
    proc->startThreadPool();

    AndroidRuntime* ar = AndroidRuntime::getRuntime();
    //調用AndroidRuntime.cpp的callMain函數,參數與Pm.java相關
    ar->callMain(mClassName, mClass, mArgs);

    IPCThreadState::self()->stopProcess();
}
status_t AndroidRuntime::callMain(const String8& className, jclass clazz,
        const Vector& args) {
    ..........
    env = getJNIEnv();
    ..........
    methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
    ..........
    const size_t numArgs = args.size();
    stringClass = env->FindClass("java/lang/String");
    strArray = env->NewObjectArray(numArgs, stringClass, NULL);

    for (size_t i = 0; i < numArgs; i++) {
        jstring argStr = env->NewStringUTF(args[i].string());
        env->SetObjectArrayElement(strArray, i, argStr);
    }
    ...........
    //最終調用了Pm.java的main函數
    env->CallStaticVoidMethod(clazz, methodId, strArray);
    return NO_ERROR;
}

這裡自己初次看時,認為這裡沒有fork新的進程,那麼APK安裝運行在zygote進程中。
實際上這是一個錯誤的理解,說明自己的理解還不到位。
init創建zygote進程時,是fork出一個子進程,然後才調用app_main中的函數,此時整個zygote嚴格來講只是一個native進程;當app_main函數最終通過AndroidRuntime等反射調用zygoteInit.java的main函數後,才演變成了Java層的zygote進程。
這裡的情況是類似的,adb進程發送消息給Shell服務,Shell服務執行Pm腳本,由於exec函數並未創建出新的進程,因此調用app_main後整個代碼仍然是運行在Shell服務對應的native進程中,同樣通過反射後演變為Java層中的進程。

這裡自己花了很多的筆墨來分析如何從執行腳本文件,到啟動Java進程。
主要是弄懂這個機制後,我們實際上完全可以學習pm的寫法,依葫蘆畫瓢寫一個腳本文件,然後定義對應的Java文件。
通過腳本命令,來讓Java層的進程提供服務。

最後,我們通過一個圖來總結一下這個過程:

三、Pm中的流程
現在我們進入了Pm.java的main函數:

public static void main(String[] args) {
    int exitCode = 1;
    try {
        //別被寫法欺騙了,Pm並沒有繼承Runnable
        exitCode = new Pm().run(args);
    } catch (Exception e) {
        .......
    }
    System.exit(exitCode);
}

//根據參數進行對應的操作,現在我們僅關注APK安裝
public int run(String[] args) throws RemoteException {
    ...........
    //利用Binder通信,得到PKMS服務端代理
    mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));

    //保存參數
    mArgs = args;
    String op = args[0];
    mNextArg = 1;

    ............
    //返回PKMS中保存的PackageInstallerService
    mInstaller = mPm.getPackageInstaller();
    ........
    if ("install".equals(op)) {
        //安裝APK將調用runInstall
        return runInstall();
    }
    .......
}

我們跟進runInstall函數:

private int runInstall() throws RemoteException {
    //根據參數創建InstallParams,其中包含了SessionParams,標志為MODE_FULL_INSTALL
    final InstallParams params = makeInstallParams();
    //1 創建Session
    final int sessionId = doCreateSession(params.sessionParams,
            params.installerPackageName, params.userId);
    try {
        //inPath對應於安裝的APK文件
        final String inPath = nextArg();
        .......
        //2 wirite session
        if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
                false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
        //3 commit session
        if (doCommitSession(sessionId, false /*logSuccess*/)
                != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
        System.out.println("Success");
        return 0;
    } finally {
        ........
    }
}

從上面的代碼來看,runInstall主要進行了三件事,即創建session、對session進行寫操作,最後提交session。
接下來,我們來看看每一步究竟在干些什麼:

1、 create session

private int doCreateSession(SessionParams params, String installerPackageName, int userId)
        throws RemoteException {
    //通過ActivityManagerService得到"runInstallCreate"(作為Context對應的字符串)對應的uid
    userId = translateUserId(userId, "runInstallCreate");
    if (userId == UserHandle.USER_ALL) {
        userId = UserHandle.USER_SYSTEM;
        params.installFlags |= PackageManager.INSTALL_ALL_USERS;
    }

    //通過PackageInstallerService創建session
    final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
    return sessionId;
}

跟進一下PackageInstallerService的createSession函數:

@Override
public int createSession(SessionParams params, String installerPackageName, int userId) {
    try {
        return createSessionInternal(params, installerPackageName, userId);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}

private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
        throws IOException {
    //安裝權限檢查
    .......
    //修改SessionParams的installFlags
    if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
        params.installFlags |= PackageManager.INSTALL_FROM_ADB;
    } else {
        .........
    }
    ..........
    // Defensively resize giant app icons
    //調整app圖標大小,這裡應該是不同安裝方式共用的代碼
    //通過adb安裝apk時,應該還沒有解析到app圖標
    if (params.appIcon != null) {
        ........
    }

    //根據SessionParams的installFlags進行一些操作
    ..........
    } else {
        // For now, installs to adopted media are treated as internal from
        // an install flag point-of-view.
        //adb安裝應該進入這個分支(不添加參數指定安裝在sd card時),為SessionParams設置InstallInternal Flag,後文會用到
        params.setInstallFlagsInternal();
        ...........
    }

    final int sessionId;
    final PackageInstallerSession session;
    synchronized (mSessions) {
        // Sanity check that installer isn't going crazy
        //確保同一個uid沒有提交過多的Session,MAX_ACTIVE_SESSIONS為1024
        final int activeCount = getSessionCount(mSessions, callingUid);
        if (activeCount >= MAX_ACTIVE_SESSIONS) {
            throw new IllegalStateException(
                    "Too many active sessions for UID " + callingUid);
        }

        //同樣確保同一個uid沒有提交過多的Session,MAX_HISTORICAL_SESSIONS為1048576
        final int historicalCount = getSessionCount(mHistoricalSessions, callingUid);
        if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
            throw new IllegalStateException(
                    "Too many historical sessions for UID " + callingUid);
        }
        ........
        //sessionId是個隨機值
        sessionId = allocateSessionIdLocked();

        // We're staging to exactly one location
        File stageDir = null;
        String stageCid = null;
        //根據installFlags,決定安裝目錄,前文已經提到,過默認將安裝到internal目錄下
        if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
            final boolean isEphemeral =
                    (params.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
            //此處將會在臨時性的data目錄下創建出file,應該是作為copy的目的地址
            stageDir = buildStageDir(params.volumeUuid, sessionId, isEphemeral);
        } else {
            stageCid = buildExternalStageCid(sessionId);
        }

        session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
                mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
                params, createdMillis, stageDir, stageCid, false, false);
                mSessions.put(sessionId, session);
        mSessions.put(sessionId, session);
    }
    //進行回調
    mCallbacks.notifySessionCreated(session.sessionId, session.userId);
    //在mSessionsFile中進行記錄
    writeSessionsAsync();

    return sessionId;
}

從代碼來看,上述代碼的目的就是為APK安裝做好准備工作,例如權限檢查、目的臨時文件的創建等, 最終創建出PackageInstallerSession對象。PackageInstallerSession可以看做是”安裝APK”這個請求的封裝,其中包含了處理這個請求需要的一些信息。
這種設計方式,大致可以按照命令模式來理解。

實際上PackageInstallerSession不僅是分裝請求的對象,其自身還是個服務端:

public class PackageInstallerSession extends IPackageInstallerSession.Stub 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved