为了账号安全,请及时绑定邮箱和手机立即绑定

Android Apk安装过程分析(上)

标签:
Android

Apk安装的主要步骤:

为了学习这个过程,真的是陷入了pms的源码很久,也看了很多前人的博文,才算是有了些思路,所以此处先把主要步骤列出来,后面再慢慢分析细节。

  1. 将apk文件复制到data/app目录

  2. 解析apk信息

  3. dexopt操作

  4. 更新权限信息

  5. 完成安装,发送Intent.ACTION_PACKAGE_ADDED广播

下面将具体步骤列张图出来:

5b6d94ca0001c1e307000510.jpg

Paste_Image.png

由图可见安装过程中流转的步骤还是比较多的,下面具体分析

1. 将apk文件copy至data/app目录

1.1 installPackageAsUser

 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);        final int callingUid = Binder.getCallingUid();
        ...
        ...        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
            installFlags |= PackageManager.INSTALL_FROM_ADB;

        } else {            // Caller holds INSTALL_PACKAGES permission, so we're less strict
            // about installerPackageName.

            installFlags &= ~PackageManager.INSTALL_FROM_ADB;
            installFlags &= ~PackageManager.INSTALL_ALL_USERS;
        }

        UserHandle user;        if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(userId);
        }

        verificationParams.setInstallerUid(callingUid);        final File originFile = new File(originPath);        final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(origin, observer, installFlags,
                installerPackageName, verificationParams, user, packageAbiOverride);
        mHandler.sendMessage(msg);

这个方法主要是判断安装来源,包括adb,shell,all_user,然后向PMS的mHandler发送INIT_COPY的消息,这个mHandler运行在一个HandlerThread中。

1.2 handleMessage(INIT_COPY)&handleMessage(MCS_BOUND)

case INIT_COPY:{
                    HandlerParams params = (HandlerParams) msg.obj;                    int idx = mPendingInstalls.size();                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);                    // If a bind was already initiated we dont really
                    // need to do anything. The pending install
                    // will be processed later on.
                    if (!mBound) {                        // If this is the only one pending we might
                        // have to bind to the service again.
                        if (!connectToService()) {
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();                            return;
                        } else {                            // Once we bind to the service, the first
                            // pending request will be processed.
                            mPendingInstalls.add(idx, params);
                        }
                    } else {
                        mPendingInstalls.add(idx, params);                        // Already bound to the service. Just make
                        // sure we trigger off processing the first request.
                        if (idx == 0) {
                            mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
}case MCS_BOUND:{
...
...
 HandlerParams params = mPendingInstalls.get(0);                        if (params != null) {                            if (params.startCopy()) {                                // We are done...  look for more work or to
                                // go idle.
                                if (DEBUG_SD_INSTALL) Log.i(TAG,                                        "Checking for more work or unbind...");                                // Delete pending install
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }                                if (mPendingInstalls.size() == 0) {                                    if (mBound) {                                        if (DEBUG_SD_INSTALL) Log.i(TAG,                                                "Posting delayed MCS_UNBIND");
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);                                        // Unbind after a little delay, to avoid
                                        // continual thrashing.
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
...
...
}

INIT_COPY主要是确保DefaultContainerService已bound,DefaultContainerService是一个应用服务,具体负责实现APK等相关资源文件在内部或外部存储器上的存储工作。而MCS_BOUND中则执行了
params.startCopy()这句,也是最关键的开始copy文件。

1.3 HandlerParams.startCopy

  final boolean startCopy() {            boolean res;            try {                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);                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;
        }

该方法中除了检查重试次数外只是简单的调用了handleStartCopy()handleReturnCode()方法.

1.4 handleStartCopy()

这个方法内容非常多,下面只列出些核心部分

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

            PackageInfoLite pkgLite = null;            if (onInt && onSd) {                // Check if both bits are set.
                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else {
                pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
                        packageAbiOverride);                /*
                 * If we have too little free space, try to free cache
                 * before giving up.
                 */
                if (!origin.staged && pkgLite.recommendedInstallLocation
                        == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {                    final StorageManager storage = StorageManager.from(mContext);                    final long lowThreshold = storage.getStorageLowBytes(
                            Environment.getDataDirectory());                    final long sizeBytes = mContainerService.calculateInstalledSize(
                            origin.resolvedPath, isForwardLocked(), packageAbiOverride);                    if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) {
                        pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
                                installFlags, packageAbiOverride);
                    }
                }
            }
            ...
            ...
                     * No package verification is enabled, so immediately start
                     * the remote call to initiate copy using temporary file.
                     */
                    ret = args.copyApk(mContainerService, true);
               
            }
            mRet = ret;
        }

