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

##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 none
com.slightech.slife.zte 10081 0 /data/data/com.slightech.slife.zte default 3003
com.mediatek.lbs.em2.ui 10056 0 /data/data/com.mediatek.lbs.em2.ui platform 3003
com.android.wallpaper 10054 0 /data/data/com.android.wallpaper default none
com.android.vpndialogs 10026 0 /data/data/com.android.vpndialogs platform none
com.android.email 10043 0 /data/data/com.android.email platform 1023,1015,3003
com.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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪舞飞影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值