Android PackageManagerService实现剖析

1 PackageManager

/*常用命令*/
adb shell dumpsys package   (dump出系统中所有的application信息)
adb shell dumpsys package “com.android.contacts" p  (dump出系统中特定包名的application信息)
/*常用命令*/
一: PackageManager服务
在Android系统中,和用户关系最密切的service应该是PackageManager了。
一般来说,用户想要在Android Phone上进行自己感兴趣的活动,都少不了apk的支持。
不论是打电话,上网,发短信还是玩一些自己喜欢的游戏,这些内容在android的世界里都是以apk的形式存在的。
所以,apk的安装,卸载是与每个用户息息相关的。
我们接下来会用一些文章去解析PackageManager的工作原理,apk的安装和卸载的过程。
在SystemServer中,经过前面的分析,我们知道了启动Android的系统关键服务的函数首先就是startBootstrapServices.

private void startBootstrapServices() {
    // Wait for installd to finish starting up so that it has a chance to
    // create critical directories such as /data/user with the appropriate
    // permissions.  We need this to complete before we initialize other services.
    mInstaller = mSystemServiceManager.startService(Installer.class); // Install是pm路径下面的一个单独的类,主要用于通过InstallerConnection建立和installd的链接
                                                                      // 然后Installd会进行创建一些系统关键目录的作用,所以我们要等待Installd的结束,才可以继续进行其它的创建
.....
    // Only run "core" apps if we're encrypting the device.
    String cryptState = SystemProperties.get("vold.decrypt");
    if (ENCRYPTING_STATE.equals(cryptState)) {
        Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
        mOnlyCore = true;
    } else if (ENCRYPTED_STATE.equals(cryptState)) {
        Slog.w(TAG, "Device encrypted - only parsing core apps");
        mOnlyCore = true;
    }


    // Start the package manager.
    Slog.i(TAG, "Package Manager");
    // mOnlyCore 用于判断是否只扫描系统的目录
    mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); // mFactoryTestMode 用于判断是否为工厂测试,正常情况下mOnlyCore = false
    mFirstBoot = mPackageManagerService.isFirstBoot();
    mPackageManager = mSystemContext.getPackageManager();
.....
}

在经过systemServer的启动后,我们知道了系统将会通过PackageManagerService的main函数进入到PackageManager的实际工作中。
PackageManagerService的main函数其实很简单,只有一点点的内容。

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;
}

我们知道了PackageManager的main函数其实主要的实现是通过PackageManagerService的构造函数实现的。
那么构造函数的实现也是比较多的,做了很多耗时的工作。
其中,PackageManager的构造函数的主要功能为:
1. 扫描Android系统中几个目标文件夹中的apk,并且建立相应的数据结构去管理Package的信息,四大组件的信息,权限信息等内容
2. 解析物理的apk的文件,生成符合自己需求的数据结构。
所以,学习PackageManager最重要的就是学习保存各种信息的数据结构和他们之间的关系,以及控制的策略
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

二: PackageManager的构造函数之前期准备
/*UID和GID*/
如果你是一个Linux的用户,那么一定会有如下的体验。当你的Linux的系统里面,存在多个用户的时候,你在登录了用户A之后,是没有权限去修改用户B的文件内容的。
再举个极端的例子,用普通用户的权限是没有办法进行Root用户所特有的操作的。
UID: Linux系统用于区别不同的用户,使用不同的用户名是一种方法。但是用户名只是一种让人方便读的字符串,对机器来讲是没有意义的。
      为了方便机器的读取,Linux采用了一个32位的整数记录和区分不同的用户。这个用来区分不同用户的数字被称为User ID,简称UID。
      Root的UID为0,普通的UID一般是从500开始。Android系统不同,后续会进行分析
GID: 除了用户的概念,Linux的系统还有用户组的概念。同一个用户组的用户之间具有相似的特征。
      假如我们把某一个用户加入到root组,那么这个用户就可以浏览root用户组目录的文件。 如果root用户把某个文件的读写执行权限开放,root用户组的所有用户都可以修改该文件。
/*PackageManager 构造函数解析*/

     public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());
       if (mSdkVersion <= 0) { // 在最开始,我们会去检查sdk的version,因为如果检查不到sdk的话,apk就不知道运行在哪个版本上
            Slog.w(TAG, "**** ro.build.version.sdk not set!");
        }

        mContext = context;
        mFactoryTest = factoryTest;
        mOnlyCore = onlyCore;
        mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); //判断build的类型,一般为eng,user版本,eng的版本一般是不会进行odex的优化的
        mMetrics = new DisplayMetrics(); // 初始化一个DisplayMetrics,用于保存屏幕像素的参数
        mSettings = new Settings(context); // new了一个Settings的对象,这个settings是pm里面的,主要是保存系统apk的相关设置,互相之间关系等内容  --1
        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);  // --2
        mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);


        String separateProcesses = SystemProperties.get("debug.separate_processes");
        if (separateProcesses != null && separateProcesses.length() > 0) {
            if ("*".equals(separateProcesses)) {
                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
                mSeparateProcesses = null;
                Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
            } else {
                mDefParseFlags = 0;
                mSeparateProcesses = separateProcesses.split(",");
                Slog.w(TAG, "Running with debug.separate_processes: "
                        + separateProcesses);
            }
        } else {
            mDefParseFlags = " + mDefParseFlags + " mSeparateProcesses " + mSeparateProcesses);
        }
        mInstaller = installer;

        getDefaultDisplayMetrics(context, mMetrics);

        SystemConfig systemConfig = SystemConfig.getInstance();
        mGlobalGids = systemConfig.getGlobalGids();
        mSystemPermissions = systemConfig.getSystemPermissions();
        mAvailableFeatures = systemConfig.getAvailableFeatures();
......
}


/* --1 Settings的分析*/
/*
Settings的构造函数如下,在构造函数中主要是创建了/data/system的目录,并且创建了packages.xml, packages-backup.xml, packages.list, packages-stopped.xml等文件。
这些文件又都有一些具体的作用:
packages.xml:        是保存了系统所有的Package信息
packages-backup.xml: 是packages.xml的备份,防止在写packages.xml突然断电
packages.list:        保存了系统中已经安装的apk,以及对应的data/data/下面的对应关系
packages-stopped.xml: 用于记录系统中强制停止运行的Package信息
packages-stopped-backup.xml: 是packages-stopped.xml的备份,防止在写packages-stopped-backup的时候突然断电
*/

 

    Settings(Context context) {
        this(context, Environment.getDataDirectory());
    }


    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");
    }

