PackageManagerService的启动过程分析

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

时间: 2024-11-07 22:24:12

PackageManagerService的启动过程分析的相关文章

ActivityManagerService启动过程分析

ActivityManagerService启动过程 一 从Systemserver到AMS zygote-> systemserver:java入层口: /** * The main entry point from zygote. */ public static void main(String[] args) { new SystemServer().run(); } 接下来继续看SystemServer run函数执行过程: private void run() { // 准备Syst

startActivity启动过程分析(转)

基于Android 6.0的源码剖析, 分析android Activity启动流程,相关源码: frameworks/base/services/core/java/com/android/server/am/ - ActivityManagerService.java - ActivityStackSupervisor.java - ActivityStack.java - ActivityRecord.java - ProcessRecord.java frameworks/base/co

startActivity启动过程分析

copy from : http://gityuan.com/2016/03/12/start-activity/ 基于Android 6.0的源码剖析, 分析android Activity启动流程,相关源码: frameworks/base/services/core/java/com/android/server/am/ - ActivityManagerService.java - ActivityStackSupervisor.java - ActivityStack.java - A

S5PV210-kernel-内核启动过程分析

1.1.内核启动过程分析前的准备 1.拿到一个内核源码时,先目录下的无用文件删除 2.建立SI工程 3.makefile (1)makefile中不详细的去分析,几个关键的地方,makefile开始部分是kernel的版本号,这个版本号比较重要,因为在模块化驱动安装时会需要用到,要注意会查,会改,版本号在makefile中,改直接改的就行 (2)kernel顶层的makefile中定义的两个变量很重要,一个是ARCH,一个CROSS,ARCH表示我们当前的配置编译路径,如果我们的ARCH =AR

Android4.0(Phone)拨号启动过程分析(二)

接上:Android4.0(Phone)拨号启动过程分析(一) InCallScreen处理来电和拨号的界面,接通电话也是这个界面,接下来分析InCallScreen类是如何处理拨号流程的: @Override protected void onCreate(Bundle icicle) { Log.i(LOG_TAG, "onCreate()... this = " + this); Profiler.callScreenOnCreate(); super.onCreate(icic

OpenWrt启动过程分析+添加自启动脚本【转】

一.OpenWrt启动过程分析 转自: http://www.eehello.com/?post=107 总结一下OpenWrt的启动流程:1.CFE->2.linux->3./etc/preinit->4./sbin/init ->5./etc/inittab ->6./etc/init.d/rcS->7./etc/rc.d/S* ->8. OpenWrt是一个开放的linux平台,主要用于带wifi的无线路由上. 类似于Ubuntu.Red Hat.之类的li

ARM多核处理器启动过程分析

说明: 该流程图按照代码执行时间顺序划分为4部分: 1.     Bootloader在图片上半部,最先启动: 2.     Kernel在图片下半部,由bootloader引导启动: 3.CPU0执行流程在图片左半部,bootloader代码会进行判断,先行启动CPU0: 4.  Secondary CPUs在图片右半部,由CPU唤醒 具体启动流程如下: 1.     在bootloader启动时,会判断执行代码的是否为CPU0,如果不是,则执行wfe等待CPU0发出sev指令唤醒.如果是CP

Chromium的Plugin进程启动过程分析

前面我们分析了Chromium的Render进程和GPU进程的启动过程,它们都是由Browser进程启动的.在Chromium中,还有一类进程是由Browser进程启动的,它们就是Plugin进程.顾名思义,Plugin进程是用来运行浏览器插件的.浏览器插件的作用是扩展网页功能,它们由第三方开发,安全性和稳定性都无法得到保证,因此运行在独立的进程中.本文接下来就详细分析Plugin进程的启动过程. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 在Chro

Chromium的GPU进程启动过程分析

Chromium除了有Browser进程和Render进程,还有GPU进程.GPU进程负责Chromium的GPU操作,例如Render进程通过GPU进程离屏渲染网页,Browser进程也是通过GPU进程将离屏渲染好的网页显示在屏幕上.Chromium之所以将GPU操作运行在独立进程中,是考虑到稳定性问题.毕竟GPU操作是硬件相关操作,硬件的差异性会引发一定的不稳性.本文分析GPU进程的启动过程. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! GPU进程