Android PackageManagerService分析二:安装APK

原文地址: http://www.xuebuyuan.com/2172921.html


这一章我们开始分析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.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }

在安装APK的时候,PMS还得依赖另一个service,也就是ContainerService,它具体负责实现APK等相关资源文件在内部或外部存储器上的存储工作。如果之前我们没有绑定ContainerService,这里首先会绑定服务,并把我们前面创建的InstallParams加入到mPendingInstalls列表中,mPendingInstalls保存所有的安装请求。如果我们前面绑定过ContainerService,就再发送MCS_BOUND消息给自身。当然,在我们在绑定服务之后,同样也会发送MCS_BOUND消息。我们来看MCS_BOUND的处理流程:

                case MCS_BOUND: {
                    if (msg.obj != null) {
                        mContainerService = (IMediaContainerService) msg.obj;
                    }
                    if (mContainerService == null) {

                    } else if (mPendingInstalls.size() > 0) {
                        HandlerParams params = mPendingInstalls.get(0);
                        if (params != null) {
                            if (params.startCopy()) {
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    mHandler.sendEmptyMessage(MCS_BOUND);
                                }
                            }
                        }
                    } else {

                    }
                    break;
                }

在处理MCS_BOUND消息时,首先把前面得到的ContainerService服务赋给mContainerService。然后不断的处理mPendingInstalls所有的安装请求,这里调用InstallParams的startCopy方法:

        final boolean startCopy() {
            boolean res;
            try {

                if (++mRetries > MAX_RETRIES) {
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            handleReturnCode();
            return res;
        }

这里主要调用InstallParams的handleStartCopy方法来完成资源文件的拷贝:

        public void handleStartCopy() throws RemoteException {
            int ret = PackageManager.INSTALL_SUCCEEDED;
            final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
            final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
            PackageInfoLite pkgLite = null;

            if (onInt && onSd) {

            } else {
                final long lowThreshold;

                final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
                        .getService(DeviceStorageMonitorService.SERVICE);
                if (dsm == null) {
                    Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
                    lowThreshold = 0L;
                } else {
                    lowThreshold = dsm.getMemoryLowThreshold();
                }

                try {
                    mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION);

                    final File packageFile;
                    if (encryptionParams != null || !"file".equals(mPackageURI.getScheme())) {
                         //关于drm文件的安装
                    } else {
                        packageFile = new File(mPackageURI.getPath());
                    }

                    if (packageFile != null) {
                        final String packageFilePath = packageFile.getAbsolutePath();
                        pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
                                lowThreshold);

                        if (pkgLite.recommendedInstallLocation
                                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                            final long size = mContainerService.calculateInstalledSize(
                                    packageFilePath, isForwardLocked());
                            if (mInstaller.freeCache(size + lowThreshold) >= 0) {
                                pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,
                                        flags, lowThreshold);
                            }

                            if (pkgLite.recommendedInstallLocation
                                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                                pkgLite.recommendedInstallLocation
                                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
                            }
                        }
                    }
                } finally {
                    mContext.revokeUriPermission(mPackageURI,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
            }

            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                int loc = pkgLite.recommendedInstallLocation;
                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                    ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_APK;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_URI;
                } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
                    ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
                } else {
                    loc = installLocationPolicy(pkgLite, flags);
                    if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
                        ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                    } else if (!onSd && !onInt) {
                        if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
                            // Set the flag to install on external media.
                            flags |= PackageManager.INSTALL_EXTERNAL;
                            flags &= ~PackageManager.INSTALL_INTERNAL;
                        } else {
                            flags |= PackageManager.INSTALL_INTERNAL;
                            flags &= ~PackageManager.INSTALL_EXTERNAL;
                        }
                    }
                }
            }

handleStartCopy这个函数比较长,我们先来看前一部分,这里首先从installFlags中检查是否有指定安装位置,然后从DeviceStorageMonitorService中取得系统设置的内存空间的最低阀值。然后调用ContainerService去计算APK文件的大小,并检查当前内存空间是否足够,如果不足够,则调用Installd的freeCache方法尝试去释放一些cache文件。最终再来检查是否有足够的空间,并决定安装位置。接着来看handleStartCopy的实现:

            final InstallArgs args = createInstallArgs(this);
            mArgs = args;

            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                int userIdentifier = getUser().getIdentifier();
                if (userIdentifier == UserHandle.USER_ALL
                        && ((flags & PackageManager.INSTALL_FROM_ADB) != 0)) {
                    userIdentifier = UserHandle.USER_OWNER;
                }

                final int requiredUid = mRequiredVerifierPackage == null ? -1
                        : getPackageUid(mRequiredVerifierPackage, userIdentifier);
                if (requiredUid != -1 && isVerificationEnabled(flags)) {
                        
                } else {
                    ret = args.copyApk(mContainerService, true);
                }
            }

            mRet = ret;
        }