/*在初始化了Settings的文件之后,就进行了addSharedUserLPw的操作*/
/*
    mSharedUsers是一个hashmap,HashMap<String, SharedUserSetting>,保存的是string和SharedUserSetting的对应

    SharedUserSetting类继承自GrantedPermissions,包含了如下的五个内部变量:
        final String name;
        int userId;
        // flags that are associated with this uid, regardless of any package flags
        int uidFlags;
        final HashSet<PackageSetting> packages = new HashSet<PackageSetting>();
        final PackageSignatures signatures = new PackageSignatures();
    PackageSetting 继承自PackageSettingBase,主要有下面的三个成员变量:
        int appId;
        PackageParser.Package pkg;
        SharedUserSetting sharedUser;
*/

 

    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
        SharedUserSetting s = mSharedUsers.get(name); // 从当前的mSharedUsers里面寻找对应名字的字符串是否已经有了对应
        if (s != null) {  // 如果已经存在的话,就返回对应的UID
            if (s.userId == uid) {
                return s;
            }
            // 如果从hashmap里面找到了这个String name,但是两个的uid却不一样的话,那么就会报错。因为一个shared user不可能有两个UID,只会保留第一个
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared user, keeping first: " + name);
            return null;
        }
        s = new SharedUserSetting(name, pkgFlags); // 如果从mSharedUsers里面没有找到这个name的话,就会去新建一个
        s.userId = uid; // 赋值UID
        if (addUserIdLPw(uid, s, name)) { // 将index和sharedUserSettings保存在mUserIds和mOtherUserIds
            mSharedUsers.put(name, s); // 讲name和sharedUserSettings保存在mSharedUsers
            return s;
        }
        return null;
    }

/*接下来就会进入到addUserIdLPw的函数中*/
/*
    UID前面讲了,是Linux的一种安全机制。
    在android系统里面,继承并扩展了这种安全机制。
    Android的系统,不同的应用程序之间原则上是不可以互相访问数据的。除非运行在同一个进程,或者通过contentProvider或其他的机制。
    这个安全机制的主要原因,也就是android为每一个app都分配了一个UID,每个app从Linux的层面上面,都是一个独立的用户。
    所以可以解释了这个安全机制的实现原理。
*/

 

    private boolean addUserIdLPw(int uid, Object obj, Object name) {
        if (uid > Process.LAST_APPLICATION_UID) { // 系统为app分配的UID为10000 - 19999,如果超出了这个范围就会报错
            return false;
        }

        /*
           mUserIds是一个ArrayList,也就是一个动态的数组
           private final ArrayList<Object> mUserIds = new ArrayList<Object>();

           举个例子,如果是uid=10001的用户,说明是一个app,那么这个时候就会进入到app的相应处理中去
           会去读去mUserIds的size.

           在第一次读取的时候,N肯定是为0的。但是index肯定不会为0,因为在apk安装的时候就会为其分配一个UID
           所以在第一次读取的时候,index一定是>=N的。
           这个时候,会add null,其实就是什么也没有做,而只是对N进行++
           所以,N的数字,一定是比前面遍历过得apk的uid的index大1的
           N只是用来统计当前的ArrayList里面的apk,index的数目

           mOtherUserIds的定义如下:是一个SparseArray。可以通过put,append两种方式写入数据
           private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();
        */
        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++;
            }

            /*如果通过index从mUserIds里面读取到了数据,那肯定是错误的。因为一个app只有一个index,且只能被赋值一次*/
            if (mUserIds.get(index) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate user id: " + uid
                        + " name=" + name);
                return false;
            }
            mUserIds.set(index, obj); // 将当前的obj设置到ArrayList对应的index中
        } else {
            if (mOtherUserIds.get(uid) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate shared id: " + uid
                        + " name=" + name);
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }
        return true;
    }

        /*
           mUserIds是一个ArrayList,也就是一个动态的数组
           private final ArrayList<Object> mUserIds = new ArrayList<Object>();

           举个例子,如果是uid=10001的用户,说明是一个app,那么这个时候就会进入到app的相应处理中去
           会去读去mUserIds的size.

           在第一次读取的时候,N肯定是为0的。但是index肯定不会为0,因为在apk安装的时候就会为其分配一个UID
           所以在第一次读取的时候,index一定是>=N的。
           这个时候,会add null,其实就是什么也没有做,而只是对N进行++
           所以,N的数字,一定是比前面遍历过得apk的uid的index大1的
           N只是用来统计当前的ArrayList里面的apk,index的数目

           mOtherUserIds的定义如下:是一个SparseArray。可以通过put,append两种方式写入数据
           private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();
        */

/*
    通过上述的分析,我们可以知道在PackageManager的构造函数中,先将系统的一些关键进程的UID进行了添加
    为什么这样做呢?举个例子

    在Settings里面,AndroidManifest里面有如下的声明:
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.settings"
        coreApp="true"
        android:sharedUserId="android.uid.system">

    关注的焦点为sharedUserId为android.uid.system,设置这个可以使生成的apk能够获取system权限。
    可以在任意system权限目录下面进行目录或者文件的创建,以及访问其他apk资源等

    进行这个设置,也应该是让后面的app的uid在进行添加的时候,可以找到对应的android.uid.system的权限

    然后在实际添加的过程中,调用了addSharedUserLPw函数,在这个函数中,我们会去检查是否当前的UID已经在mSharedUsers中存在,如果存在,就返回已经存在的对象。如果UID不一致,则会报错。
    如果不存在的话,就会去new一个SharedUserSetting的对象。

    在SharedUserSetting中,主要存在以下的对象。
    final String name;  --> name
    int userId;         --> 对应的UID
    int uidFlags;       --> 对应的FLAG
    final HashSet<PackageSetting> packages = new HashSet<PackageSetting>();  --> 一个PackageSetting的对象,
    final PackageSignatures signatures = new PackageSignatures();  -->  签名

    在new出来SharedUserSetting的对象后,会将传递进来的UID进行赋值给userId,这样的话,结合构造函数,就完成了对
    name, userId, uidFlags的赋值

    然后执行addUserIdLPw函数,会将当前的SharedUserSetting对象和index进行关联,如果是app的话存入到了mUserIds的动态数组,如果是系统的userId的话,将对应的SharedUserSetting存入到了mOtherUserIds的hashmap中
*/

/*
    在构造函数中,下面的code部分也是非常重要的
*/
    SystemConfig systemConfig = SystemConfig.getInstance(); // 单例模式,获取SystemConfig的实例
    mGlobalGids = systemConfig.getGlobalGids();
    mSystemPermissions = systemConfig.getSystemPermissions();
    mAvailableFeatures = systemConfig.getAvailableFeatures();