handleStartCopy的核心就是copyApk,其他的都是些存储空间检查,权限检查等等安全校验

2 .解析apk信息

完成apk copy到data/app目录的操作后,下一步就到了 handleReturnCode,这个方法又跳转到processPendingInstall()方法,下面先来看看processPendingInstall()方法:

2.1 processPendingInstall()

    private void processPendingInstall(final InstallArgs args, final int currentStatus) {        // Queue up an async operation since the package installation may take a little while.
        mHandler.post(new Runnable() {            public void run() {
                mHandler.removeCallbacks(this);                 // Result object to be returned
                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, res); //1.安装
                    }
                    args.doPostInstall(res.returnCode, res.uid);
                }                // A restore should be performed at this point if (a) the install
                // succeeded, (b) the operation is not an update, and (c) the new
                // package has not opted out of backup participation.
                final boolean update = res.removedInfo.removedPackage != null;                final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags;                boolean doRestore = !update
                        && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0);                // Set up the post-install work request bookkeeping.  This will be used
                // and cleaned up by the post-install event handling regardless of whether
                // there's a restore pass performed.  Token values are >= 1.
                int token;                if (mNextInstallToken < 0) mNextInstallToken = 1;
                token = mNextInstallToken++;

                PostInstallData data = new PostInstallData(args, res);
                mRunningInstalls.put(token, data);                if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {                    // Pass responsibility to the Backup Manager.  It will perform a
                    // restore if appropriate, then pass responsibility back to the
                    // Package Manager to run the post-install observer callbacks
                    // and broadcasts.
                    IBackupManager bm = IBackupManager.Stub.asInterface(
                            ServiceManager.getService(Context.BACKUP_SERVICE));                    if (bm != null) {                        if (DEBUG_INSTALL) Log.v(TAG, "token " + token
                                + " to BM for possible restore");                        try {
                            bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token); //2.调用backup服务
                        } catch (RemoteException e) {                            // can't happen; the backup manager is local
                        } catch (Exception e) {
                            Slog.e(TAG, "Exception trying to enqueue restore", e);
                            doRestore = false;
                        }
                    } else {
                        Slog.e(TAG, "Backup Manager not found!");
                        doRestore = false;
                    }
                }                if (!doRestore) {                    // No restore possible, or the Backup Manager was mysteriously not
                    // available -- just fire the post-install work request directly.
                    if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
                    Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                    mHandler.sendMessage(msg);
                }
            }
        });
    }

这个方法有几个关键步骤,一是installPackageLI(args, res);,这个方法具体执行了解析package和后续操作,而再installPackageLI(args, res);执行完毕后会走到bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);,会调用backupservice的restoreAtInstall方法,而restoreAtInstall方法最终又会调用PMSfinishPackageInstall()方法,完成安装。

