在Android中,有几个比较重要的Service。
ActivityManagerService-------主要负责管理所有的Activity的逻辑
WindowManagerService-------主要负责Android中窗口关的逻辑
PackageManagerService-------主要是用来处理apk的安装,卸载和应用程序信息的获取的
今天我们主要研究一下PackageManagerService,因为这个Service我们在平时开发过程中会经常的遇到,所以了解了它的启动流程,对于我们App的开发会有很大的帮助。说到这里估计有些同学就会说,我们平时开发很少用到PackageManagerService啊?确实我们很少直接使用PackageManagerService这个类,但是我们会经常使用PackageManager这个类吧,比如我们要拿到当前应用版本时,通常会使用如下代码:
PackageManager packageManager = getPackageManager(); // getPackageName()是你当前类的包名,0代表是获取版本信息 PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(),0);
PackageManager是什么?它仅仅是一个接口而已,它的实现类是ApplicationPackageManager,但是当你去研究ApplicationPackageManager的源码的时候,你会发现,它的功能其实都是通过一个mPM的变量完成的,它的类型是IPackageManager类型,如果知道Android中Binder机制(关于Binder机制可以参考我的另外一篇文章:http://blog.csdn.net/yuanzeyao/article/details/12954641)的同学相信已经知道这个mPM是什么东西了,它就是PackageManagerService在客户端的一个代理,通过这个代理客户端可以调用到PackageManagerService中的一些方法,如获取某一个应用的版本号,其实版本号这些信息最终都是保存在PackageManagerService中的,我们只有通过mPM这个代理才能拿到这些信息。下面我给出一个类图,大致的描述一下PackageManager,ApplicationpackageManager和PackageManagerService的关系。
通过上面拿到版本号的例子,我们知道其实手机中所有App的信息都是保存在了PackageManagerService中的,我们今天研究PackageManagerService的目的其实就是熟悉PackageManagerService在启动的过程中到底做了什么,是如何保存这些信息的。PackageManagerService是通过SystemService启动的,主要是通过调用PackageManagerService的main函数开始,在main函数中,其实就是创建了一个PackageManagerService的实例并且在ServiceManager中注册,进入PackageManagerService的构造函数,你会发现这里做了很多重量级的操作,这个也是Android系统启动比较慢的一个主要原因。在开始学习之前我需要提醒大家,这里涉及到的数据结构非常多,我们没有必要知道每一个数据结构有什么,因为那样可能导致我们越陷越深,最终走不出来,我们可以先从宏观分析它的启动过程,然后再去分析它涉及的数据结构,所以为了让大家有一个比较清晰的轮廓,我先给出两个时序图,然后我们可以跟着这些时序图走,我们就永远不会迷路了。之所以给两个时序图,是因为我觉得PackageManagerServiec的启动过程大致可以分为两部分:
建议:看文章时,最好手边有一份Android源码,因为这里涉及到的代码非常多,我不方便将代码都贴出来
1、扫描xml文件并解析成对应的数据结构
2、扫描apk文件并解析成对应的数据结构
好了,我们先看第一阶段的时序图
图 1-1
根据图1-1,发现这里引入了一个数据结构Settings,这个类主要功能就是存储第一阶段扫描xml后解析结果的,具体的结构我们后面会分析的,创建了Setttings实例之后,调用addSharedUserLPw方法,当你查看该函数的实现时,你会发现这里有引入了一个SharedUserSetting类型的结构,这个我们暂且不用去分析,接下来就调用readPermissions去扫描/system/etc/permissions/中的所有的xml文件,解析结果都存储到了上面创建的Settings类型变量了,最后调用readLPw函数读取/data/system/packages.xml文件,第一阶段就这么简单,就是扫描xml并解析。接下来我们详细分析一下Settings这个数据结构吧,我先给出这个类的类图
这里我列出了Settings这个类中比较重要的字段:
mSettingFilename:这个字段就是/data/system/packages.xm文件
mBackupSettingsFilename:这个字段就是/data/system/packages_backup.xml文件,这个文件不一定存在,如果存在,那么说明上次跟新packages.xml文件出错了。
mPackageListFilename:这个字段是/data/system/packages.list
mPackages:这个字段是一个HashMap,key 是包名,值是一个新的数据结构PackageSetting,主要包含了一个app的基本信息,如安装位置,lib位置等等
mSharedUsers:这个字段也是一个HashMap,key是类似"android.ui.system"这样的字段,在Android中每一个应用都有一个uid,两个有相同uid的应用可以运行在同一个进程中的,所以为了让两个应用运行在用一个进程中,往往会在Androidmanifest.xml文件中设置shareUserId这个属性,这个属性就是一个字符串,但是我们知道在Linux系统中一个uid是一个整型,所以为了将字符串和整型对应起来,就有了SharedUserSetting类型,刚才说key是shareUserId这个属性的值,那么值就是SharedUserSetting类型了,ShareUserdSetting中除了name(其实就是key),uid(对应Linux系统的uid),还有一个列表字段,记录了当前系统中有相同shareUserId的应用。
mPermissions:这个字段主要保存的是/system/etc/permissions/platform.xml中的的permission标签的内容,因为Android系统是基于Linux系统的,所以也有用户组的概念,在platform.xml中定义了一些权限,并且指定了哪些用户组具有这些权限,一旦一个应用属于某一个用户组,那么它就拥有了这个用户组的所有权限
mPermissionTrees:这个字段对应packages.xml文件中的permission-trees标签
Settings的结构就简单的介绍到这里吧,下面开始第二个阶段:apk文件的扫描并解析。
在Android系统中,apk主要存在以下三个目录:
/system/app:存放的是系统app
/vender/app:存放时厂商预装app
/data/app:用户自己安装的app
其实还有一个地方会放一个apk文件,只不过这个apk文件很特殊,只有资源文件,没有代码,/system/framework/framework-res.apk
另外对于apk的解析,其实就是解析Androidmanifest.xml文件,例如版本号,包名,Application的各种属性,包含哪些Activity,Service等等,定义了哪些权限以及运行该应用需要哪些权限等等。同样也是先来看一看时序图吧,如下图:
图1-2
根据图1-2,可以看出扫描并解析apk文件其实调用的就是scanDirLI方法,我们就进入该方法看看吧
private void scanDirLI(File dir, int flags, int scanMode, long currentTime) { //拿到指定目录中所有的文件 String[] files = dir.list(); if (files == null) { Log.d(TAG, "No files in app dir " + dir); return; } int i; for (i=0; i<files.length; i++) { File file = new File(dir, files[i]); //过滤掉非apk文件 if (!isPackageFilename(files[i])) { // Ignore entries which are not apk‘s continue; } PackageParser.Package pkg = scanPackageLI(file, flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime); // 如果解析失败,并且非系统app,那么删掉 if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) { // Delete the apk Slog.w(TAG, "Cleaning up failed install of " + file); file.delete(); } } }
其实这个方法的逻辑非常简单,首先拿到指定目录下面的所有文件,并过滤掉非apk文件,然后调用scanPackageLI方法进行解析,注意这里的scanPackageLI方法第一个参数是File,从图1-2可以看到后面还有一个scanPackageLI方法,它的第一个参数是PackageParser.Package。我们继续看第一个scanPackageLI做了什么吧,从时序图可以看到首先创建了PackageParser.Package对象,并调用了parsePackage方法,这里同样要注意的一点就是这个parsePackage方法第一个参数是File类型,后面同样也有一个重载的方法,第一个参数类型是Resources,我们先看第一个parsePackage,它的逻辑其实也很简单,就是通过AssertManager拿到了apk文件的Resource文件,然后调用重载的parsePackage方法,在重载的parsePackage方法中,通过传入的Resources对象,拿到Androidmanifest.xml文件,并进行解析,并将结果以PackageParser.Package的形式返回给PackageManagerService,PackageManagerService为了方便以后的访问,需要将这个Package对象保存起来,于是调用了第二个scanPackageLI方法,我们现在以Package例的activity数据为例,看看怎么保存Package中的数据
N = pkg.activities.size(); r = null; for (i=0; i<N; i++) { PackageParser.Activity a = pkg.activities.get(i); a.info.processName = fixProcessName(pkg.applicationInfo.processName, a.info.processName, pkg.applicationInfo.uid); mActivities.addActivity(a, "activity"); if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { if (r == null) { r = new StringBuilder(256); } else { r.append(‘ ‘); } r.append(a.info.name); } }
这里调用了mActivitys.addActivity方法,这里的mActivitys是ActivityIntentResolver类型,我们看看addActivity具体干了什么
public final void addActivity(PackageParser.Activity a, String type) { final boolean systemApp = isSystemApp(a.info.applicationInfo); //private final HashMap<ComponentName, PackageParser.Activity> mActivities // = new HashMap<ComponentName, PackageParser.Activity>(); mActivities.put(a.getComponentName(), a); if (DEBUG_SHOW_INFO) Log.v( TAG, " " + type + " " + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":"); if (DEBUG_SHOW_INFO) Log.v(TAG, " Class=" + a.info.name); final int NI = a.intents.size(); for (int j=0; j<NI; j++) { PackageParser.ActivityIntentInfo intent = a.intents.get(j); if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) { intent.setPriority(0); Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity " + a.className + " with priority > 0, forcing to 0"); } if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } if (!intent.debugCheck()) { Log.w(TAG, "==> For Activity " + a.info.name); } // addFilter(intent); } }
这里其实是将activity数据保存到了一个HashMap数据中,这里涉及到了一个Intent机制的一些代码,我将会在下面一篇文章中详解介绍怎么通过Intent启动Android中的组件的。这里就不再介绍了。