这里首先根据安装路径的不同,创建不同的InstallArgs:

    private InstallArgs createInstallArgs(InstallParams params) {
        if (installOnSd(params.flags) || params.isForwardLocked()) {
            return new AsecInstallArgs(params);
        } else {
            return new FileInstallArgs(params);
        }
    }

AsecInstallArgs就是指安装在外部存储空间上;FileInstallArgs是指安装在内部存储空间。来看一下上面的两者的类图关系:

接下来以分安装在内部存储空间和外部存储空间两种情况来分析:
安装在内部存储空间
首先来看FileInstallArgs的copyApk方法:
        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
            if (temp) {
                createCopyFile();
            }
            File codeFile = new File(codeFileName);
            if (!created) {

            }
            ParcelFileDescriptor out = null;
            try {
                out = ParcelFileDescriptor.open(codeFile, ParcelFileDescriptor.MODE_READ_WRITE);
            } catch (FileNotFoundException e) {
                Slog.e(TAG, "Failed to create file descriptor for : " + codeFileName);
                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            }
            int ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            try {
                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                ret = imcs.copyResource(packageURI, null, out);
            } finally {
                IoUtils.closeQuietly(out);
                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }

            if (isFwdLocked()) {

            }

            final File nativeLibraryFile = new File(getNativeLibraryPath());
            if (nativeLibraryFile.exists()) {
                NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
                nativeLibraryFile.delete();
            }
            try {
                int copyRet = copyNativeLibrariesForInternalApp(codeFile, nativeLibraryFile);
                if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
                    return copyRet;
                }
            } catch (IOException e) {
                ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
            }

            return ret;
        }

因为前面传进来的第二个参数是true,所以这里这里首先调用createCopyFile创建一个临时文件:

        void createCopyFile() {
            installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
            codeFileName = createTempPackageFile(installDir).getPath();
            resourceFileName = getResourcePathFromCodePath();
            libraryPath = getLibraryPathFromCodePath();
            created = true;
        }

installDir就是/data/app,createTempPackageFile在/data/app目录下面创建一个以"vmdl"开始,并以".tmp"结尾的临时文件。resourceFileName就是codeFileName所在的路径名。libraryPath在/data/app-lib路径下面以"vmdlxxxx"命名的文件。回到copyApk函数,接着调用ContainerService的copyResource方法将原始packageURI所指向的文件内容拷贝到我们创建的临时文件里面。最后将APK文件里面的lib文件全部拷贝到libraryPath所指定的文件目录下面。当数据拷贝完成,接着到InstallParams的startCopy方法中会调用handleReturnCode来处理拷贝的结果:

        void handleReturnCode() {
            if (mArgs != null) {
                processPendingInstall(mArgs, mRet);

            }
        }

    private void processPendingInstall(final InstallArgs args, final int currentStatus) {
        mHandler.post(new Runnable() {
            public void run() {
                mHandler.removeCallbacks(this);

                PackageInstalledInfo res = new PackageInstalledInfo();
                res.returnCode = currentStatus;
                res.uid = -1;
                res.pkg = null;
                res.removedInfo = new PackageRemovedInfo();
                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                    args.doPreInstall(res.returnCode);
                    synchronized (mInstallLock) {
                        installPackageLI(args, true, res);
                    }
                    args.doPostInstall(res.returnCode, res.uid);
                }

在processPendingInstall里面,首先调用FileInstallArgs的doPreInstall方法,默认的它什么不会做。然后执行真正的安装过程installPackageLI:

    private void installPackageLI(InstallArgs args,
            boolean newInstall, PackageInstalledInfo res) {
        int pFlags = args.flags;
        String installerPackageName = args.installerPackageName;
        File tmpPackageFile = new File(args.getCodePath());
        boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
        boolean onSd = ((pFlags & PackageManager.INSTALL_EXTERNAL) != 0);
        boolean replace = false;
        int scanMode = (onSd ? 0 : SCAN_MONITOR) | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
                | (newInstall ? SCAN_NEW_INSTALL : 0);

        res.returnCode = PackageManager.INSTALL_SUCCEEDED;

        int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
        PackageParser pp = new PackageParser(tmpPackageFile.getPath());
        pp.setSeparateProcesses(mSeparateProcesses);
        final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
                null, mMetrics, parseFlags);
        if (pkg == null) {
            res.returnCode = pp.getParseError();
            return;
        }
        String pkgName = res.name = pkg.packageName;
        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
            if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
                res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
                return;
            }
        }
        if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
            res.returnCode = pp.getParseError();
            return;
        }

