Android PackageManagerService分析二:安装APK

本文探讨了Android系统的APK安装流程,从installd服务的介绍开始,详细分析了Package Installer在内部和外部存储空间的安装步骤,揭示了用户点击安装APK后背后的技术细节。
摘要由CSDN通过智能技术生成

这一章我们开始分析APK的安装过程,当我们从网上download一个APK后,点击这个APK文件,就会启动PackageInstallerActivity这个页面来parse这个APK文件,并提示一些信息给用户,当用户点击安装以后,就会开始APK的安装。在介绍安装APK之前,我们先来分析一下installd。

installd介绍

首先从installd的启动开始介绍,installd是在init.rc脚本中启动的:
service installd /system/bin/installd
    class main
    socket installd stream 600 system system

上面的脚本文件会创建一个/dev/socket/installd的unix domain socket,并把socket文件描述符返回给installd。我们简单的来看看installd的main函数:
int main(const int argc, const char *argv[]) {
    char buf[BUFFER_MAX];
    struct sockaddr addr;
    socklen_t alen;
    int lsocket, s, count;

    ALOGI("installd firing up\n");

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

    drop_privileges();

    lsocket = android_get_control_socket(SOCKET_PATH);
    if (lsocket < 0) {
        ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
        exit(1);
    }
    if (listen(lsocket, 5)) {
        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);

main函数中首先做一些全局化的初始化设置,然后从底层获取创建的socket号,并在这个socket上面开始listen。在这里installd就是一个server,等待client的连接。那我们再来看一下client的创建过程,也就是Installer对象的实例化,实例化Installer也是在systemServer当中:
            installer = new Installer();
            installer.ping();
来看一下 Installer的ping方法,它是用于建立也installd之间的socket连接,并测试是否可以正常的发送命令给installd:
    public boolean ping() {
        if (execute("ping") < 0) {
            return false;
        } else {
            return true;
        }
    }

    private int execute(String cmd) {
        String res = transaction(cmd);
        try {
            return Integer.parseInt(res);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }

    private synchronized String transaction(String cmd) {
        if (!connect()) {
            return "-1";
        }

        if (!writeCommand(cmd)) {
            if (!connect() || !writeCommand(cmd)) {
                return "-1";
            }
        }
        if (readReply()) {
            String s = new String(buf, 0, buflen);
            return s;
        } else {
            return "-1";
        }
    }

connect函数比较简单,就是创建一个socket,并且与前面的installd进行连接,如果成功连接上,就返回true。那看一下writeCommand的实现:
    private boolean writeCommand(String _cmd) {
        byte[] cmd = _cmd.getBytes();
        int len = cmd.length;
        if ((len < 1) || (len > 1024))
            return false;
        buf[0] = (byte) (len & 0xff);
        buf[1] = (byte) ((len >> 8) & 0xff);
        try {
            mOut.write(buf, 0, 2);
            mOut.write(cmd, 0, len);
        } catch (IOException ex) {
            Slog.e(TAG, "write error");
            disconnect();
            return false;
        }
        return true;
    }

发送到installd的数据格式:cmd长度+cmd本身。再到installd中看如何处理ping的命令:
        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 (execute(s, buf)) break;
        }
首先读出两字节的长度保存在count中,再从socket读出长度为count的命令保存在buf中,最后调用execute去执行命令:
static int execute(int s, char cmd[BUFFER_MAX])
{
    char reply[REPLY_MAX];
    char *arg[TOKEN_MAX+1];
    unsigned i;
    unsigned n = 0;
    unsigned short count;
    int ret = -1;

    reply[0] = 0;

    arg[0] = cmd;
    while (*cmd) {
        if (isspace(*cmd)) {
            *cmd++ = 0;
            n++;
            arg[n] = cmd;
            if (n == TOKEN_MAX) {
                ALOGE("too many arguments\n");
                goto done;
            }
        }
        cmd++;
    }

    for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
        if (!strcmp(cmds[i].name,arg[0])) {
            if (n != cmds[i].numargs) {
                ALOGE("%s requires %d arguments (%d given)\n",
                     cmds[i].name, cmds[i].numargs, n);
            } else {
                ret = cmds[i].func(arg + 1, reply);
            }
            goto done;
        }
    }
    ALOGE("unsupported command '%s'\n", arg[0]);

done:
    if (reply[0]) {
        n = snprintf(cmd, BUFFER_MAX, "%d %s", ret, reply);
    } else {
        n = snprintf(cmd, BUFFER_MAX, "%d", ret);
    }
    if (n > BUFFER_MAX) n = BUFFER_MAX;
    count = n;

    // ALOGI("reply: '%s'\n", cmd);
    if (writex(s, &count, sizeof(count))) return -1;
    if (writex(s, cmd, count)) return -1;
    return 0;
}

