Android PackageManagerService详细分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1 主流程图

详细分析

在 systemserver.java 中启动包管理服务

pm=PackageManagerService.main(context,factoryTest != SystemServer.FACTORY_TEST_OFF);

main 函数主要功能是构造 PackageManagerService 实例,然后添加到 ServiceManager 中。

public static final IPackageManager main(Context context, boolean factoryTest) {

PackageManagerService m = new PackageManagerService(context, factoryTest);

ServiceManager.addService(“package”, m);

return m;

}

PackageManagerService(context, factoryTest) 是 包 管 理 服 务 的 主 进 程 。 它 完 成 了对/system/app,/data/app,/system/framework,/data/app-private 下的 apk 文件的解析。详细流程如下:

初始化过程:

判断 ro.build.type 是否等于 eng;

创建系统显示像素实例 mMetrics = new DisplayMetrics();

创建 mSettings 实例 mSettings = new Settings(),Settings 类是 PackageManagerService 的一个

静态子类,它的作用主要是保持动态设置的信息,通过 Settings()构造函数在/data/system 下

创建了三个文件名:packages.xml,packages-backup.xml(这个文件在 mSettings.writeLP()里被删除了),packages.list。

mSettings 增加 android.uid.system,android.uid.phone,android.uid.log 三个共享用户 ID,同时授予其系统权限。

//建立 installer 与 installd 的 socket 联接

Installer installer = new Installer();

installer.ping() && Process.supportsProcesses();

installd 完成以下一些命令

struct cmdinfo cmds[] = {

{ “ping”,             0, do_ping },

{ “install”,          3, do_install },

{ “dexopt”,          3, do_dexopt },

{ “movedex”,          2, do_move_dex },

{ “rmdex”,            1, do_rm_dex },

{ “remove”,           1, do_remove },

{ “rename”,          2, do_rename },

{ “freecache”,       1, do_free_cache },

{ “rmcache”,               1, do_rm_cache },

{ “protect”,               2, do_protect },

{ “getsize”,               3, do_get_size },

{ “rmuserdata”,           1, do_rm_user_data },

{ “movefiles”,         0, do_movefiles },

};

//获取当前缺省的显示像素

WindowManager wm=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

Display d = wm.getDefaultDisplay();

d.getMetrics(mMetrics);

建立一个消息循环,用于处理 apk 安装时的请求消息处理(这些请求来自 adb install/push,包安装器,android market 下载安装 apk 时发送的)

mHandlerThread.start();

mHandler = new PackageHandler(mHandlerThread.getLooper());

这个消息循环处理的消息事件如下:

static final int SEND_PENDING_BROADCAST = 1;

static final int MCS_BOUND = 3;

static final int END_COPY = 4;

static final int INIT_COPY = 5;

static final int MCS_UNBIND = 6;

static final int START_CLEANING_PACKAGE = 7;

static final int FIND_INSTALL_LOC = 8;

static final int POST_INSTALL = 9;

static final int MCS_RECONNECT = 10;

static final int MCS_GIVE_UP = 11;

static final int UPDATED_MEDIA_STATUS = 12;

static final int WRITE_SETTINGS = 13;

//创建/data/data 和/data/app-private 目录

File dataDir = Environment.getDataDirectory();//获得/data 目录

mAppDataDir = new File(dataDir, “data”);

mDrmAppPrivateInstallDir = new File(dataDir, “app-private”);

// Read permissions from /system/etc/permission directory.

//这些文件在 framework/base/data/etc

Void readPermissions()

{

//解析/system/etc/permission/下的*.xml 文件,获取权限信息

//最后解析该目录下的 platform.xml 文件,使该文件里的权限在栈顶出现,以便预先处理

//这个文件记录了系统级应用的 uid 及其拥有的权限

File permFile = new File(Environment.getRootDirectory(),“etc/permissions/platform.xml”);

readPermissionsFromXml(permFile);

//该函数的功能如下:

通过 xml 解 析 器 解 释 *.xml 文 件 , 提 取 标 签 名 “ group” , “permission” , “assign-permission”,“library”,"feature"并进行相应处理。在 platform.xml 中对底层的系统用户和组ID(group ids)同上层的由平台管理的 permission 名字之间进行了关系映射,使它们关联起来。当一个应用被授予某个权限后,同时属于已知的组 ID,这个应用就可以进行允许这个组的文件系统操作,如(read,write,execute )。这里记录了一些系统级的应用的 uid 对应的permission

//每个标签的含义:

group:安装到系统中的所有 APK 都具备的组 ID。

permission:可以指定一个权限与几个组 ID 对应。当一个 APK 被授予这个权限时,它也同时属于这几个组。

assign-permission:把一个权限赋予一个 UID,当进程使用这个 UID 运行时,就具备了这个权限。

library:为系统添加一些扩展库用的。对应的.jar 文件放在/system/framework/目录下。比如Google Map 相关的库。

feature : 每 添 加 一 个 硬 件 , 都 要 增 加 对 应 的 feature 。 将 以 上 解 析 的 结 果 对 应 放 入mGlobalGids,mSettings.mPermissions,mSystemPermissions,mSharedLibraries,mAvailableFeatures 等几个 list 中供系统查询和权限配置使用。

}