这里首先调用PackageParser的parsePackage方法解析AndroidManifest文件,构造一个Package对象,前面我们讲过Package对象了,大致内容如下:

然后手机APK文件中的数字签名信息。接着来看installPackageLI方法:
        pp = null;
        String oldCodePath = null;
        boolean systemApp = false;
        synchronized (mPackages) {

            if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {

            }
            PackageSetting ps = mSettings.mPackages.get(pkgName);
            if (ps != null) {

        }

        if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
            res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            return;
        }
        
        setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
        pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();
        if (replace) {

        } else {
            installNewPackageLI(pkg, parseFlags, scanMode, args.user,
                    installerPackageName, res);
        }
        synchronized (mPackages) {
            final PackageSetting ps = mSettings.mPackages.get(pkgName);
            if (ps != null) {
                res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
            }
        }
    }

这里先不关注APK的更新,直接来看FileInstallArgs的doRename方法:

        boolean doRename(int status, final String pkgName, String oldCodePath) {
            if (status != PackageManager.INSTALL_SUCCEEDED) {

            } else {
                final File oldCodeFile = new File(getCodePath());
                final File oldResourceFile = new File(getResourcePath());
                final File oldLibraryFile = new File(getNativeLibraryPath());

                final String apkName = getNextCodePath(oldCodePath, pkgName, ".apk");
                final File newCodeFile = new File(installDir, apkName + ".apk");
                if (!oldCodeFile.renameTo(newCodeFile)) {
                    return false;
                }
                codeFileName = newCodeFile.getPath();

                final File newResFile = new File(getResourcePathFromCodePath());
                if (isFwdLocked() && !oldResourceFile.renameTo(newResFile)) {
                    return false;
                }
                resourceFileName = newResFile.getPath();

                final File newLibraryFile = new File(getLibraryPathFromCodePath());
                if (newLibraryFile.exists()) {
                    NativeLibraryHelper.removeNativeBinariesFromDirLI(newLibraryFile);
                    newLibraryFile.delete();
                }
                if (!oldLibraryFile.renameTo(newLibraryFile)) {
                    Slog.e(TAG, "Cannot rename native library directory "
                            + oldLibraryFile.getPath() + " to " + newLibraryFile.getPath());
                    return false;
                }
                libraryPath = newLibraryFile.getPath();

                if (!setPermissions()) {
                    return false;
                }

                return true;
            }
        }

getNextCodePath返回一个类似"包名-num.apk"的文件名, 例如以新浪微博为例,apkName为"com.sina.weibo-1.apk"。然后将原来的codeFileName改为新的名字。resourceFileName和libraryPath也会做类似的改变。回到installPackageLI函数中,接着调用installNewPackageLI方法将新的package文件里面的资源加入到PMS的数据结构中,让PMS来管理这些activity、service、receiver:

    private void installNewPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, UserHandle user,
            String installerPackageName, PackageInstalledInfo res) {

        String pkgName = pkg.packageName;

        boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
        synchronized(mPackages) {
            if (mSettings.mRenamedPackages.containsKey(pkgName)) {
                res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                return;
            }
            if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.mPath)) {
                res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                return;
            }
        }
        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
                System.currentTimeMillis(), user);
        if (newPackage == null) {

        } else {
            updateSettingsLI(newPackage,
                    installerPackageName,
                    null, null,
                    res);
        }
    }

