浅谈Android Apk安装到PackageManagerService源码解析

app 安装的流程:

  1. 网络下载应用安装――通过应用市场完成,没有安装界面

  2. ADB工具安装――没有安装界面。

  3. 第三方应用安装――通过SD卡里的APK文件安装,有安装界面,由 packageinstaller.apk应用处理安装及卸载过程的界面。

安装其实就是把apk文件copy到了对应的目录

  1. data/app —————安装时把 apk文件复制到此目录,—- 可以将文件取出并安装,和我们本身的apk 是一样的。

  2. data/data/包名————— 开辟存放应用程序的文件数据的文件夹
    包括我们应用的 so库,缓存文件 等等。

  3. 将apk中的dex文件安装到data/dalvik-cache目录下(dex文件是dealvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)

Android手机为啥启动那么慢呢?

其实就是会把安装过的app在一次的安装一变

PackageManagerService源码:

PMS会把整个目录扫描一遍,包括系统目录(data/system、data/app等)

首先我们看下packageManagerService的 main方法中的代码:
其中这个main是被SystemServer调用的

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    ServiceManager.addService("package", m);
    return m;
}

这段代码呢,我们可以看到通过new 的方式创建了一个对象,并添加到了 ServiceManager 中,serviceManager 内部是一个 HashMap的集合,存储了很多相关的 binder 服务,缓存起来,我们在使用的时候, 会通过 getService(key) 的方式去 map 中获取。

在PackageManagerService构造函数中,是用同步的方式初始化了解析所需要的文件目录:

File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");

我们可以看到在 构造函数中调用了scanDirLI 方法, 我们继续跟进。

private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    final File[] files = dir.listFiles();
    
    ...

    for (File file : files) {
        ... // 进行校验文件 格式
        try {
            scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                    scanFlags, currentTime, null);
        } catch (PackageManagerException e) {
            ... // 删除了无效的文件目录
        }
    }
}

这里遍历的文件目录,就是我们上面的初始化的File, 比如我们 app的目录 就是data/app下。 进行了遍历,下面我们进scanPackageLI 看看它都做了什么?

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
    long currentTime, UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);

if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
    parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
}

final PackageParser.Package pkg;
try {
    pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
    throw PackageManagerException.from(e);
}

PackageSetting ps = null;
PackageSetting updatedPkg;
// reader
synchronized (mPackages) {
    // Look to see if we already know about this package.
    String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
    if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
        // This package has been renamed to its original name.  Let's
        // use that.
        ps = mSettings.peekPackageLPr(oldName);
    }
    // If there was no original package, see one for the real package name.
    if (ps == null) {
        ps = mSettings.peekPackageLPr(pkg.packageName);
    }
    // Check to see if this package could be hiding/updating a system
    // package.  Must look for it either under the original or real
    // package name depending on our state.
    updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
    if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