2.2 installPackageLI(args, res)

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {        final int installFlags = args.installFlags;
        String installerPackageName = args.installerPackageName;
        File tmpPackageFile = new File(args.getCodePath());        boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);        boolean onSd = ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0);        boolean replace = false;        final int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE;        // Result object to be returned
        res.returnCode = PackageManager.INSTALL_SUCCEEDED;        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);        // Retrieve PackageSettings and parse package
        final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
        PackageParser pp = new PackageParser();
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setDisplayMetrics(mMetrics);        final PackageParser.Package pkg;        try {
            pkg = pp.parsePackage(tmpPackageFile, parseFlags);
        } catch (PackageParserException e) {
            res.setError("Failed parse during installPackageLI", e);            return;
        }        // Mark that we have an install time CPU ABI override.
        pkg.cpuAbiOverride = args.abiOverride;

        String pkgName = res.name = pkg.packageName;        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {            if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
                res.setError(INSTALL_FAILED_TEST_ONLY, "installPackageLI");                return;
            }
        }        try {
            pp.collectCertificates(pkg, parseFlags);
            pp.collectManifestDigest(pkg);
        } catch (PackageParserException e) {
            res.setError("Failed collect during installPackageLI", e);            return;
        }        /* If the installer passed in a manifest digest, compare it now. */
        if (args.manifestDigest != null) {            if (DEBUG_INSTALL) {                final String parsedManifest = pkg.manifestDigest == null ? "null"
                        : pkg.manifestDigest.toString();
                Slog.d(TAG, "Comparing manifests: " + args.manifestDigest.toString() + " vs. "
                        + parsedManifest);
            }            if (!args.manifestDigest.equals(pkg.manifestDigest)) {
                res.setError(INSTALL_FAILED_PACKAGE_CHANGED, "Manifest digest changed");                return;
            }
        } else if (DEBUG_INSTALL) {            final String parsedManifest = pkg.manifestDigest == null
                    ? "null" : pkg.manifestDigest.toString();
            Slog.d(TAG, "manifestDigest was not present, but parser got: " + parsedManifest);
        }        // Get rid of all references to package scan path via parser.
        pp = null;
        String oldCodePath = null;        boolean systemApp = false;        synchronized (mPackages) {            // Check whether the newly-scanned package wants to define an already-defined perm
            int N = pkg.permissions.size();            for (int i = N-1; i >= 0; i--) {
                PackageParser.Permission perm = pkg.permissions.get(i);
                BasePermission bp = mSettings.mPermissions.get(perm.info.name);                if (bp != null) {                    // If the defining package is signed with our cert, it's okay.  This
                    // also includes the "updating the same package" case, of course.
                    // "updating same package" could also involve key-rotation.
                    final boolean sigsOk;                    if (!bp.sourcePackage.equals(pkg.packageName)
                            || !(bp.packageSetting instanceof PackageSetting)
                            || !bp.packageSetting.keySetData.isUsingUpgradeKeySets()
                            || ((PackageSetting) bp.packageSetting).sharedUser != null) {
                        sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
                                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
                    } else {
                        sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
                    }                    if (!sigsOk) {                        // If the owning package is the system itself, we log but allow
                        // install to proceed; we fail the install on all other permission
                        // redefinitions.
                        if (!bp.sourcePackage.equals("android")) {
                            res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
                                    + pkg.packageName + " attempting to redeclare permission "
                                    + perm.info.name + " already owned by " + bp.sourcePackage);
                            res.origPermission = perm.info.name;
                            res.origPackage = bp.sourcePackage;                            return;
                        } else {
                            Slog.w(TAG, "Package " + pkg.packageName
                                    + " attempting to redeclare system permission "
                                    + perm.info.name + "; ignoring new declaration");
                            pkg.permissions.remove(i);
                        }
                    }
                }
            }            // Check if installing already existing package
            if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                String oldName = mSettings.mRenamedPackages.get(pkgName);                if (pkg.mOriginalPackages != null
                        && pkg.mOriginalPackages.contains(oldName)
                        && mPackages.containsKey(oldName)) {                    // This package is derived from an original package,
                    // and this device has been updating from that original
                    // name.  We must continue using the original name, so
                    // rename the new package here.
                    pkg.setPackageName(oldName);
                    pkgName = pkg.packageName;
                    replace = true;                    if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
                            + oldName + " pkgName=" + pkgName);
                } else if (mPackages.containsKey(pkgName)) {                    // This package, under its official name, already exists
                    // on the device; we should replace it.
                    replace = true;                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
                }
            }
            PackageSetting ps = mSettings.mPackages.get(pkgName);            if (ps != null) {                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
                oldCodePath = mSettings.mPackages.get(pkgName).codePathString;                if (ps.pkg != null && ps.pkg.applicationInfo != null) {
                    systemApp = (ps.pkg.applicationInfo.flags &
                            ApplicationInfo.FLAG_SYSTEM) != 0;
                }
                res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
            }
        }        if (systemApp && onSd) {            // Disable updates to system apps on sdcard
            res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,                    "Cannot install updates to system apps on sdcard");            return;
        }        if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
            res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");            return;
        }        if (replace) {
            replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
                    installerPackageName, res);
        } else {
            installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, res);
        }        synchronized (mPackages) {            final PackageSetting ps = mSettings.mPackages.get(pkgName);            if (ps != null) {
                res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
            }
        }
    }