在installNewPackageLI方法中首先调用scanPackageLI方法把新package的资源归入到PMS中,并创建一个PackageSettings对象,加入到Settings中的mPackages这个map中。这个函数我们在前面一章中启动PMS中大致介绍过了,这里主要来看一下与安装有关的部分:

    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, long currentTime, UserHandle user) {
                    File dataPath;
        if (mPlatformPackage == pkg) {

        } else {
            dataPath = getDataPathForPackage(pkg.packageName, 0);

            boolean uidError = false;

            if (dataPath.exists()) {
     
            } else {

                int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
                                           pkg.applicationInfo.seinfo);

                if (dataPath.exists()) {
                    pkg.applicationInfo.dataDir = dataPath.getPath();
                } else {
                    Slog.w(TAG, "Unable to create data directory: " + dataPath);
                    pkg.applicationInfo.dataDir = null;
                }
            }

        String path = scanFile.getPath();

        if (pkg.applicationInfo.nativeLibraryDir != null) {
            try {
                File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
                final String dataPathString = dataPath.getCanonicalPath();

                if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {

                } else {
                    if (!isForwardLocked(pkg) && !isExternal(pkg)) {
                        if (nativeLibraryDir.getPath().startsWith(dataPathString)) {
                            setInternalAppNativeLibraryPath(pkg, pkgSetting);
                            nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
                        }

                        try {
                            if (copyNativeLibrariesForInternalApp(scanFile, nativeLibraryDir) != PackageManager.INSTALL_SUCCEEDED) {
                                Slog.e(TAG, "Unable to copy native libraries");
                                mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
                                return null;
                            }
                        } catch (IOException e) {
                            Slog.e(TAG, "Unable to copy native libraries", e);
                            mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
                            return null;
                        }
                    }

                    if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
                    final int[] userIds = sUserManager.getUserIds();
                    synchronized (mInstallLock) {
                        for (int userId : userIds) {
                            if (mInstaller.linkNativeLibraryDirectory(pkg.packageName,
                                    pkg.applicationInfo.nativeLibraryDir, userId) < 0) {
                                Slog.w(TAG, "Failed linking native library dir (user=" + userId
                                        + ")");
                                mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
                                return null;
                            }
                        }
                    }
                }
            } catch (IOException ioe) {
                Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
            }
        }
        pkg.mScanPath = path;

        if ((scanMode&SCAN_NO_DEX) == 0) {
            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                    == DEX_OPT_FAILED) {
                mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                return null;
            }
        }

首先取得Package的dataPath,路径为/data/data/包名,当我们第一次安装时,这个目录还没有创建,所以调用createDataDirsLI去创建data目录:

    private int createDataDirsLI(String packageName, int uid, String seinfo) {
        int[] users = sUserManager.getUserIds();
        int res = mInstaller.install(packageName, uid, uid, seinfo);
        if (res < 0) {
            return res;
        }
        for (int user : users) {
            if (user != 0) {

        }
        return res;
    }
这里调用Installer的install方法去创建data目录,因为默认的user只有一个,并且userID为0,我们这里先不关注user的部分。来看一下Installd处理install的方法:
static int do_install(char **arg, char reply[REPLY_MAX])
{
    return install(arg[0], atoi(arg[1]), atoi(arg[2]), arg[3]); /* pkgname, uid, gid, seinfo */
}

int install(const char *pkgname, uid_t uid, gid_t gid, const char *seinfo)
{
    char pkgdir[PKG_PATH_MAX];
    char libsymlink[PKG_PATH_MAX];
    char applibdir[PKG_PATH_MAX];
    struct stat libStat;

    if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
        ALOGE("invalid uid/gid: %d %d\n", uid, gid);
        return -1;
    }

    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
        ALOGE("cannot create package path\n");
        return -1;
    }

    if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, 0)) {
        ALOGE("cannot create package lib symlink origin path\n");
        return -1;
    }

    if (create_pkg_path_in_dir(applibdir, &android_app_lib_dir, pkgname, PKG_DIR_POSTFIX)) {
        ALOGE("cannot create package lib symlink dest path\n");
        return -1;
    }

    if (mkdir(pkgdir, 0751) < 0) {
        ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
        return -1;
    }
    if (chmod(pkgdir, 0751) < 0) {
        ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(pkgdir);
        return -1;
    }

    if (lstat(libsymlink, &libStat) < 0) {
        if (errno != ENOENT) {
            ALOGE("couldn't stat lib dir: %s\n", strerror(errno));
            return -1;
        }
    } else {
        if (S_ISDIR(libStat.st_mode)) {
            if (delete_dir_contents(libsymlink, 1, 0) < 0) {
                ALOGE("couldn't delete lib directory during install for: %s", libsymlink);
                return -1;
            }
        } else if (S_ISLNK(libStat.st_mode)) {
            if (unlink(libsymlink) < 0) {
                ALOGE("couldn't unlink lib directory during install for: %s", libsymlink);
                return -1;
            }
        }
    }

    if (symlink(applibdir, libsymlink) < 0) {
        ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, applibdir,
                strerror(errno));
        unlink(pkgdir);
        return -1;
    }

    if (chown(pkgdir, uid, gid) < 0) {
        ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(libsymlink);
        unlink(pkgdir);
        return -1;
    }

    return 0;
}