boolean updatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
    // If new package is not located in "/system/priv-app" (e.g. due to an OTA),
    // it needs to drop FLAG_PRIVILEGED.
    if (locationIsPrivileged(scanFile)) {
        updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
    } else {
        updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
    }

    if (ps != null && !ps.codePath.equals(scanFile)) {
        // The path has changed from what was last scanned...  check the
        // version of the new path against what we have stored to determine
        // what to do.
        if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
        if (pkg.mVersionCode <= ps.versionCode) {
            // The system package has been updated and the code path does not match
            // Ignore entry. Skip it.
            if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
                    + " ignored: updated version " + ps.versionCode
                    + " better than this " + pkg.mVersionCode);
            if (!updatedPkg.codePath.equals(scanFile)) {
                Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg : "
                        + ps.name + " changing from " + updatedPkg.codePathString
                        + " to " + scanFile);
                updatedPkg.codePath = scanFile;
                updatedPkg.codePathString = scanFile.toString();
                updatedPkg.resourcePath = scanFile;
                updatedPkg.resourcePathString = scanFile.toString();
            }
            updatedPkg.pkg = pkg;
            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
                        "Package " + ps.name + " at " + scanFile
                                + " ignored: updated version " + ps.versionCode
                                + " better than this " + pkg.mVersionCode);
            } else {
                // The current app on the system partition is better than
                // what we have updated to on the data partition; switch
                // back to the system partition version.
                // At this point, its safely assumed that package installation for
                // apps in system partition will go through. If not there won't be a working
                // version of the app
                // writer
                synchronized (mPackages) {
                    // Just remove the loaded entries from package lists.
                    mPackages.remove(ps.name);
                }

                logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
                        + " reverting from " + ps.codePathString
                        + ": new version " + pkg.mVersionCode
                        + " better than installed " + ps.versionCode);

                InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                        ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
                synchronized (mInstallLock) {
                    args.cleanUpResourcesLI();
                }
                synchronized (mPackages) {
                    mSettings.enableSystemPackageLPw(ps.name);
                }
                updatedPkgBetter = true;
            }
        }
    }

    if (updatedPkg != null) {
        // An updated system app will not have the PARSE_IS_SYSTEM flag set
        // initially
        parseFlags |= PackageParser.PARSE_IS_SYSTEM;

        // An updated privileged app will not have the PARSE_IS_PRIVILEGED
        // flag set initially
        if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
            parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
        }
    }

    // Verify certificates against what was last scanned
    collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);

    /*
     * A new system app appeared, but we already had a non-system one of the
     * same name installed earlier.
     */
    boolean shouldHideSystemApp = false;
    if (updatedPkg == null && ps != null
            && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
        /*
         * Check to make sure the signatures match first. If they don't,
         * wipe the installed application and its data.
         */
        if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
                != PackageManager.SIGNATURE_MATCH) {
            logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
                    + " signatures don't match existing userdata copy; removing");
            deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
            ps = null;
        } else {
            /*
             * If the newly-added system app is an older version than the
             * already installed version, hide it. It will be scanned later
             * and re-added like an update.
             */
            if (pkg.mVersionCode <= ps.versionCode) {
                shouldHideSystemApp = true;
                logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
                        + " but new version " + pkg.mVersionCode + " better than installed "
                        + ps.versionCode + "; hiding system");
            } else {
                /*
                 * The newly found system app is a newer version that the
                 * one previously installed. Simply remove the
                 * already-installed application and replace it with our own
                 * while keeping the application data.
                 */
                logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
                        + " reverting from " + ps.codePathString + ": new version "
                        + pkg.mVersionCode + " better than installed " + ps.versionCode);
                InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                        ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
                synchronized (mInstallLock) {
                    args.cleanUpResourcesLI();
                }
            }
        }
    }

    // The apk is forward locked (not public) if its code and resources
    // are kept in different files. (except for app in either system or
    // vendor path).
    // TODO grab this value from PackageSettings
    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
        if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
            parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
        }
    }

    // TODO: extend to support forward-locked splits
    String resourcePath = null;
    String baseResourcePath = null;
    if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {
        if (ps != null && ps.resourcePathString != null) {
            resourcePath = ps.resourcePathString;
            baseResourcePath = ps.resourcePathString;
        } else {
            // Should not happen at all. Just log an error.
            Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
        }
    } else {
        resourcePath = pkg.codePath;
        baseResourcePath = pkg.baseCodePath;
    }

    // Set application objects path explicitly.
    pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
    pkg.applicationInfo.setCodePath(pkg.codePath);
    pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
    pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
    pkg.applicationInfo.setResourcePath(resourcePath);
    pkg.applicationInfo.setBaseResourcePath(baseResourcePath);
    pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);

    // Note that we invoke the following method only if we are about to unpack an application
    PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
            | SCAN_UPDATE_SIGNATURE, currentTime, user);

    /*
     * If the system app should be overridden by a previously installed
     * data, hide the system app now and let the /data/app scan pick it up
     * again.
     */
    if (shouldHideSystemApp) {
        synchronized (mPackages) {
            /*
             * We have to grant systems permissions before we hide, because
             * grantPermissions will assume the package update is trying to
             * expand its permissions.
             */
            grantPermissionsLPw(pkg, true, pkg.packageName);
            mSettings.disableSystemPackageLPw(pkg.packageName);
        }
    }

    return scannedPkg;
}