这个方法先是解析了package包,然后做了大量签名和权限校验的工作,最终会走到

  if (replace) {
            replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
                    installerPackageName, res);
        } else {
            installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, res);
        }

这两个方法分别是覆盖安装和安装新应用对应的具体执行.我们来看看installNewPackageLI()

2.3 installNewPackageLI()

    private void installNewPackageLI(PackageParser.Package pkg,            int parseFlags, int scanFlags, UserHandle user,
            String installerPackageName, PackageInstalledInfo res) {        // Remember this for later, in case we need to rollback this install
        String pkgName = pkg.packageName;        if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);        boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();        synchronized(mPackages) {            if (mSettings.mRenamedPackages.containsKey(pkgName)) {                // A package with the same name is already installed, though
                // it has been renamed to an older name.  The package we
                // are trying to install should be installed as an update to
                // the existing one, but that has not been requested, so bail.
                res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
                        + " without first uninstalling package running as "
                        + mSettings.mRenamedPackages.get(pkgName));                return;
            }            if (mPackages.containsKey(pkgName)) {                // Don't allow installation over an existing package with the same name.
                res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
                        + " without first uninstalling.");                return;
            }
        }        try {
            PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
                    System.currentTimeMillis(), user);

            updateSettingsLI(newPackage, installerPackageName, null, null, res);            // delete the partially installed application. the data directory will have to be
            // restored if it was already existing
            if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {                // remove package from internal structures.  Note that we want deletePackageX to
                // delete the package data and cache directories that it created in
                // scanPackageLocked, unless those directories existed before we even tried to
                // install.
                deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
                        dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
                                res.removedInfo, true);
            }

        } catch (PackageManagerException e) {
            res.setError("Package couldn't be installed in " + pkg.codePath, e);
        }
    }

这个方法核心的步骤有两个:

  • PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,System.currentTimeMillis(), user);

  • updateSettingsLI(newPackage, installerPackageName, null, null, res);

scanPackageLI负责安装,而updateSettingLI则是完成安装后的设置信息更新

2.4 scanPackageLI()

scanPackageLI()方法主要逻辑是由scanPackageDirtyLI()实现的,scanPackageDirtyLI()实在太长了,此处就不列出了,主要说下,这个方法实现了以下操作:

  • 设置系统App的一些参数

  • 校验签名

  • 解析app的provider,校验是否与已有的provider冲突

  • 32/64位abi的一些设置

  • 四大组件的解析,注册

scanPackageDirtyLI()里面的操作确实是太多了,并不止这几点。如需更详细的信息还请查看源码。

另一方面,这个方法里,会调用到performDexOptLI(),其会去执行dexopt操作

原文链接:http://www.apkbus.com/blog-664680-77895.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消