这里首先构造几个目录名:pkgdir为/data/data/包名,libsymlink为/data/data/包名/lib,applibdir为/data/app-lib/包名。然后创建pkgdir的目录,并修改相应的权限。然后创建/data/data/包名/lib指向/data/app-lib/包名的符号链接。我们知道安装到data/app-lib下面的nativeLibrary的目录其实”包名-num“,所以我们需要重新建立符号链接,这也就是在scanPackageLI方法中mInstaller.linkNativeLibraryDirectory的作用:

static int do_linklib(char **arg, char reply[REPLY_MAX])
{
    return linklib(arg[0], arg[1], atoi(arg[2]));
}

int linklib(const char* pkgname, const char* asecLibDir, int userId)
{
    char pkgdir[PKG_PATH_MAX];
    char libsymlink[PKG_PATH_MAX];
    struct stat s, libStat;
    int rc = 0;

    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, userId)) {
        ALOGE("cannot create package path\n");
        return -1;
    }
    if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, userId)) {
        ALOGE("cannot create package lib symlink origin path\n");
        return -1;
    }

    if (stat(pkgdir, &s) < 0) return -1;

    if (chown(pkgdir, AID_INSTALL, AID_INSTALL) < 0) {
        ALOGE("failed to chown '%s': %s\n", pkgdir, strerror(errno));
        return -1;
    }

    if (chmod(pkgdir, 0700) < 0) {
        ALOGE("linklib() 1: failed to chmod '%s': %s\n", pkgdir, strerror(errno));
        rc = -1;
        goto out;
    }

    if (lstat(libsymlink, &libStat) < 0) {
        if (errno != ENOENT) {
            ALOGE("couldn't stat lib dir: %s\n", strerror(errno));
            rc = -1;
            goto out;
        }
    } else {
        if (S_ISDIR(libStat.st_mode)) {
            if (delete_dir_contents(libsymlink, 1, 0) < 0) {
                rc = -1;
                goto out;
            }
        } else if (S_ISLNK(libStat.st_mode)) {
            if (unlink(libsymlink) < 0) {
                ALOGE("couldn't unlink lib dir: %s\n", strerror(errno));
                rc = -1;
                goto out;
            }
        }
    }

    if (symlink(asecLibDir, libsymlink) < 0) {
        ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, asecLibDir,
                strerror(errno));
        rc = -errno;
        goto out;
    }

这里的第一个参数是packageName;第二个参数是nativeLibraryDir,也就是/data/app-lib/包名-num;第三个参数userID为0。在linklib中,与install一样,首先创建几个目录路径:pkgdir为/data/data/包名,libsymlink为/data/data/包名/lib。然后先把/data/data/包名/lib这个符号链接删除,然后创建一个新的符号链接/data/data/包名/lib指向/data/app-lib/包名-num。回到scanPackageLI函数中,最后调用performDexOptLI对APK安装包中的dex文件做优化,当然这里会依据当前是采用DVM还是ART的运行环境,分别用dexopt和dex2oat两种不同的命令对classes.dex文件做优化并存储在/data/dalvik-cache下面的文件里,关于dex文件优化的部分,可以参考一下Android虚拟机相关知识。接着回到installNewPackageLI函数中调用updateSettingsLI去更新Settings中的设置:

    private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
            int[] allUsers, boolean[] perUserInstalled,
            PackageInstalledInfo res) {
        String pkgName = newPackage.packageName;
        synchronized (mPackages) {
            mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE);
            mSettings.writeLPr();
        }

        if ((res.returnCode = moveDexFilesLI(newPackage))
                != PackageManager.INSTALL_SUCCEEDED) {
            // Discontinue if moving dex files failed.
            return;
        }

        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);

        synchronized (mPackages) {
            updatePermissionsLPw(newPackage.packageName, newPackage,
                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
                            ? UPDATE_PERMISSIONS_ALL : 0));
            if (isSystemApp(newPackage)) {

            }
            res.name = pkgName;
            res.uid = newPackage.applicationInfo.uid;
            res.pkg = newPackage;
            mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
            mSettings.setInstallerPackageName(pkgName, installerPackageName);
            res.returnCode = PackageManager.INSTALL_SUCCEEDED;
            //to update install status
            mSettings.writeLPr();
        }
    }

