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通信。

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类中包含的数据结构关系如下图所示:

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容