//readLP()会判断/data/system/packages.xml 文件是否存在,如果不存在则返回 false,如果存在则进行解析,在系统第一次启动时 packages.xml 文件是不存在的,由 writeLP()创建该文件,并将该文件写到 nand 上,下次开机会直接读取并解析这个文件。解析的过程即是按照 xml定义的标签,将对应的属性和值添加到全局列表中。packages.xml 文件中记录了系统安装的所有 apk 的属性权限的信息,当系统中的 apk 安装,删除或升级时,改文件就会被更新。

标 签 定 义 了 目 前 系 统 中 定 义 的 所 有 权 限 。 主 要 分 为 两 类 : 系 统 定 义 的(package 属性为 Android)和 APK 定义的(package 属性为 APK 的包名)

sharedUserId/userId:Android 系统启动一个普通的 APK 时,会为这个 APK 分配一个独立的UID , 这 就 是 userId 。 如 果 APK 要 和 系 统 中 其 它 APK 使 用 相 同 的 UID 的 话 , 那 就 是sharedUserId。perms:APK 的 AndroidManifest.xml 文件中,每使用一个标签,标签中就会增加一项。

代 表 一 个 共 享 UID , 通 常 , 共 同 实 现 一 系 列 相 似 功 能 的 APK 共 享 一 个UID。标签中的 权限代表了这个共享 UID 的权限,所有使用的同一个共享 UID 的APK 运行在同一进程中,这个进程的 UID 就是这个共享 UID,这些 APK 都具有这个共享UID 的权限。

name:共享 UID 的名字,在 APK 的 Android:sharedUserId 属性中使用。

userId:使用这个共享 UID 的所有 APK 运行时所在的进程的 UID。

mRestoredSettings = mSettings.readLP();

// 判 断 boot class path 里 的 文 件 ( jar 文 件 ) 是 否 需 要 dexopt( 判 断 标 准 是 检 查DvmGlobals.bootClassPath 是否已经包含这个文件),如果需要先将文件添加到 libFiles 里,同时 进 行 dexopt : 由 Installer 通 过 socket 将 命 令 传 给 installd 的 run_dexopt, 最 终 调 用 的是/system/bin/dexopt 对 jar 包进行处理。如果已经进行了 dexopt 动作,则将/data/dalvik-cache下的以 data 开头的文件删除,后续重新建立。如果外部库 mSharedLibraries 列表存在,也要检 查 列 表 中 的 元 素 是 否 需 要 dexopt, 如 果 需 要 则 和 boot class path 进 行 相 同 处 理 。 对于/system/framework 下 apk 和 jar 文件检查是否需要 dexopt.

String bootClassPath = System.getProperty(“java.boot.class.path”);

if (bootClassPath != null) {

String[] paths = splitString(bootClassPath, ‘:’);

for (int i=0; i<paths.length; i++) {

try {

if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {//是否需要 dexopt

libFiles.add(lib);//添加到 libFiles 列表

mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true);//进行 dexopt

}

}

}

}

//对 framework-res.apk 不进行 dexopt 直接添加到 libFiles

libFiles.add(mFrameworkDir.getPath() + “/framework-res.apk”);

// 启 动 AppDirObserver 线 程 监 测 /system/framework , /system/app , /data/app , /data/app-private 几个目录的事件,主要监听的是 add 和 remove 事件。对于目录监听底层通过 inotify机制实现,inotify 是在 2.6.13 中引入的新功能,它为用户态监视文件系统的变化提供了强大的支持;inotify 是一种文件系统的变化通知机制,如文件增加 ,删除等事件可以立刻让用户态得知,当监测到事件发生时该线程做何处理呢?

MframeworkInstallObserver =

new AppDirObserver(mFrameworkDir.getPath(),OBSERVER_EVENTS, true);

mFrameworkInstallObserver.startWatching();

//调用 scanDirLI 解析以上目录下的 apk 文件,该函数是包管理服务的重要函数,在后面有详细的分析

private void scanDirLI(File dir, int flags, int scanMode) {

String[] files = dir.list();

for (i=0; i<files.length; i++)

{

File file = new File(dir, files[i]);

PackageParser.Package pkg =

scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode);

if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&

mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {

// Delete the apk

file.delete();

}

}

}

//对于不存在的 system apk 调用以下函数删除掉

Iterator psit = mSettings.mPackages.values().iterator();

PackageSetting ps = psit.next();

if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0

&&!mPackages.containsKey(ps.name)

&& !mSettings.mDisabledSysPackages.containsKey(ps.name))

{

psit.remove();

mInstaller.remove(ps.name);

}

//在解析完以上目录下的 apk 后,更新应用的权限

updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions);

//writeLP 会 生 成 packages.xml 和 packages.list 文 件 ,packages.list 的 数 据 格 式 是 :pkgName,userId,debugFlag,dataPath(包的数据路径),packages.xml 保存了每个已经安

