PackageManagerService源码分析之第一阶段(二)

来源:互联网 发布:曲面显示屏优缺点知乎 编辑:程序博客网 时间:2024/04/28 20:16

1、背景

在前面一篇文章中,我们介绍了和PackageManagerService相关的一些类和关系,如果大家不是很明白的可以先去看一下这篇文章PackageManagerService源码分析之入门(一)。
由于PackageManagerService(后面简称为PKMS)是系统的核心服务,所以我准备多写几篇文章来分析。

在之前的博客文章中我们讲解了PKMS是通过SystemServer被创建出来的,具体可以参考SystemServer启动流程之SystemServer分析(三)这篇文章,其相关代码如下:

private void startBootstrapServices() {    // Start the package manager.        Slog.i(TAG, "Package Manager");        //调用PackageManagerService的main方法        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);        //判断系统是否第一次启动        mFirstBoot = mPackageManagerService.isFirstBoot();        mPackageManager = mSystemContext.getPackageManager();        //dex优化        mPackageManagerService.performBootDexOpt();        //通知系统进入就绪状态        mPackageManagerService.systemReady();}

这里可以看到在SystemServer中创建了PKMS服务,并调用其main方法,我们进入其main方法。

public static final PackageManagerService main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {        //调用PKMS的main方法        PackageManagerService m = new PackageManagerService(context, installer,                factoryTest, onlyCore);        //向ServiceManager注册PKMS        ServiceManager.addService("package", m);        return m;    }

这里我们需要注意的是向ServiceManager中注册PKMS,其key值是package,通过ContextImpl.getPackageManager()来获得PackageManager对象时会使用到。

可以看出其main方法还是比较简单的只有几行代码,但是其执行时间却比较长,因为其执行的都是重体力活,这就是Android启动慢的原因之一。

我们先来大致说明一下PKMS的构造函数的主要功能:扫描系统中几个特定文件夹下的apk,从而建立合适的数据结构来管理Package信息,四大组件信息,权限信息等(PKMS主要解析apk文件的AndroidManifest.xml文件)。由于其工作是如此的繁重,所以我准备分3个阶段来分析。

  • 扫描文件夹前的准备工作
  • 扫描目标文件夹
  • 扫描之后的处理工作

    好啦,接下来将正式的进入我们的第一阶段分析。

2、扫描文件夹前的准备工作

我们进入PKMS的构造函数,由于其构造函数相关的长,所以我们将分段解析。

(1)初识Settings

final int mSdkVersion = Build.VERSION.SDK_INT;public PackageManagerService(Context context, Installer installer,            boolean factoryTest, boolean onlyCore) {        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,                SystemClock.uptimeMillis());        //mSdkVersion始PKMS的成员变量,其值取自系统属性ro.build.version.sdk        if (mSdkVersion <= 0) {            Slog.w(TAG, "**** ro.build.version.sdk not set!");        }        mContext = context;        mFactoryTest = factoryTest;        mOnlyCore = onlyCore;        //如果是eng版,则扫描Package后,不对package进行dex优化        mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));        //显示屏相关属性(屏幕尺寸和分辨率)        mMetrics = new DisplayMetrics();        //重点分析对象        mSettings = new Settings(context);        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);        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);}

我们先来看一下Settings这个类的构造函数。

Settings(Context context) {        this(context, Environment.getDataDirectory());    }    Settings(Context context, File dataDir) {        //创建data/system目录        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, 0640, 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类在data目录下新建了system目录,并在该目录下新建几个重要的文件,我们首先来看一下packages.xml文件。

1.packages.xml:记录系统中所有已安装应用的相关信息

<package name="com.android.calendar" codePath="/system/app/MtkCalendar" nativeLibraryPath="/system/app/MtkCalendar/lib" publicFlags="940162629" privateFlags="0" pkgFlagsEx="0" ft="157a9940280" it="157a9940280" ut="157a9940280" version="23" userId="10059">        <sigs count="1">            <cert index="1" />        </sigs>        <perms>            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />            <item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />            <item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" />            <item name="android.permission.NFC" granted="true" flags="0" />            <item name="android.permission.WRITE_SYNC_SETTINGS" granted="true" flags="0" />            <item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />            <item name="android.permission.INTERNET" granted="true" flags="0" />            <item name="android.permission.INTERACT_ACROSS_USERS_FULL" granted="true" flags="0" />            <item name="android.permission.INTERACT_ACROSS_USERS" granted="true" flags="0" />            <item name="android.permission.READ_SYNC_SETTINGS" granted="true" flags="0" />            <item name="android.permission.VIBRATE" granted="true" flags="0" />            <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />        </perms>        <proper-signing-keyset identifier="1" />    </package>

2.packages-backup.xml:上面文件的备份
3.packages-stopped.xml:被强制停止运行的应用信息
4.packages-stopped-backup.xml:上面文件的备份
5.packages.list:保存普通应用的数据目录和uid信息等

......com.android.calculator2 10035 0 /data/data/com.android.calculator2 default nonecom.slightech.slife.zte 10081 0 /data/data/com.slightech.slife.zte default 3003com.mediatek.lbs.em2.ui 10056 0 /data/data/com.mediatek.lbs.em2.ui platform 3003com.android.wallpaper 10054 0 /data/data/com.android.wallpaper default nonecom.android.vpndialogs 10026 0 /data/data/com.android.vpndialogs platform nonecom.android.email 10043 0 /data/data/com.android.email platform 1023,1015,3003com.android.music 10062 0 /data/data/com.android.music platform 1023,1015,3003......

当Android对文件packages.xml和packages-stopped.xml写之前会先将它们备份,如果文件写入成功了就把备份文件删除掉,如果写的时候,系统出问题重启了,重启后会读取这两份文件时,发现有备份文件,会使用备份文件的内容,因为此时原文件已经损坏了。

Settings类的构造器已经分析完毕了,我们回到PackageManagerService的构造器中,此时Settings对象已经创建好了,接下来将调用其addSharedUserLPw()方法,我们以第一个为例。

mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);

第一个参数是字符串android.uid.system,第二个参数是SYSTEM_UID值为1000,表示系统进程的用户id,第三个参数FLAG_SYSTEM表示是系统Package、而FLAG_PRIVILEGED表示其具有高权限。

我们进入addSharedUserLPw方法。

final ArrayMap<String, SharedUserSetting> mSharedUsers =            new ArrayMap<String, SharedUserSetting>();SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {        //mSharedUsers是一个ArrayMap,其key为字符串,值为SharedUserSetting对象        SharedUserSetting s = mSharedUsers.get(name);        if (s != null) {            if (s.userId == uid) {                return s;            }            return null;        }        //创建SharedUserSetting对象,并设置其userId        s = new SharedUserSetting(name, pkgFlags);        s.userId = uid;        //这里调用addUserIdLPw()方法        if (addUserIdLPw(uid, s, name)) {            //将name设置为key值存储s            mSharedUsers.put(name, s);            return s;        }        return null;    }

从以上代码分析可知,Settings有一个成员变量mSharedUsers,该ArrayMap以字符串为key,SharedUserSetting为值的键值对,接下来我们来看一下和SharedUserSetting相关类的关系图。

SharedUserSetting相关类类图

可以看出:

  1. 可以看出Settings类定义了一个mSharedUsers变量(ArrayMap类型),以字符串为key(如android.uid.system),对应的Value是SharedUserSetting对象。
  2. SharedUserSetting继承于GrantedPermissions类,从命名可知它和权限相关,SharedUserSetting类中定义了一个成员变量packages(类型是ArraySet),用于保存声明了相同sharedUserId的Package权限设置信息。
  3. 每个Package有自己的权限设置,权限由PackageSetting类表达,该类继承于PackageSettingBase,而PackageSettingBase类又继承于GrantedPermissions类。
  4. Settings类中还有两个成员变量mUserIds(类型是ArrayList)和mOtherUserIds(类型是SparseArray),其目的是通过UID找到对应的SharedUserSetting对象。

下面来分析一下addUserIdLPw()方法。

private boolean addUserIdLPw(int uid, Object obj, Object name) {        //应用APK所在进程的uid>10000,而系统APK所在进程的uid<10000        //uid不能超过限制,LAST_APPLICATION_UID的值是19999        if (uid > Process.LAST_APPLICATION_UID) {            return false;        }        //表示应用APK的uid        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++;            }            if (mUserIds.get(index) != null) {                return false;            }            //mUserIds保存应用Package            mUserIds.set(index, obj);        } else {            if (mOtherUserIds.get(uid) != null) {                return false;            }            //mOtherUserIds保存系统Package            mOtherUserIds.put(uid, obj);        }        return true;    }

好啦,到此Settings的分析就结束啦,我们接着PKMS的构造器往下分析。

(2)扫描XML文件

//将传入的installer对象赋值给本类的mInstaller对象,以便后续安装应用时使用mInstaller = installer;//获取当前的显示屏信息getDefaultDisplayMetrics(context, mMetrics);//创建SystemConfig对象,并调用其相关方法SystemConfig systemConfig = SystemConfig.getInstance();mGlobalGids = systemConfig.getGlobalGids();mSystemPermissions = systemConfig.getSystemPermissions();mAvailableFeatures = systemConfig.getAvailableFeatures();private static void getDefaultDisplayMetrics(Context context, DisplayMetrics metrics) {        DisplayManager displayManager = (DisplayManager) context.getSystemService(                Context.DISPLAY_SERVICE);        displayManager.getDisplay(Display.DEFAULT_DISPLAY).getMetrics(metrics);    }

这里我们主要来分析SystemConfig类,我们进入该类的getInstance()方法。

public static SystemConfig getInstance() {        synchronized (SystemConfig.class) {            if (sInstance == null) {                sInstance = new SystemConfig();            }            return sInstance;        }    }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);    }public static File buildPath(File base, String... segments) {        File cur = base;        for (String segment : segments) {            if (cur == null) {                cur = new File(segment);            } else {                cur = new File(cur, segment);            }        }        return cur;    }

可以看出这是一个单例类,然后一层一层的建立文件夹,依次为system/etc/sysconfig,system/etc/permissions,oem/etc/sysconfig,oem/etc/permissions四个文件夹(可能有的文件夹手机中不存在)。

当四个文件夹创建完成后,接下来就是调用readPermissions方法对其循环遍历。

void readPermissions(File libraryDir, boolean onlyFeatures) {        // Read permissions from given directory.        if (!libraryDir.exists() || !libraryDir.isDirectory()) {            return;        }        if (!libraryDir.canRead()) {            return;        }        // Iterate over the files in the directory and scan .xml files        File platformFile = null;        //获取libraryDir下所有文件路径,然后通过for循环遍历操作该目录下所有文件        for (File f : libraryDir.listFiles()) {            // We'll read platform.xml last            //当遇到该目录下的platform.xml文件时,将其保存到变量platformFile中后续处理            if (f.getPath().endsWith("etc/permissions/platform.xml")) {                platformFile = f;                continue;            }            if (!f.getPath().endsWith(".xml")) {                continue;            }            if (!f.canRead()) {                continue;            }            //调用readPermissionsFromXml方法对结尾为xml文件进行处理            readPermissionsFromXml(f, onlyFeatures);        }        // Read platform permissions last so it will take precedence        //最后也是调用readPermissionsFromXml方法单独处理platform.xml文件        if (platformFile != null) {            readPermissionsFromXml(platformFile, onlyFeatures);        }    }

可以看出该方法主要就是将上面四个目录下的xml文件进行解析,同时保存platform.xml文件至最后单独处理,我们再次进入readPermissionsFromXml方法。

//该方法源码去除了大量的条件判断语句,方便代码阅读private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {    //......    permReader = new FileReader(permFile);    //......    while (true) {                //......                if ("group".equals(name) && !onlyFeatures) {                    String gidStr = parser.getAttributeValue(null, "gid");                    if (gidStr != null) {                        int gid = android.os.Process.getGidForName(gidStr);                        mGlobalGids = appendInt(mGlobalGids, gid);                    }                    continue;                } else if ("permission".equals(name) && !onlyFeatures) {                    String perm = parser.getAttributeValue(null, "name");                    //这里再次调用readPermission方法来进行处理保存                    readPermission(parser, perm);                } else if ("assign-permission".equals(name) && !onlyFeatures) {                    int uid = Process.getUidForName(uidStr);                    ArraySet<String> perms = mSystemPermissions.get(uid);                    if (perms == null) {                        perms = new ArraySet<String>();                        mSystemPermissions.put(uid, perms);                    }                } else if ("library".equals(name) && !onlyFeatures) {                    String lname = parser.getAttributeValue(null, "name");                    String lfile = parser.getAttributeValue(null, "file");                    mSharedLibraries.put(lname, lfile);                    continue;                } else if ("feature".equals(name)) {                    String fname = parser.getAttributeValue(null, "name");                    fi.name = fname;                    mAvailableFeatures.put(fname, fi);                    continue;                } else if ("unavailable-feature".equals(name)) {                    String fname = parser.getAttributeValue(null, "name");                    mUnavailableFeatures.add(fname);                    continue;                } else if ("allow-in-power-save".equals(name)&& !onlyFeatures) {                    String pkgname = parser.getAttributeValue(null, "package");                    mAllowInPowerSave.add(pkgname);                    continue;                } else if ("fixed-ime-app".equals(name) && !onlyFeatures) {                    String pkgname = parser.getAttributeValue(null, "package");                    mFixedImeApps.add(pkgname);                    continue;      }}

从以上源码可知,该方法主要是对xml文件进行标签解读,当解读到不同的标签时对其值进行保存处理,我们来看一下其保存不同值的成员变量。

// Group-ids that are given to all packages as read from etc/permissions/*.xml.    int[] mGlobalGids;    // These are the built-in uid -> permission mappings that were read from the    // system configuration files.    final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();    // These are the built-in shared libraries that were read from the    // system configuration files.  Keys are the library names; strings are the    // paths to the libraries.    final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();    // These are the features this devices supports that were read from the    // system configuration files.    final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();    // These are the features which this device doesn't support; the OEM    // partition uses these to opt-out of features from the system image.    final ArraySet<String> mUnavailableFeatures = new ArraySet<>();    // These are the permission -> gid mappings that were read from the    // system configuration files.    final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();    // These are the packages that are white-listed to be able to run in the    // background while in power save mode, as read from the configuration files.    final ArraySet<String> mAllowInPowerSave = new ArraySet<>();    // These are the app package names that should not allow IME switching.    final ArraySet<String> mFixedImeApps = new ArraySet<>();

以上就是SystemConfig中的主要成员变量,通过解析不同路径下的xml文件,然后保存到各个变量中后面可以直接拿来使用,这里我们在来简单的看一下platform.xml文件。

<?xml version="1.0" encoding="utf-8"?><permissions>    <!--......-->    <permission name="android.permission.BLUETOOTH_ADMIN" >        <group gid="net_bt_admin" />    </permission>    <permission name="android.permission.BLUETOOTH" >        <group gid="net_bt" />    </permission>    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />    <library name="android.test.runner"            file="/system/framework/android.test.runner.jar" />    <library name="javax.obex"            file="/system/framework/javax.obex.jar"/>    <allow-in-power-save package="com.android.providers.downloads" />    <!--......--></permissions>

我们来看一下SystemConfig的数据结构关系图。

SystemConfig数据结构图

接下来我们继续往下分析。

    //(1)创建一个ServiceThread对象,其实就是一个带消息循环处理的线程,用于程序的安装与卸载    mHandlerThread = new ServiceThread(TAG,                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);    mHandlerThread.start();    mHandler = new PackageHandler(mHandlerThread.getLooper());    //(2)将该Handler添加到Watchdog中进行监听    Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);    //(3)创建多个文件目录,用于后期apk扫描    //data目录    File dataDir = Environment.getDataDirectory();    mAppDataDir = new File(dataDir, "data");    mAppInstallDir = new File(dataDir, "app");    mAppLib32InstallDir = new File(dataDir, "app-lib");    mAsecInternalPath = new File(dataDir, "app-asec").getPath();    mUserAppDataDir = new File(dataDir, "user");    mDrmAppPrivateInstallDir = new File(dataDir, "app-private");    mPreloadInstallDir = new File(Environment.getRootDirectory(), "preloadapp");    mDeleteRecord = new File(mAppInstallDir, ".delrecord");    sUserManager = new UserManagerService(context, this,                    mInstallLock, mPackages);    //(4)调用Settings.readLPw方法    mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),                    mSdkVersion, mOnlyCore);

这段代码所进行的操作还是蛮多的,我们一步一步分析。

(1)首先创建了一个ServiceThread对象,以便用于程序的按照和卸载,这个我们后面会单独分析,再次就不详细阐述了。

(2)将处理应用程序安装和卸载的Handler添加到Watchdog监听器中,以监听其是否发生死锁等现象,如果大家对Watchdog不是很熟悉的,可以先看一下我的另外一篇博客SystemServer启动流程之WatchDog分析(四)。

(3)此处会创建多个data/目录下的文件,主要用于后面的apk扫描,这个我们下篇博客将详细阐述。

(4)此处会调用Settings类的readLPw方法,我们进入该方法。

boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,boolean onlyCore) {    FileInputStream str = null;        //首先判断packages-backup.xml文件是否存在,如果存在就删除packages.xml文件        if (mBackupSettingsFilename.exists()) {            try {                str = new FileInputStream(mBackupSettingsFilename);                if (mSettingsFilename.exists()) {                    // If both the backup and settings file exist, we                    // ignore the settings since it might have been                    // corrupted.                    mSettingsFilename.delete();                }            } catch (java.io.IOException e) {            }        }    if (str == null) {        //如果不存在packages-backup.xml文件就加载packages.xml文件        if (!mSettingsFilename.exists()) {            mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;            mFingerprint = Build.FINGERPRINT;                 return false;            }            str = new FileInputStream(mSettingsFilename);        }        XmlPullParser parser = Xml.newPullParser();        parser.setInput(str, null);    //接下来主要就对packages.xml文件进行xml解析    //......}

这个方法主要是对packages.xml文件进行解析,在文章的开头Settings构造器中创建的这些文件。

好啦,到这里PKMS构造器的第一阶段就分析完毕啦,其主要工作就是扫描并解析XML文件,下一篇文章我们将分析其构造器的第二、三阶段PackageManagerService源码分析之第二、三阶段(三)。

0 0