/*
    SystemConfig 的构造函数如下,可以看到,主要是去读取了一些系统的权限相关的信息
    读取的路径是/ststem/etc/目录
*/

 

    SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), false);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), false);
        // Only read features from OEM config
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), true);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), true);
    }

/*
    那么接下来去看一下readPermissions的实现
*/

 

    void readPermissions(File libraryDir, boolean onlyFeatures) {
        // Read permissions from given directory.
        if (!libraryDir.exists() || !libraryDir.isDirectory()) { // 如果文件夹不存在,或者不为一个文件夹的时候
            if (!onlyFeatures) { // 并且读取onlyFeatures的值为flase的时候
                Slog.w(TAG, "No directory " + libraryDir + ", skipping");
            }
            return;
        } // /system/etc/sysconfig 会进入到No directory skipping


        if (!libraryDir.canRead()) {  // 如果文件夹不可读的话,会返回
            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
            return;
        }


        // Iterate over the files in the directory and scan .xml files
        for (File f : libraryDir.listFiles()) { // 遍历目录下面的文件夹
            // We'll read platform.xml last
            if (f.getPath().endsWith("etc/permissions/platform.xml")) { // 最后再去处理platform.xml
                continue;
            }


            if (!f.getPath().endsWith(".xml")) { // 如果文件的路径不是xml结尾的话, 会继续读取其他的文件
                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
                continue;
            }
            if (!f.canRead()) { // 如果文件不可读的话,会返回继续读取其它的文件
                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
                continue;
            }


            readPermissionsFromXml(f, onlyFeatures);
        }


        // Read permissions from .../etc/permissions/platform.xml last so it will take precedence
        final File permFile = new File(Environment.getRootDirectory(),
                "etc/permissions/platform.xml");
        readPermissionsFromXml(permFile, onlyFeatures);
    }

然后最终会通过readPermissionsFromXml去进行文件的读取
/*
 * 正如前面的文章分析init.rc时,我们知道针对这个文本的解析,都会有一个文本的解析器。
 * 主要是对文本中的各个标签进行解析,并且对应成了相应的数据结构
 * 解析android的system config的permission也是基于这个思想进行的解析
 * 下面就来具体分析一下这个解析的过程和对应的数据结构
 */

 

private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
/*
   FileReader fr = new FileReader(String fileName);//使用带有指定文件的String参数的构造方法。创建该输入流对象。并关联源文件。
   主要方法:
   int read(); // 读取单个字符。返回作为整数读取的字符,如果已达到流末尾,则返回 -1。
   int read(char []cbuf);//将字符读入数组。返回读取的字符数。如果已经到达尾部,则返回-1。
   void close();//关闭此流对象。释放与之关联的所有资源。


   XmlPullParser:
   常用的有:
   XmlPullParser.END_DOCUMENT
   XmlPullParser.START_DOCUMENT
   XmlPullParser.START_TAG
   XmlPullParser.END_TAG
   XmlPullParser.TEXT
   分别代表着XML文档的结束,开始,标签的开始,标签的结束,内容


   XmlPullParser.getEventType() : Returns the type of the current event (START_TAG, END_TAG, TEXT, etc.) 【获取当前事件回调类型】
   XmlPullParser.getName():For START_TAG or END_TAG events, the (local) name of the current element is returned when namespaces are enabled.【获取当前节点名字】
   XmlPullParser.getAttributeValue(int index):Returns the given attributes value.【根据id获取节点属性值】
   XmlPullParser.getAttributeValue(String namespace, String name):Returns the attributes value identified by namespace URI and namespace localName.【根据name获取节点属性值】
   XmlPullParser.netxText():  If current event is START_TAG then if next element is TEXT then element content is returned or if next event is END_TAG then empty string is returned, otherwise exception is thrown.【回调节点START_TAG时,通过此方法获取节点内容】
*/
    FileReader permReader = null;
    try {
        permReader = new FileReader(permFile);
    } catch (FileNotFoundException e) {
        Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
        return;
    }


    try {
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(permReader);


        int type;
        /*
         * parser.next() 指向下一个标签
         */
        while ((type=parser.next()) != parser.START_TAG
                   && type != parser.END_DOCUMENT) {
            ;
        } // 如果下一个标签不是START_TAG的话,会一直寻找到下一个标签


        if (type != parser.START_TAG) { // 如果遍历完了,都没有遍历到一个标签的开始的话,会返回error
            throw new XmlPullParserException("No start tag found");
        }


        /*
         * parser.getName() 获取标签的标题
         */
        if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {//如果获取到的标签的标题不为permissions,或者解析出来的标签的标题不为config的话,会报错
/*
    以android.hardware.nfc.hce.xml为例,该文件中的内容为:
    <permissions>
        <feature name="android.hardware.nfc.hce" />
    </permissions>
*/
            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
                    ", expected 'permissions' or 'config'");
        }


/*
具体的标签的属性如下:
group: 安装到系统中的所有 APK 都具备的组 ID。
permission: 可以指定一个权限与几个组 ID 对应。当一个 APK 被授予这个权限时,它也同时属于这几个组。
assign-permission: 把一个权限赋予一个 UID,当进程使用这个 UID 运行时,就具备了这个权限。
library: 为系统添加一些扩展库用的。对应的.jar 文件放在/system/framework/目录下。比如Google Map 相关的库。
feature: 每添加一个硬件,都要增加对应的feature
*/
        while (true) { // 如果是permissions和config的话,会进入到这个while循环,用来解析这个大的标签,和配置文件
            XmlUtils.nextElement(parser); // 读取permissions的大标签下面的每一个小标签
            if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
                break;
            }


            String name = parser.getName();