装 apk 的详尽的信息

mSettings.writeLP();

以上是包管理服务在系统启动时做的全部工作。

//

//

//

///

下面解析其中一个比较重要的函数 scanDirLI:

private void scanDirLI(File dir, int flags, int scanMode) {

String[] files = dir.list();

for (i=0; i<files.length; i++)

{

File file = new File(dir, files[i]);

PackageParser.Package pkg =

scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode);

if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&

mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {

// Delete the apk

file.delete();

}

}

}

这 个 函 数 结 构 比 较 简 单 , 对 监 测 的 几 个 目 录 下 的 每 一 个 apk 文 件 继 续 通 过scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode) 进 行 解 析 , 对 不存在且安装失败已经无效的非系统 apk 直接删除。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2 scanDirLI 流程分析

scanPackageLI 是 一 个 重 定 义 函 数 , 它 的 作 用 是 : 用 PackageParser 的 两 个 重 定 义 函 数parsePackage 解析 package 的 asset,res,建立 asset 资源文件路径;解析 AndroidManifest.xml文件,建立 PackageParser.Package 结构,这个结构保存了从 AndroidManifest.xml 解析出的package 的信息。对 package 进行数字签名及完整性校验,

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanMode)

{

//实例化一个 PackageParser 对象

PackageParser pp = new PackageParser(scanPath);

// parsePackage 也是一个重定义函数,它主要做了三件事,一个是解析 apk 中的 asset下 的 文 件 , 一 个 是 解 析 res 下 的 文 件 。 然 后 通 过 重 定 义 函 数     parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError) 对 apk 的AndroidManifest.xml 进行解析,将每个标签对应的信息添加到每个包的相关列表中,如将标签 application 下 的 activity 通 过 pkg.activities.add(a) 添 加 到 package 的 activities 列 表 , 将service 添加到 owner.services.add(s)。

PackageParser.Package pkg = pp.parsePackage(scanFile, scanPath, mMetrics, parseFlags);

//检查这个 package 是否已经存在,以及是否重命名过,以及该系统 package 是否可以被更新,如果可以被更新,则对比系统分区和 data 分区的 package 版本,如果系统分区的

package 高于 data 分区的版本,则保留系统分区的 package

//对 package 进行签名认证,如果是 system img 里的,只是通过 AndroidManifest.xml 获得签名,对签名校验,不会对全部文件进行有效性检查;否则,就要结合 META-INF/进行

签名和有效性校验collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);

//调用重定义函数继续进行解析,将每个 apk 解析出的标签信息添加到全局的列表里。如将 每 个 apk 的 recervers 列 表 里 的 元 素 pkg.receivers.get(i), 通 过                  mReceivers.addActivity(a,“receiver”)添加到全局列表 mReceivers 里

return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);

}

<补充知识>

res/raw 和 assets 区别

*res/raw 和 assets 的相同点:

  1. 两者目录下的文件在打包后会原封不动的保存在 apk 包中,不会被编译成二进制。

*res/raw 和 assets 的不同点:

  1. res/raw 中 的 文 件 会 被 映 射 到 R.java 文 件 中 , 访 问 的 时 候 直 接 使 用 资 源 ID 即R.id.filename ; assets 文 件 夹 下 的 文 件 不 会 被 映 射 到 R.java 中 , 访 问 的  时 候 需 要AssetManager 类。

2.res/raw 不可以有目录结构,而 assets 则可以有目录结构,也就是 assets 目录下可以再建立文件夹

*读取文件资源:

1.读取 res/raw 下的文件资源,通过以下方式获取输入流来进行写操作InputStream is = getResources().openRawResource(R.id.filename);

2.读取 assets 下的文件资源,通过以下方式获取输入流来进行写操作

AssetManager am = null;

am = getAssets();

InputStream is = am.open(“filename”);

(用于内置文件但不知道文件名称,需要筛选出想要的文件然后拷贝到目标目录中,推荐内置在 assets 文件夹中)

1.res/raw 目录:

通过反射的方式得到 R.java 里面 raw 内部类里面所有的资源 ID 的名称,然后通过名称获取资源 ID 的值来读取我们想要的文件。

2.assets 目录:

getAssets().list(“”);来获取 assets 目录下所有文件夹和文件的名称,再通过这些名称读取我们想要的文件。另,在处理 asset 时,android 限制最大的数据是 1M,超出后会报错误。

</>

public Package parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int

flags)

{

//解析/asset 下的文件

assmgr = new AssetManager();

int cookie = assmgr.addAssetPath(mArchiveSourcePath);

parser = assmgr.openXmlResourceParser(cookie, “AndroidManifest.xml”);

//解析/Res 下的文件,通过 parsePackage 函数解析 AndroidManifest.xml 文件

Resources res = new Resources(assmgr, metrics, null);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-6PqsIlcC-1712818305164)]

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-WJ6QPFrr-1712818305165)]

  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值