PackageManagerService的启动过程分析

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/42215521


在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的关系。

《PackageManagerService的启动过程分析》

通过上面拿到版本号的例子,我们知道其实手机中所有App的信息都是保存在了PackageManagerService中的,我们今天研究PackageManagerService的目的其实就是熟悉PackageManagerService在启动的过程中到底做了什么,是如何保存这些信息的。PackageManagerService是通过SystemService启动的,主要是通过调用PackageManagerService的main函数开始,在main函数中,其实就是创建了一个PackageManagerService的实例并且在ServiceManager中注册,进入PackageManagerService的构造函数,你会发现这里做了很多重量级的操作,这个也是Android系统启动比较慢的一个主要原因。在开始学习之前我需要提醒大家,这里涉及到的数据结构非常多,我们没有必要知道每一个数据结构有什么,因为那样可能导致我们越陷越深,最终走不出来,我们可以先从宏观分析它的启动过程,然后再去分析它涉及的数据结构,所以为了让大家有一个比较清晰的轮廓,我先给出两个时序图,然后我们可以跟着这些时序图走,我们就永远不会迷路了。之所以给两个时序图,是因为我觉得PackageManagerServiec的启动过程大致可以分为两部分:

建议:看文章时,最好手边有一份Android源码,因为这里涉及到的代码非常多,我不方便将代码都贴出来

1、扫描xml文件并解析成对应的数据结构

2、扫描apk文件并解析成对应的数据结构

好了,我们先看第一阶段的时序图


《PackageManagerService的启动过程分析》

图 1-1

根据图1-1,发现这里引入了一个数据结构Settings,这个类主要功能就是存储第一阶段扫描xml后解析结果的,具体的结构我们后面会分析的,创建了Setttings实例之后,调用addSharedUserLPw方法,当你查看该函数的实现时,你会发现这里有引入了一个SharedUserSetting类型的结构,这个我们暂且不用去分析,接下来就调用readPermissions去扫描/system/etc/permissions/中的所有的xml文件,解析结果都存储到了上面创建的Settings类型变量了,最后调用readLPw函数读取/data/system/packages.xml文件,第一阶段就这么简单,就是扫描xml并解析。接下来我们详细分析一下Settings这个数据结构吧,我先给出这个类的类图

《PackageManagerService的启动过程分析》


这里我列出了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等等,定义了哪些权限以及运行该应用需要哪些权限等等。同样也是先来看一看时序图吧,如下图:


《PackageManagerService的启动过程分析》

图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中的组件的。这里就不再介绍了。

    原文作者:yuanzeyao
    原文地址: https://blog.csdn.net/yuanzeyao/article/details/42215521
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