/*
    <permission name="android.permission.MODIFY_NETWORK_ACCOUNTING">
        <group gid="net_bw_acct" />
    </permission>
*/
            if ("group".equals(name) && !onlyFeatures) {
                String gidStr = parser.getAttributeValue(null, "gid"); // 如果tag的name是group的话,根据gid去获取当前的属性
                if (gidStr != null) {
                    int gid = android.os.Process.getGidForName(gidStr);
                    mGlobalGids = appendInt(mGlobalGids, gid); // 将gid保存到mGlobalGids的数组中
                } else {
                    Slog.w(TAG, "<group> without gid at "
                            + parser.getPositionDescription());
                }


                XmlUtils.skipCurrentTag(parser);
                continue;
            } else if ("permission".equals(name) && !onlyFeatures) { // 如果tag的name是permission的话,根据name去获取当前的属性
                String perm = parser.getAttributeValue(null, "name");
                if (perm == null) {
                    Slog.w(TAG, "<permission> without name at "
                            + parser.getPositionDescription());
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
                perm = perm.intern();  //  返回string池中的perm,此处perm的值并没有改变
                readPermission(parser, perm); // 调用readPermission进行perm的解析,该函数的解析我们放到后面进行分析  ----1


/*
    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
*/
            } else if ("assign-permission".equals(name) && !onlyFeatures) { // 如果tag的name是assign-permission的话,会根据name, uid去获取相应的属性
                String perm = parser.getAttributeValue(null, "name"); // 获取name
                if (perm == null) {
                    Slog.w(TAG, "<assign-permission> without name at "
                            + parser.getPositionDescription());
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
                String uidStr = parser.getAttributeValue(null, "uid"); // 获取UID
                if (uidStr == null) {
                    Slog.w(TAG, "<assign-permission> without uid at "
                            + parser.getPositionDescription());
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
                int uid = Process.getUidForName(uidStr);   // 通过uid的string,获取到真正的UID
                if (uid < 0) {
                    Slog.w(TAG, "<assign-permission> with unknown uid \""
                            + uidStr + "\" at "
                            + parser.getPositionDescription());
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
                perm = perm.intern(); // permission的name
                HashSet<String> perms = mSystemPermissions.get(uid);  // 通过uid去perms的hashset中查询,看是否可以查询的到。
                if (perms == null) {   // 如果查询不到的话
                    perms = new HashSet<String>();  // 会新建一个perms
                    mSystemPermissions.put(uid, perms);  //然后把uid和对应的hashset放到mSystemPermissions的SparseArray中
                }
                perms.add(perm); // 将新的permission放入到perms中
                XmlUtils.skipCurrentTag(parser);
                // 通过这个操作,我们可以知道目前的数据结构是mSystemPermissions是一个SparseArray,然后是通过uid建立的映射。保存的是当前的UID所拥有的权限
            } else if ("library".equals(name) && !onlyFeatures) {
/*
<library name="android.test.runner" file="/system/framework/android.test.runner.jar" />
<library name="javax.obex" file="/system/framework/javax.obex.jar"/>
*/
                String lname = parser.getAttributeValue(null, "name");  // 通过name和file去获取对应的属性
                String lfile = parser.getAttributeValue(null, "file");
                if (lname == null) {
                    Slog.w(TAG, "<library> without name at "
                            + parser.getPositionDescription());
                } else if (lfile == null) {
                    Slog.w(TAG, "<library> without file at "
                            + parser.getPositionDescription());
                } else {
                    //Log.i(TAG, "Got library " + lname + " in " + lfile);
                    mSharedLibraries.put(lname, lfile);   // 如果都有值的话,会把name和file都push到mSharedLibraries中
                }
                XmlUtils.skipCurrentTag(parser);
                continue;


            } else if ("feature".equals(name)) {
/*
<permissions>
    <feature name="android.hardware.usb.accessory" />
    <library name="com.android.future.usb.accessory"
            file="/system/framework/com.android.future.usb.accessory.jar" />
</permissions>
*/
                String fname = parser.getAttributeValue(null, "name");  // 在feature的tag下,会获取feature的name,每增加一个硬件,都要增加对应的feature
                if (fname == null) {
                    Slog.w(TAG, "<feature> without name at "
                            + parser.getPositionDescription());
                } else {
                    //Log.i(TAG, "Got feature " + fname);
                    FeatureInfo fi = new FeatureInfo();
                    fi.name = fname;
                    mAvailableFeatures.put(fname, fi); // 将对应的feature name和其对应的FeatureInfo放入到了mAvailableFeatures中
                }
                XmlUtils.skipCurrentTag(parser);
                continue;


            } else if ("allow-in-power-save".equals(name)) {
/*
    allow-in-power-save, 应该是在省电模式下面允许该package获取网络的权限。
    目前的话,只有platform.xml里面一处有这个标签的声明


    <!-- These are the standard packages that are white-listed to always have internet
         access while in power save mode, even if they aren't in the foreground. -->
    <allow-in-power-save package="com.android.providers.downloads" />


*/
                String pkgname = parser.getAttributeValue(null, "package");
                if (pkgname == null) {
                    Slog.w(TAG, "<allow-in-power-save> without package at "
                            + parser.getPositionDescription());
                } else {
                    mAllowInPowerSave.add(pkgname); // 将该pkgname保存到mAllowInPowerSave中
                }
                XmlUtils.skipCurrentTag(parser);
                continue;


            } else if ("fixed-ime-app".equals(name)) { // 目前的permission中,并没有fixed-ime-app这个标签
                String pkgname = parser.getAttributeValue(null, "package");
                if (pkgname == null) {
                    Slog.w(TAG, "<fixed-ime-app> without package at "
                            + parser.getPositionDescription());
                } else {
                    mFixedImeApps.add(pkgname);
                }
                XmlUtils.skipCurrentTag(parser);
                continue;


            } else {
                XmlUtils.skipCurrentTag(parser);
                continue;
            }


        }
        permReader.close();
    } catch (XmlPullParserException e) {
        Slog.w(TAG, "Got execption parsing permissions.", e);
    } catch (IOException e) {
        Slog.w(TAG, "Got execption parsing permissions.", e);
    }
}

在前面的解析中,我们留下了一个函数,是在permission的标签的时候,会用readPermission去进行解析
在这个函数中,我们会把permission和对应的gid进行保存。

 

void readPermission(XmlPullParser parser, String name) // name是permission的name,parser是读取到的数据流
        throws IOException, XmlPullParserException {


    name = name.intern();


    PermissionEntry perm = mPermissions.get(name); // 去mPermissions中查看是否已经有name对应的PermissionEntry
    if (perm == null) {
        perm = new PermissionEntry(name);
        mPermissions.put(name, perm);
    }  // 如果没有的话,会new一个PermissionEntry
    int outerDepth = parser.getDepth();
    int type;
    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
           && (type != XmlPullParser.END_TAG
                   || parser.getDepth() > outerDepth)) {
        if (type == XmlPullParser.END_TAG
                || type == XmlPullParser.TEXT) {
            continue;
        }


        String tagName = parser.getName();
        if ("group".equals(tagName)) {
            String gidStr = parser.getAttributeValue(null, "gid");
            if (gidStr != null) {
                int gid = Process.getGidForName(gidStr);
                perm.gids = appendInt(perm.gids, gid);
            } else {
                Slog.w(TAG, "<group> without gid at "
                        + parser.getPositionDescription());
            }
        }
        XmlUtils.skipCurrentTag(parser);
    }
}

分析到了现在,我们分析完了systemConfig的构造函数。
在systemConfig的阶段,我们会去读取/system/etc的permission内容,然后进行解析,保存在不同的数据结构中.
我们接着回到PackageManager的构造函数中:

