Android PackageManagerService分析二:安装APK

来源:互联网 发布:最长公共子序列c语言 编辑:程序博客网 时间:2024/04/28 12:02

原文地址: 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方法完成真正的创建目录以及拷贝文件的操作,这部分有兴趣的读者可以看看这个方法的实现。

0 0
原创粉丝点击