Android7.0 PackageManagerService (3) APK安装

在本篇博客中,我们分析一下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<const char*> 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<String8>& 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<ProcessState> 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<String8>& 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 
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值