execute函数也比较简单,首先从命令行中parse出命令和参数,然后从全局的cmds数组中获取到与要执行的命令匹配的函数并执行,最后将返回值和结果发送给client端。这里的cmds数组就表示了所有installd可以执行的命令:
struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },
    { "install",              4, do_install },
    { "dexopt",               3, do_dexopt },
    { "movedex",              2, do_move_dex },
    { "rmdex",                1, do_rm_dex },
    { "remove",               2, do_remove },
    { "rename",               2, do_rename },
    { "fixuid",               3, do_fixuid },
    { "freecache",            1, do_free_cache },
    { "rmcache",              2, do_rm_cache },
    { "getsize",              6, do_get_size },
    { "rmuserdata",           2, do_rm_user_data },
    { "movefiles",            0, do_movefiles },
    { "linklib",              3, do_linklib },
    { "mkuserdata",           3, do_mk_user_data },
    { "rmuser",               1, do_rm_user },
};


Package Installer过程分析

当我们点击一个APK文件后,就会发送一个Intent给PackageInstallerActivity,PackageInstallerActivity首先会去检查系统是否支持安装未知来源的APK。然后会去解析这个APK的一些信息显示出来给用户,例如是否需要读取通讯录、是否要访问网络等等。当用户点击这个activity的安装按钮后,就会开始真正的执行安装,它对应的函数如下:
            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);
这个方法的第一个参数是APK所在的路径URI;第二个参数用来监控整个安装过程;第三个参数installFlags默认为0;第四个参数是APK的包名;第五个参数用于做数字签名验证。当然这里调用到PMS的真正实现的函数需要借助binder的跨进程来实现:
    public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                null);

        final int uid = Binder.getCallingUid();

        UserHandle user;
        if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(UserHandle.getUserId(uid));
        }

        final int filteredFlags;

        if (uid == Process.SHELL_UID || uid == 0) {
            if (DEBUG_INSTALL) {
                Slog.v(TAG, "Install from ADB");
            }
            filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
        } else {
            filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
        }

        verificationParams.setInstallerUid(uid);

        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
                verificationParams, encryptionParams, user);
        mHandler.sendMessage(msg);
    }
这里首先做一些权限的检查,并判断当前安装APK的user是否具有相应的权限。在安装APK的时候分为程序开发人员通过ADB安装和user通过网上下载安装,当通过ADB安装时,往往不需要对程序做验证,这就是INSTALL_FROM_ADB这个flag的作用。最后构造一个INIT_COPY的cmd,并带有InstallParams的message发给PackageHandler处理。在分析PackageHandler处理INIT_COPY之前,先来看一下几种安装Params的关系:



再来看PackageHandler处理INIT_COPY这个命令:
                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (!mBound) {
                        if (!connectToService()) {
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            return;
                        } else {
                            mPendingInstalls.add(idx, params);
                        }
                    } else {
                        mPendingInstalls.add(idx, params);
                        if (idx == 0) {
                            mHandler.sendE
  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值