/*
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();  // 系统的etc的xml下面所声明的所有的gid
mSystemPermissions = systemConfig.getSystemPermissions();  // 系统中etc下xml声明的uid和其对应的权限
mAvailableFeatures = systemConfig.getAvailableFeatures(); // 系统中硬件所拥有的feature
*/

接下来我们继续分析PackageManager的构造函数

 

synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
    mHandlerThread = new ServiceThread(TAG,
            Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
    mHandlerThread.start();
    mHandler = new PackageHandler(mHandlerThread.getLooper()); //  建立PackageHandler的消息循环,用于处理apk的安装请求
    Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);


    File dataDir = Environment.getDataDirectory(); // 创建data的文件夹
    mAppDataDir = new File(dataDir, "data"); // /data/data
    mAppInstallDir = new File(dataDir, "app"); // data/app
    mAppLib32InstallDir = new File(dataDir, "app-lib"); // /data/app-lib
    mAsecInternalPath = new File(dataDir, "app-asec").getPath();  // /data/app-ases
    mUserAppDataDir = new File(dataDir, "user");   // /data/user
    mDrmAppPrivateInstallDir = new File(dataDir, "app-private");  // /data/app-private


    sUserManager = new UserManagerService(context, this,
            mInstallLock, mPackages);
....
    }
}

在进行完data的文件夹创建之后,会进入下面的一些操作:

 

    ArrayMap<String, SystemConfig.PermissionEntry> permConfig
            = systemConfig.getPermissions(); // return 的是SystemConfig的mPermissions,存放的是permission的name和gid
    for (int i=0; i<permConfig.size(); i++) {
        SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
        BasePermission bp = mSettings.mPermissions.get(perm.name);
        if (bp == null) {
            bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
            mSettings.mPermissions.put(perm.name, bp);
        }
        if (perm.gids != null) {
            bp.gids = appendInts(bp.gids, perm.gids);
        }
    }
    // 该for循环的作用是将前面从/system/etc/permission里面读取到的permission的name和对应的gid放入到bp中,然后保存在mSettings的mPermissions中


    ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
    for (int i=0; i<libConfig.size(); i++) {
        mSharedLibraries.put(libConfig.keyAt(i),
                new SharedLibraryEntry(libConfig.valueAt(i), null));
    }
    // 该for循环的作用是将读取出来的SharedLibrary放入到mSharedLibraries中


    mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

/*
这里首先调用Settings的readLPw函数去解析packages.xml和packages-backup.xml保存的安装列表信息
并把解析的pakcages信息添加到相应的数据结构中
这里我们先假设这是第一次开机,所有packages.xml和packages-backup.xml文件都还不存在。
所以Settings的readLPw函数会直接返回。

packages.xml里面保存了当前系统中保存的所有apk的详细信息
*/

 

    mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
            mSdkVersion, mOnlyCore);


    String customResolverActivity = Resources.getSystem().getString(
            R.string.config_customResolverActivity);
    if (TextUtils.isEmpty(customResolverActivity)) {
        customResolverActivity = null;
    } else {
        mCustomResolverComponentName = ComponentName.unflattenFromString(
                customResolverActivity);
    }


接下来会进行下面的处理:

 

    long startTime = SystemClock.uptimeMillis(); // 获取当前系统的时间


    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
            startTime);


    // Set flag to monitor and not change apk file paths when
    // scanning install directories.
    final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING; // 设置扫描模式


    final HashSet<String> alreadyDexOpted = new HashSet<String>();


    /**
     * Add everything in the in the boot class path to the
     * list of process files because dexopt will have been run
     * if necessary during zygote startup.
     */
    final String bootClassPath = System.getenv("BOOTCLASSPATH");
    final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");


    if(Debug_chao) {
/*
    bootClassPath为
    /system/framework/core-libart.jar:
    /system/framework/conscrypt.jar:
    /system/framework/okhttp.jar:
    /system/framework/core-junit.jar:
    /system/framework/bouncycastle.jar:
    /system/framework/ext.jar:
    /system/framework/framework.jar:
    /system/framework/telephony-common.jar:
    /system/framework/voip-common.jar:
    /system/framework/ims-common.jar:
    /system/framework/mms-common.jar:
    /system/framework/android.policy.jar:
    /system/framework/apache-xml.jar
*/
        Log.e(TAG,"bootClassPath = " + bootClassPath);
/*
    /system/framework/services.jar:
    /system/framework/ethernet-service.jar:
    /system/framework/wifi-service.jar
*/
        Log.e(TAG,"systemServerClassPath = " + systemServerClassPath);
    }


    if (bootClassPath != null) { // 将bootClassPath的值,加入到alreadyDexOpted的hashset中
/*
    BOOTCLASSPATH的赋值为:
    PRODUCT_BOOTCLASSPATH := $(subst $(space),:,$(foreach m,$(DEXPREOPT_BOOT_JARS_MODULES),/system/framework/$(m).jar))
    DEXPREOPT_BOOT_JARS_MODULES := $(PRODUCT_BOOT_JARS)


    PRODUCT_BOOT_JARS := \
        core-libart \
        conscrypt \
        okhttp \
        core-junit \
        bouncycastle \
        ext \
        framework \
        telephony-common \
        voip-common \
        ims-common \
        mms-common \
        android.policy \
        apache-xml \
*/
        String[] bootClassPathElements = splitString(bootClassPath, ':');
        for (String element : bootClassPathElements) {
            alreadyDexOpted.add(element);
        }
    } else {
        Slog.w(TAG, "No BOOTCLASSPATH found!");
    }