在里面做了两件比较重要的事情:

  1. 创建了PackageParser 对象

    PackageParser pp = new PackageParser();

  2. 调用了 parsePackage方法 并返回了 PackageParser.Package 对象。

    pkg = pp.parsePackage(scanFile, parseFlags);

我们要在这里稍微停一下, 说说这个PackageParser.Package. 为啥要停呢? 它有啥特别之处么? 让我们看一下它的类的信息就清楚了:

public final static class Package {

    public String packageName;
    /** Names of any split APKs, ordered by parsed splitName */
    public String[] splitNames;

    // TODO: work towards making these paths invariant

    /**
     * Path where this package was found on disk. For monolithic packages
     * this is path to single base APK file; for cluster packages this is
     * path to the cluster directory.
     */
    public String codePath;

    /** Path of base APK */
    public String baseCodePath;
    /** Paths of any split APKs, ordered by parsed splitName */
    public String[] splitCodePaths;

    /** Flags of any split APKs; ordered by parsed splitName */
    public int[] splitFlags;

    public boolean baseHardwareAccelerated;

    // For now we only support one application per package.
    public final ApplicationInfo applicationInfo = new ApplicationInfo();

    public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
    public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
    public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
    public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
    public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
    public final ArrayList<Service> services = new ArrayList<Service>(0);
    public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);

    public final ArrayList<String> requestedPermissions = new ArrayList<String>();
    public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();

    ...     // 部分代码。

}

Ok, 你也看到了, 里面定义了 我们的packageNameapk 路径 baseCodePath 以及 各种 Arraylist来保存我们的ActivityService、权限 等等。

这里我们会发现receivers 的集合类型也是Activity,这是因为广播在AndroidManifest.xml 文件的结构跟Activity是一样的

注意:这里的Activity不是四大组件的Activity,而是PackageParser.Package 的内部类,相当于JavaBean,

好的,我们继续往下面走。 下面我们的 主角从 PackageManagerService 切换到了 我们的PackageParser.

public Package parsePackage(File packageFile, int flags) throws PackageParserException {
    if (packageFile.isDirectory()) {
        return parseClusterPackage(packageFile, flags);
    } else {
        return parseMonolithicPackage(packageFile, flags);
    }
}

我们看到parsePakcage 转调用了parseMonolithicPackage方法,让我们继续。

@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
    if (mOnlyCoreApps) {
        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
        if (!lite.coreApp) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                    "Not a coreApp: " + apkFile);
        }
    }

    final AssetManager assets = new AssetManager();
    try {
        final Package pkg = parseBaseApk(apkFile, assets, flags);
        pkg.codePath = apkFile.getAbsolutePath();
        return pkg;
    } finally {
        IoUtils.closeQuietly(assets);
    }
}

这里的AssetManager现在还不能加载apk里的资源,如果想可以加载,可定会调用AssetManageraddAssetPath方法,才可以有效,我们这里猜测一下,下一步肯定是调用了addAssetPath方法。

