本文首要内容

  • 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源码浅析

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或其它服务的源码时,细细领会,终有一天咱们能大有收成。