/*
    # The order of PRODUCT_SYSTEM_SERVER_JARS matters.
    PRODUCT_SYSTEM_SERVER_JARS := \
        services \
        ethernet-service \
        wifi-service
*/
    if (systemServerClassPath != null) {
        String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
        for (String element : systemServerClassPathElements) {
            alreadyDexOpted.add(element);
        }
    } else {
        Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
    }


    boolean didDexOptLibraryOrTool = false;


    final List<String> allInstructionSets = getAllInstructionSets(); ---1
    final String[] dexCodeInstructionSets = // 将allInstructionSets转化为数组
        getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));


    /**
     * Ensure all external libraries have had dexopt run on them.
     */
    if (mSharedLibraries.size() > 0) { // 当知道了系统abi的架构后,会进行对sharedLibraries的处理,这边的mSharedLibraries,是jar


        // NOTE: For now, we're compiling these system "shared libraries"
        // (and framework jars) into all available architectures. It's possible
        // to compile them only when we come across an app that uses them (there's
        // already logic for that in scanPackageLI) but that adds some complexity.
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {  // 遍历所支持的平台,一般有arm,x86,arm64等平台
            for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
                final String lib = libEntry.path; // 获取lib的path
                if (lib == null) {
                    continue;
                }


                try {
    /*
     * 该函数为L上面新增的接口
     *
     * Returns UP_TO_DATE if the VM believes that the apk/jar file
     * is up to date, PATCHOAT_NEEDED if it believes that the file is up
     * to date but it must be relocated to match the base address offset,
     * and DEXOPT_NEEDED if it believes that it is out of date and should
     * be passed through "dexopt" again.
     *
     * @param fileName the absolute path to the apk/jar file to examine.
     * @return DEXOPT_NEEDED if dexopt should be called on the file,
     *         PATCHOAT_NEEDED if we need to run "patchoat" on it and
     *         UP_TO_DATE otherwise.
     */
                    byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
                                                                         dexCodeInstructionSet,
                                                                         false);
                    if (dexoptRequired != DexFile.UP_TO_DATE) {  // 如果不是UP_TO_DATE
                        alreadyDexOpted.add(lib); // 会将lib path加入到alreadyDexOpted


                        // The list of "shared libraries" we have at this point is
                        if (dexoptRequired == DexFile.DEXOPT_NEEDED) {  // 如果是DEXOPT_NEEDED的话,灰烬行dexopt的优化
                            mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                        } else {  // 如果不是的话,那就是PATCHOAT_NEEDED,会进行oat的优化
                            mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                        }
                        didDexOptLibraryOrTool = true;  // 只要进行了oat或者opt的优化的话,会将该变量置为true
                    }
                } catch (FileNotFoundException e) {
                    Slog.w(TAG, "Library not found: " + lib);
                } catch (IOException e) {
                    Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
                            + e.getMessage());
                }
            }
        }
    }

-------------------------------------------
1.  getAllInstructionSets函数的实现:

 

    private static List<String> getAllInstructionSets() {
        final String[] allAbis = Build.SUPPORTED_ABIS; // 获取当前手机所支持的硬件架构
        final List<String> allInstructionSets = new ArrayList<String>(allAbis.length); // new一个List,长度即为获取出来的ABI的个数

        for (String abi : allAbis) { // 从所有的支持的架构开始遍历,abi为armeabi,armeabi-v7a....
            final String instructionSet = VMRuntime.getInstructionSet(abi); // 从以下的2,3可以看到,是从目前android系统支持的架构map中,去查找,查找出来一般为arm,mips,arm64...
            if (!allInstructionSets.contains(instructionSet)) { // 如果查询到的不包含在我们从Build的supported里面的话,把其加入到allInstructionSets里面
                allInstructionSets.add(instructionSet); // 第一次的时候,因为allInstructionSets为一个刚new出来的对象,所以肯定是会加入到allInstructionSets里面
            }
        }


        return allInstructionSets;
    }

2. VMRuntime getInstructionSet的函数的实现:

 

    public static String getInstructionSet(String abi) {
        final String instructionSet = ABI_TO_INSTRUCTION_SET_MAP.get(abi);
        if (instructionSet == null) {
            throw new IllegalArgumentException("Unsupported ABI: " + abi);
        }


        return instructionSet;
    }

3.  ABI_TO_INSTRUCTION_SET_MAP的声明:

 

    private static final Map<String, String> ABI_TO_INSTRUCTION_SET_MAP
            = new HashMap<String, String>();
    static {
        ABI_TO_INSTRUCTION_SET_MAP.put("armeabi", "arm");
        ABI_TO_INSTRUCTION_SET_MAP.put("armeabi-v7a", "arm");
        ABI_TO_INSTRUCTION_SET_MAP.put("mips", "mips");
        ABI_TO_INSTRUCTION_SET_MAP.put("mips64", "mips64");
        ABI_TO_INSTRUCTION_SET_MAP.put("x86", "x86");
        ABI_TO_INSTRUCTION_SET_MAP.put("x86_64", "x86_64");
        ABI_TO_INSTRUCTION_SET_MAP.put("arm64-v8a", "arm64");
    }
---------------------------------------------------------------------------------
    File frameworkDir = new File(Environment.getRootDirectory(), "framework"); // /system/framework


    // Gross hack for now: we know this file doesn't contain any
    // code, so don't dexopt it to avoid the resulting log spew.
    alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk"); // 将framework-res.apk加入到alreadyDexOpted中


    // Gross hack for now: we know this file is only part of
    // the boot class path for art, so don't dexopt it to
    // avoid the resulting log spew.
    alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");


    /**
     * And there are a number of commands implemented in Java, which
     * we currently need to do the dexopt on so that they can be
     * run from a non-root shell.
     */
    String[] frameworkFiles = frameworkDir.list();  // 或者/system/framework下的所有文件
    if (frameworkFiles != null) {
        // TODO: We could compile these only for the most preferred ABI. We should
        // first double check that the dex files for these commands are not referenced
        // by other system apps.
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
            for (int i=0; i<frameworkFiles.length; i++) {
                File libPath = new File(frameworkDir, frameworkFiles[i]);  // 获取文件的绝对路径
                String path = libPath.getPath(); // path是绝对路径
                // Skip the file if we already did it.
                if (alreadyDexOpted.contains(path)) { // 如果该文件是已经包含在alreadyDexOpted中了,就忽略不去处理
                    continue;
                }
                // Skip the file if it is not a type we want to dexopt.
                if (!path.endsWith(".apk") && !path.endsWith(".jar")) { // 如果不是以jar和apk结尾的话,就忽略。因为odex优化针对的就是jar和apk
                    continue;
                }
                try {
                    byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
                                                                         dexCodeInstructionSet,
                                                                         false);  // 判断文件是否需要优化
                    if (dexoptRequired == DexFile.DEXOPT_NEEDED) {  // 如果需要dex的话,就会去进行Dex的优化
                        mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                        didDexOptLibraryOrTool = true;
                    } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) { // 如果是需要oat优化的话,就会去进行oat的优化
                        mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
                        didDexOptLibraryOrTool = true;
                    }
                } catch (FileNotFoundException e) {
                    Slog.w(TAG, "Jar not found: " + path);
                } catch (IOException e) {
                    Slog.w(TAG, "Exception reading jar: " + path, e);
                }
            }
        }
    }

    // 在接下来的处理中,就会遇到packagemanager的一个重要的函数,scanDirLI,我们接下来继续分析。

 

    // (Do this before scanning any apps.)
    // For security and version matching reason, only consider
    // overlay packages if they reside in VENDOR_OVERLAY_DIR.


    /*
     * private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
     */
    File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
    scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0); // 扫描该系统目录下的所有apk,进行安装 -1


    // Find base frameworks (resource packages without code).
    scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR
            | PackageParser.PARSE_IS_PRIVILEGED,
            scanFlags | SCAN_NO_DEX, 0);


    // Collected privileged system packages.
    final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
    scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR
            | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);


    // Collect ordinary system packages.
    final File systemAppDir = new File(Environment.getRootDirectory(), "app");
    scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


    // Collect all vendor packages.
    File vendorAppDir = new File("/vendor/app");
    try {
        vendorAppDir = vendorAppDir.getCanonicalFile();
    } catch (IOException e) {
        // failed to look up canonical path, continue with original one
    }
    scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


    // Collect all OEM packages.
    final File oemAppDir = new File(Environment.getOemDirectory(), "app");
    scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);


    if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
    mInstaller.moveFiles();   