这里首先调用Settings的writeLPr方法更新packages.xml文件,将新安装的package信息写到这个xml文件。moveDexFilesLI这里用于处理系统更新后安装目录有变化的情况,需要将原来的优化后的dex文件做重命名。updatePermissionsLPw我们在前面一章也介绍过,它用于给当前安装的APK分配权限,并把相应的gid号保存在PackageSetting或者SharedUserSetting的gids数组中。到这里installPackageLI就介绍完了,回到processPendingInstall方法中:

                final boolean update = res.removedInfo.removedPackage != null;
                boolean doRestore = (!update
                        && res.pkg != null
                        && res.pkg.applicationInfo.backupAgentName != null);

                int token;
                if (mNextInstallToken < 0) mNextInstallToken = 1;
                token = mNextInstallToken++;

                PostInstallData data = new PostInstallData(args, res);
                mRunningInstalls.put(token, data);

                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
                     //与BackAgent(云备份)相关
                }

                if (!doRestore) {
                    Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                    mHandler.sendMessage(msg);
                }
            }
        });
    }

首先构造一个PostInstallData数据结构并添加到正在安装的列表mRunningInstalls中,这里跳过与BackAgent相关的部分。最后往PackageHandler发送一个POST_INSTALL消息,来看PackageHandler如何处理POST_INSTALL消息:

                case POST_INSTALL: {
                    if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
                    PostInstallData data = mRunningInstalls.get(msg.arg1);
                    mRunningInstalls.delete(msg.arg1);
                    boolean deleteOld = false;

                    if (data != null) {
                        InstallArgs args = data.args;
                        PackageInstalledInfo res = data.res;

                        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                            res.removedInfo.sendBroadcast(false, true, false);
                            Bundle extras = new Bundle(1);
                            extras.putInt(Intent.EXTRA_UID, res.uid);

                            int[] firstUsers;
                            int[] updateUsers = new int[0];
                            if (res.origUsers == null || res.origUsers.length == 0) {
                                firstUsers = res.newUsers;
                            } else {

                            }
                            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                    res.pkg.applicationInfo.packageName,
                                    extras, null, null, firstUsers);
                            final boolean update = res.removedInfo.removedPackage != null;
                            if (update) {
                                extras.putBoolean(Intent.EXTRA_REPLACING, true);
                            }
                            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                    res.pkg.applicationInfo.packageName,
                                    extras, null, null, updateUsers);
                            if (update) {

                            }
                            if (res.removedInfo.args != null) {
                                deleteOld = true;
                            }
                        }

                        Runtime.getRuntime().gc();

                        if (deleteOld) {
                            synchronized (mInstallLock) {
                                res.removedInfo.args.doPostDeleteLI(true);
                            }
                        }
                        if (args.observer != null) {
                            try {
                                args.observer.packageInstalled(res.name, res.returnCode);
                            } catch (RemoteException e) {
                                Slog.i(TAG, "Observer no longer exists.");
                            }
                        }
                    } else {
                        Slog.e(TAG, "Bogus post-install token " + msg.arg1);
                    }
                } break;

在POST_INSTALL消息中,主要就是发送一些安装成功的广播,这些广播可以被launch接收,并在桌面上面增加图标等。然后向我们注册的observer回调安装成功的callback。这样在内部存储空间上APK安装过程就介绍完了。我们来看一下大致的的流程图:

