PackageManagerService源码浅析

本文主要内容

  • pms简要介绍
  • pms构造函数

上一篇文章中阐述了apk的安装过程,本文对pms的源码简要分析下

pms简要介绍

pms即PackageManagerService,它主要负责应用安装、卸载、更新等,在应用开机阶段还需要负责扫描已安装的应用,记录应用相关信息,比如应用中的activity、receiver等等,应用的odex优化,甚至为开发者提供接口,以便开发者查询应用名称,主icon等等各种信息

pms运行在system进程当中,在SystemServer类中启动。

mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

用户获取的PackageManager,经过android的封装,得到的是binder的proxy。而PackageManager是一个抽象类,它的子类是ApplicationPackageManager,而ApplicationPackageManager获得了pms binder的proxy对象,所以可以与pms进行IPC通信。

《PackageManagerService源码浅析》 image.png

pms构造函数

在pms的main方法中,其实就是调用pms的构造方法而已

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

pms在构造方法中,主要扫描系统中几个特定文件夹下的apk,从而建立合适的数据结构来管理Package信息,四大组件信息,权限信息等(PKMS主要解析apk文件的AndroidManifest.xml文件

先看第1阶段,处理 packages.xml 等文件。

mSettings = new Settings(context);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
            ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);

mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                mSdkVersion, mOnlyCore);

Settings类很有意思,此处的Settings类与数据库读取的那个Settings无关,它主要用于保存各应用相关的信息。

Settings(Context context, File dataDir) {
    mSystemDir = new File(dataDir, "system");
    mSystemDir.mkdirs();
    FileUtils.setPermissions(mSystemDir.toString(),
            FileUtils.S_IRWXU|FileUtils.S_IRWXG
            |FileUtils.S_IROTH|FileUtils.S_IXOTH,
            -1, -1);
    mSettingsFilename = new File(mSystemDir, "packages.xml");
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
    mPackageListFilename = new File(mSystemDir, "packages.list");
    FileUtils.setPermissions(mPackageListFilename, 0660, SYSTEM_UID, PACKAGE_INFO_GID);

    // Deprecated: Needed for migration
    mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
    mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}

从它的构造方法中可以看出,它与packages.xml等文件密切相关。实质上,每个应用都会有一个uid,Settings类将保存各应用的uid及其它参数,在开机阶段也会去读取packages.xml等文件

回到pms的构造方法,查看addSharedUserLPw方法:

SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
    // 。。。。
    s = new SharedUserSetting(name, pkgFlags);
    s.userId = uid;
    if (addUserIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);
        return s;
    }
    return null;
}

private boolean addUserIdLPw(int uid, Object obj, Object name) {
    //根据应用uid,如果是非系统应用,则保存在mUserIds中,如果是系统应用则保存在mOtherUserIds中
    if (uid >= Process.FIRST_APPLICATION_UID) {
        int N = mUserIds.size();
        final int index = uid - Process.FIRST_APPLICATION_UID;
        while (index >= N) {
            mUserIds.add(null);
            N++;
        }
        mUserIds.set(index, obj);
    } else {
        mOtherUserIds.put(uid, obj);
    }
    return true;
}

在pms的构造方法中,后续还会调用 mSettings.readLPw 方法,在这个方法中将读取 packages.xml 等文件

boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
        boolean onlyCore) {
    FileInputStream str = null;
    if (mBackupSettingsFilename.exists()) {
            str = new FileInputStream(mBackupSettingsFilename);
            if (mSettingsFilename.exists()) {
                //如果备份的文件存在,则读取备份文件并删除正常的文件
                mSettingsFilename.delete();
            }
    }
    try {
        if (str == null) {
            if (!mSettingsFilename.exists()) {
                //如果既没有备份文件也没有正常文件,则异常
                return false;
            }
            str = new FileInputStream(mSettingsFilename);
        }
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(str, null);

        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG
                && type != XmlPullParser.END_DOCUMENT) {
            ;
        }

        int outerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (tagName.equals("package")) {
                readPackageLPw(parser);
        }
        //。。。
    }

如果大家去看 /data/sysem/packages.xml 文件时,会发现有时候还有各种备份文件,文件名中包含backup等,这其实是在写文件时加的保险措施,在写文件之前,先将文件备份,如果写入失败,不删除备份文件,在下次开机的时候直接读取备份文件即可。如果写入成功,则删除备份文件。这种文件操作思路,值得借鉴,一般的写文件都用这种方式,包括硬盘缓存等

Settings类中包含的数据结构关系如下图所示:

《PackageManagerService源码浅析》

完成这一步骤后,开始应用优化

byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
       dexCodeInstructionSet, false);
if (dexoptRequired != DexFile.UP_TO_DATE) {
        alreadyDexOpted.add(lib);
        // The list of "shared libraries" we have at this point is
        if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
             mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
        } else {
              mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
        }

判断当前应用是否需要进行odex优化,如果需要则调用mInstaller完成。mInstaller通过socket调用 installerd 进程完成优化,关于installerd 可以见本人上一篇博文

这一步骤完成后,则是最重要的工作了,扫描应用安装的文件夹:

File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

我们来简要看看scanDirLI方法

private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    final File[] files = dir.listFiles();
    for (File file : files) {
        final boolean isPackage = (isApkFile(file) || file.isDirectory())
                && !PackageInstallerService.isStageName(file.getName());
        try {
            scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                    scanFlags, currentTime, null);
        } catch (PackageManagerException e) {
        }
    }
}

可以看到,scanDirLI方法遍历对应的文件夹,执行scanPackageLI方法。

在scanPackageLI中,系统收集比较应用签名等,完全性校验完成后,再度扫描应用文件。

final PackageParser.Package pkg;
    try {
        //解析androidMenifest文件
        pkg = pp.parsePackage(scanFile, parseFlags);
    } catch (PackageParserException e) {
        throw PackageManagerException.from(e);
    }

collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);

if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)


PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
            | SCAN_UPDATE_SIGNATURE, currentTime, user);

在parsePackage方法中,将解析AndroidMenifest.xml文件,解析出包名、所需要的权限,申明的四大组件等等,将结果封装在Package对象中。

关于四大组件的扫描,请参见 PackageParser.parseBaseApplication 方法,此方法解析xml文件并保存相关信息,太长了

经过上述几个步骤,pms的构造方法核心内容就介绍完毕了,阅读源码真的不要被过长的代码吓住了,要耐下心来,同时要非常注意源码中的英文注释,结合注释一起看,事半功倍。

虽然pms还有非常多的内容,但限于篇幅,本文也只讲了这么多。对于源码,相信每个程序员都是又爱又恨,关键是需要体会源码的设计精神,为什么要这么做,这些东西都是需要时间的打磨,当我们清楚pms或其它服务的源码时,细细体会,终有一天我们能大有收获。

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