-------------------------------------------------------------------------------------------------------------------
    /*
     *  scanDirLI的实现如下:
     */

 

    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = dir.listFiles(); // 会从传入的dir中,遍历所有的文件
        if (ArrayUtils.isEmpty(files)) {  // 如果该文件夹中一个文件都没有的话,会return
            Log.d(TAG, "No files in app dir " + dir);
            return;
        }


        if (DEBUG_PACKAGE_SCANNING) {
            Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
                    + " flags=0x" + Integer.toHexString(parseFlags));
        }


        for (File file : files) {  // 如果有文件存在的话,就会进行遍历
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName()); // 判断一个文件是否是一个apk的文件(以apk结尾),或者是一个文件并且文件夹满足isStageName的条件
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            try {
                scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                        scanFlags, currentTime, null); // 会利用scanPackageLI进行接下来的解析
            } catch (PackageManagerException e) {
                Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());


                // Delete invalid userdata apps
                if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                        e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { // 只有非系统的apk扫描失败的时候,才会删除该apk。
                    logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
                    if (file.isDirectory()) {
                        FileUtils.deleteContents(file);
                    }
                    file.delete();
                }
            }
        }
    }

---------------------------------------------------------------------------------------------------------------------

 

    public static boolean isStageName(String name) {
        final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
        final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
        final boolean isLegacyContainer = name.startsWith("smdl2tmp");
        return isFile || isContainer || isLegacyContainer;
    }

---------------------------------------------------------------------------------------------------------------------

 

  /*
     *  Scan a package and return the newly parsed package.
     *  Returns null in case of errors and the error code is stored in mLastScanError
     *  调用scanPackageLI函数扫描一个特定的文件,返回值是PackageParser的内部类package。
     *  该类的实例代表一个apk文件,所以它就是和apk文件对应的数据结构
     */
    private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException {
        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
        parseFlags |= mDefParseFlags;
        PackageParser pp = new PackageParser(); // new 了一个PackageParser的对象
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setOnlyCoreApps(mOnlyCore);
        pp.setDisplayMetrics(mMetrics);


        if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
        }


        /*
         * 各个参数的值如下:
           mSeparateProcesses = null,因为前面初始化值的时候separateProcesses为null
           mOnlyCore = false   正常情况下mOnlyCore为flase
           mMetrics 为前面获取到的手机的分辨率
           mDefParseFlags 的值为0
           parseFlags的值会随着scanFile的变化而变化
         */


        final PackageParser.Package pkg;
        try {
            pkg = pp.parsePackage(scanFile, parseFlags);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        }


        PackageSetting ps = null;
        PackageSetting updatedPkg;
        // reader
        synchronized (mPackages) {
            // Look to see if we already know about this package.
            String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
            if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
                // This package has been renamed to its original name.  Let's
                // use that.
                ps = mSettings.peekPackageLPr(oldName);
            }
            // If there was no original package, see one for the real package name.
            if (ps == null) {
                ps = mSettings.peekPackageLPr(pkg.packageName);
            }
            // Check to see if this package could be hiding/updating a system
            // package.  Must look for it either under the original or real
            // package name depending on our state.
            updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
            if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
        }
        boolean updatedPkgBetter = false;
        // First check if this is a system package that may involve an update
        if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
            if (ps != null && !ps.codePath.equals(scanFile)) {
                // The path has changed from what was last scanned...  check the
                // version of the new path against what we have stored to determine
                // what to do.
                if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
                if (pkg.mVersionCode < ps.versionCode) {
                    // The system package has been updated and the code path does not match
                    // Ignore entry. Skip it.
                    logCriticalInfo(Log.INFO, "Package " + ps.name + " at " + scanFile
                            + " ignored: updated version " + ps.versionCode
                            + " better than this " + pkg.mVersionCode);
                    if (!updatedPkg.codePath.equals(scanFile)) {
                        Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg : "
                                + ps.name + " changing from " + updatedPkg.codePathString
                                + " to " + scanFile);
                        updatedPkg.codePath = scanFile;
                        updatedPkg.codePathString = scanFile.toString();
                        // This is the point at which we know that the system-disk APK
                        // for this package has moved during a reboot (e.g. due to an OTA),
                        // so we need to reevaluate it for privilege policy.
                        if (locationIsPrivileged(scanFile)) {
                            updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
                        }
                    }
                    updatedPkg.pkg = pkg;
                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null);
                } else {
                    // The current app on the system partition is better than
                    // what we have updated to on the data partition; switch
                    // back to the system partition version.
                    // At this point, its safely assumed that package installation for
                    // apps in system partition will go through. If not there won't be a working
                    // version of the app
                    // writer
                    synchronized (mPackages) {
                        // Just remove the loaded entries from package lists.
                        mPackages.remove(ps.name);
                    }


                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
                            + " reverting from " + ps.codePathString
                            + ": new version " + pkg.mVersionCode
                            + " better than installed " + ps.versionCode);


                    InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                            ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString,
                            getAppDexInstructionSets(ps));
                    synchronized (mInstallLock) {
                        args.cleanUpResourcesLI();
                    }
                    synchronized (mPackages) {
                        mSettings.enableSystemPackageLPw(ps.name);
                    }
                    updatedPkgBetter = true;
                }
            }
        }


        if (updatedPkg != null) {
            // An updated system app will not have the PARSE_IS_SYSTEM flag set
            // initially
            parseFlags |= PackageParser.PARSE_IS_SYSTEM;


            // An updated privileged app will not have the PARSE_IS_PRIVILEGED
            // flag set initially
            if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
                parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
            }
        }


        // Verify certificates against what was last scanned
        collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);


        /*
         * A new system app appeared, but we already had a non-system one of the
         * same name installed earlier.
         */
        boolean shouldHideSystemApp = false;
        if (updatedPkg == null && ps != null
                && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
            /*
             * Check to make sure the signatures match first. If they don't,
             * wipe the installed application and its data.
             */
            if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
                    != PackageManager.SIGNATURE_MATCH) {
                logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
                        + " signatures don't match existing userdata copy; removing");
                deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
                ps = null;
            } else {
                /*
                 * If the newly-added system app is an older version than the
                 * already installed version, hide it. It will be scanned later
                 * and re-added like an update.
                 */
                if (pkg.mVersionCode < ps.versionCode) {
                    shouldHideSystemApp = true;
                    logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
                            + " but new version " + pkg.mVersionCode + " better than installed "
                            + ps.versionCode + "; hiding system");
                } else {
                    /*
                     * The newly found system app is a newer version that the
                     * one previously installed. Simply remove the
                     * already-installed application and replace it with our own
                     * while keeping the application data.
                     */
                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
                            + " reverting from " + ps.codePathString + ": new version "
                            + pkg.mVersionCode + " better than installed " + ps.versionCode);
                    InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                            ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString,
                            getAppDexInstructionSets(ps));
                    synchronized (mInstallLock) {
                        args.cleanUpResourcesLI();
                    }
                }
            }
        }


        // The apk is forward locked (not public) if its code and resources
        // are kept in different files. (except for app in either system or
        // vendor path).
        // TODO grab this value from PackageSettings
        if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
            if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
                parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
            }
        }


        // TODO: extend to support forward-locked splits
        String resourcePath = null;
        String baseResourcePath = null;
        if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {
            if (ps != null && ps.resourcePathString != null) {
                resourcePath = ps.resourcePathString;
                baseResourcePath = ps.resourcePathString;
            } else {
                // Should not happen at all. Just log an error.
                Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
            }
        } else {
            resourcePath = pkg.codePath;
            baseResourcePath = pkg.baseCodePath;
        }


        // Set application objects path explicitly.
        pkg.applicationInfo.setCodePath(pkg.codePath);
        pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
        pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
        pkg.applicationInfo.setResourcePath(resourcePath);
        pkg.applicationInfo.setBaseResourcePath(baseResourcePath);
        pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);


        // Note that we invoke the following method only if we are about to unpack an application
        PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
                | SCAN_UPDATE_SIGNATURE, currentTime, user);


        /*
         * If the system app should be overridden by a previously installed
         * data, hide the system app now and let the /data/app scan pick it up
         * again.
         */
        if (shouldHideSystemApp) {
            synchronized (mPackages) {
                /*
                 * We have to grant systems permissions before we hide, because
                 * grantPermissions will assume the package update is trying to
                 * expand its permissions.
                 */
                grantPermissionsLPw(pkg, true, pkg.packageName);
                mSettings.disableSystemPackageLPw(pkg.packageName);
            }
        }


        return scannedPkg;
    }