不多说,直接进入我们的 parseBaseApk 方法。 这里有两点用说:

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
        throws PackageParserException {
    final String apkPath = apkFile.getAbsolutePath();

    mParseError = PackageManager.INSTALL_SUCCEEDED;
    mArchiveSourcePath = apkFile.getAbsolutePath();

    if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);

    final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);

    Resources res = null;
    XmlResourceParser parser = null;
    try {
        res = new Resources(assets, mMetrics, null);
        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                Build.VERSION.RESOURCES_SDK_INT);
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
//ANDROID_MANIFEST_FILENAME="AndroidManifest.xml"

        final String[] outError = new String[1];
        final Package pkg = parseBaseApk(res, parser, flags, outError);
        if (pkg == null) {
            throw new PackageParserException(mParseError,
                    apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
        }

        pkg.baseCodePath = apkPath;
        pkg.mSignatures = null;

        return pkg;

    } catch (PackageParserException e) {
        throw e;
    } catch (Exception e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    } finally {
        IoUtils.closeQuietly(parser);
    }
}
  1. parseBaseApk 方法里面调用了loadApkIntoAssetManager方法
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
        throws PackageParserException {
    if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                "Invalid package file: " + apkPath);
    }

    // The AssetManager guarantees uniqueness for asset paths, so if this asset path
    // already exists in the AssetManager, addAssetPath will only return the cookie
    // assigned to it.
    int cookie = assets.addAssetPath(apkPath);
    if (cookie == 0) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                "Failed adding asset path: " + apkPath);
    }
    return cookie;
}

这个方法里面就调用了AssetManageraddAssetPath方法,把apk路径传到了addAssetPath方法中

  1. parseBaseApk 方法调用了重载方法 解析AndroidManifest.xml 文件
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {}

这个方法有下面一段代码

String tagName = parser.getName();
if (tagName.equals("application")) {
    ... // 省略部分代码
    if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
        return null;
    }
} 

这里就是就是对应AndroidManifest.xml 文件的application标签

直接来看 parseBaseApplication 中的操作:

  1. 解析了application 节点中的相关信息 如 icon、theme等。

  2. 解析了application 节点下各个子节点的信息。

String tagName = parser.getName();
if (tagName.equals("activity")) {
    Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
            owner.baseHardwareAccelerated);
    if (a == null) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
        return false;
    }

    owner.activities.add(a);

} else if (tagName.equals("receiver")) {
    Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
    if (a == null) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
        return false;
    }

    owner.receivers.add(a);

} else if (tagName.equals("service")) {
    Service s = parseService(owner, res, parser, attrs, flags, outError);
    if (s == null) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
        return false;
    }

    owner.services.add(s);

} else if (tagName.equals("provider")) {
    Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
    if (p == null) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
        return false;
    }

    owner.providers.add(p);

}

我们只看其中的一个节点:

if (tagName.equals("activity")) {
    Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
            owner.baseHardwareAccelerated);
    if (a == null) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
        return false;
    }

    owner.activities.add(a);

}

parseActivity 中解析了 <activity> 节点下的相关信息,比如 exported、tag、theme、intent-filter 等,封装成了一个类即Activity。 然后执行了

owner.activities.add(a);

这里的封装成了一个类即Activity是PackageParser.Package 的内部类

继续看parseActivity方法,

  1. 这个方法里面有个
mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>";

就是判断是广播还是Activity

  1. 这个方法里面还有一段代码
if (parser.getName().equals("intent-filter")) {
    ActivityIntentInfo intent = new ActivityIntentInfo(a);
    if (!parseIntent(res, parser, attrs, true, intent, outError)) {
        return null;
    }
    if (intent.countActions() == 0) {
        Slog.w(TAG, "No actions in intent filter at "
                + mArchiveSourcePath + " "
                + parser.getPositionDescription());
    } else {
        a.intents.add(intent);
    }
} 

就是解析intent-filter标签,然后调用parseIntent方法解析intent-filter标签的标签

因为一个”<receiver>” 或”<activity>”有可能有多个intent-filter,所以使用Activity里面的ArrayList<II> intents集合添加每个intent

总结

从整体的角度看,我们所做的这一系列操作,其实只是在做一件事,创建一个PackageParser.Package对象,然后填充它,然后 return 回去。

    原文作者:itzuo
    原文地址: https://www.jianshu.com/p/16bb78a69b70
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