从前面的介绍,我们可以把安装APK的过程简化为以下几步:
1.复制文件到code、library和resource等文件
2.解析待安装的APK的manifest文件,并把activity、service、provider等信息更新到PMS的全局数据结构中
3.更新Settings中的PackageSetting等信息
4.调用installd在/data/data和/data/dalvik-cache中新建package的文件目录,并link相应的文件
安装在外部存储空间
在介绍AsecInstallArgs的copyApk之前,我们先来简单的了解一下Android上面的目录结构:/system 存放的是rom的信息;/system/app 存放rom本身附带的软件即系统软件;/system/data 存放/system/app 中核心系统软件的数据文件信息。
    /data 存放的是用户的软件信息(非自带rom安装的软件);/data/app 存放用户安装的软件;/data/data 存放所有软件(包括/system/app 和 /data/app 和 /mnt/asec中装的软件)的一些lib和xml文件等数据信息;/data/dalvik-cache 缓存的dex文件,这里的文件都是可以删除的。
    /mnt 目录,熟悉linux的人都清楚,linux默认挂载外部设备都会挂到这个目录下面去,如将sd卡挂载上去后,会生成一个/mnt/sdcard 目录。
    /sdcard 目录,这是一个软链接(相当于windows的文件夹的快捷方式),链接到/mnt/sdcard 目录,即这个目录的内容就是sdcard的内容。
    在Android 2.2之后的版本允许将应用程序安装于SD卡,每一个安装在SD卡的应用程序,都可以在SD卡中的/sdcard/.android_secure 目录里找到名称中有出现它的程序名,和副文件名为asec的经过特殊加密处理后的档案。当SD卡挂载于手机时,/mnt/sdcard/.android_secure 目录会被映射到/mnt/asec 目录和 /mnt/secure 目录。其中/mnt/asec 目录中主要是程序的安装目录,包括其执行文件和lib文件等;而/mnt/secure 目录中就存放程序加密后的档案。也就是说,在/mnt路径下看到的/mnt/asec目录和/mnt/secure目录并不是真正存在在手机内存或者sd卡的分区挂载目录,它们只是/mnt/sdcard/.android_secure目录的一个影像而已。
    因此,用户程序安装到到sd卡上后,其内容可能分散到:/mnt/asec , /mnt/secure , /data/data 。
我们来看AsecInstallArgs的copyApk方法,通过前面的介绍,copyApk就是将原来的安装文件以及library复制到SD卡中的android_secure目录中,这里会借助ContainerService和MountService。从前面安装到内部存储空间的流程上我们知道,FileInstallArgs和AsecInstallArgs主要区别就是在copyApk和doRename两个方法上面:
        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
            if (temp) {
                createCopyFile();
            } else {
                /*
                 * Pre-emptively destroy the container since it's destroyed if
                 * copying fails due to it existing anyway.
                 */
                PackageHelper.destroySdDir(cid);
            }

            final String newCachePath;
            try {
                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                newCachePath = imcs.copyResourceToContainer(packageURI, cid, getEncryptKey(),
                        RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked());
            } finally {
                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }

            if (newCachePath != null) {
                setCachePath(newCachePath);
                return PackageManager.INSTALL_SUCCEEDED;
            } else {
                return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
            }
        }

首先调用createCopyFile顺序的在SD卡上面创建目录结构,这些目录用于保存待安装的APK文件以及它们的library等资源:

        void createCopyFile() {
            cid = getTempContainerId();
        }

    static String getTempContainerId() {
        int tmpIdx = 1;
        String list[] = PackageHelper.getSecureContainerList();
        if (list != null) {
            for (final String name : list) {
                // Ignore null and non-temporary container entries
                if (name == null || !name.startsWith(mTempContainerPrefix)) {
                    continue;
                }

                String subStr = name.substring(mTempContainerPrefix.length());
                try {
                    int cid = Integer.parseInt(subStr);
                    if (cid >= tmpIdx) {
                        tmpIdx = cid + 1;
                    }
                } catch (NumberFormatException e) {
                }
            }
        }
        return mTempContainerPrefix + tmpIdx;
    }

getTempContainerId调用MountService的方法去获取/mnt/asec下面的所有子目录,这些子目录都是以"smdl2tmp"开始,结尾是一个数字来命名。getTempContainerId找到一个尚未被使用的目录名赋予给cid。在copyApk方法中调用ContainService的copyResourceToContainer方法完成真正的创建目录以及拷贝文件的操作,这部分有兴趣的读者可以看看这个方法的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值