---------------------------------------------------------------------------------------------------------------
    在parsePackage的时候,会进行如下处理,如果是文件夹的话,会调用parseClusterPackage
    如果不是文件夹的话,会进行parseMonolithicPackage的操作。

 

    public Package parsePackage(File packageFile, int flags) throws PackageParserException {
        if (packageFile.isDirectory()) {
            return parseClusterPackage(packageFile, flags);
        } else {
            return parseMonolithicPackage(packageFile, flags);
        }
    }

---------------------------------------------------------------------------------------------------------------
    对于普通apk来说的话,是会进行parseMonolithicPackage的操作。然后我们来看一下这个函数的实现。

 

    @Deprecated
    public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
        if (mOnlyCoreApps) { // mOnlyCoreApps的值是mOnlyCore,正常情况下也为false
            final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
            if (!lite.coreApp) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                        "Not a coreApp: " + apkFile);
            }
        }


        final AssetManager assets = new AssetManager(); // new了一个AssetManager的对象,AssetManager是资源管理框架
        try {
            final Package pkg = parseBaseApk(apkFile, assets, flags); // 在parseBaseApk的时候,会把assets传人
            pkg.codePath = apkFile.getAbsolutePath();
            return pkg;
        } finally {
            IoUtils.closeQuietly(assets);
        }
    }

----------------------------------------------------------------------------------------------------------------

 

    private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath(); // 返回apkFile绝对路径名字字符串


        mParseError = PackageManager.INSTALL_SUCCEEDED; // 在parse的开始,将mParseError的值设置为INSTALL_SUCCEEDED
        mArchiveSourcePath = apkFile.getAbsolutePath();  // 返回apkFile的绝对路径的字符串


        if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);


        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);


        Resources res = null;
        XmlResourceParser parser = null;
        try {
            res = new Resources(assets, mMetrics, null);
            /*
             Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的当前配置信息,
             例如,屏幕大小和密码、国家地区和语言、键盘配置情况等等,
             接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中去。
            */
            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);


            final String[] outError = new String[1];
            final Package pkg = parseBaseApk(res, parser, flags, outError);
            if (pkg == null) {
                throw new PackageParserException(mParseError,
                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
            }


            pkg.baseCodePath = apkPath;
            pkg.mSignatures = null;


            return pkg;


        } catch (PackageParserException e) {
            throw e;
        } catch (Exception e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to read manifest from " + apkPath, e);
        } finally {
            IoUtils.closeQuietly(parser);
        }
    }

---------------------------------------------------------------------------------------------------------------

 

    private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
            throws PackageParserException {
        if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                    "Invalid package file: " + apkPath);
        }


        // The AssetManager guarantees uniqueness for asset paths, so if this asset path
        // already exists in the AssetManager, addAssetPath will only return the cookie
        // assigned to it.
        int cookie = assets.addAssetPath(apkPath); // addAssetPath 函数的作用是来将参数resDir所描述的Apk文件路径作为它的资源目录。
        /* cookie有两种情况,分别为等不等于0. 如果等于0的话
           AssetManager类的成员函数addAssetPath接着再检查在其成员变量mAssetPaths所描述的一个类型为asset_path的Vector中是否已经添加过参数path所描述的一个Apk文件路径了。
           如果已经添加过了,那么AssetManager类的成员函数addAssetPath就不会再继续往下处理了,而是将与参数path所描述的一个Apk文件路径所对应的一个Cookie返回给调用者,即保存在输出参数cookie中,
           前提是参数cookie的值不等于NULL。
           一个Apk文件路径所对应的Cookie实际上只是一个整数,这个整数表示该Apk文件路径所对应的一个asset_path对象在成员变量mAssetPaths所描述的一个Vector中的索引再加上1。
         */
        if (cookie == 0) { // 等于0的时候,是添加vector失败的。所以会throw一个exception
            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Failed adding asset path: " + apkPath);
        }
        return cookie;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值