PKMS概述
PKMS和AMS一样是Android系统的核心服务,它主要负责系统中Package的管理,应用程序的安装、卸载、信息查询等工作。PKMS也是由system_server调用PKMS的main函数启动的:
// Start the package manager. Slog.i(TAG, "Package Manager"); mPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
PKMS的main函数代码如下:
1 public static final PackageManagerService main(Context context, Installer installer, 2 boolean factoryTest, boolean onlyCore) { 3 //调用PKMS的构造函数,factoryTest和onlyCore都是false 4 PackageManagerService m = new PackageManagerService(context, installer, 5 factoryTest, onlyCore); 6 //向ServiceManager中注册PKMS 7 ServiceManager.addService("package", m); 8 return m; 9 }
main函数很简单,但是执行时间却很长,主要原因是PKMS在其构造函数中做了很多“体力活”,这也是Android启动速度慢的主要原因之一。其构造函数的主要功能是:
扫描Android系统中几个目标文件夹中的APK,从而建立合适的数据结构以管理诸如Package信息、四大组件信息、权限信息等各种信息。PKMS通过解析APK包中的AndroidManifest.xml文件,并根据其中声明的Activity标签来创建与此对应的对象并加以保管。
PKMS的工作流程相对简单,复杂的是其中用于保存各种信息的数据结构和它们之间的关系,以及影响最终结果的策略控制(如onlycore变量,用于判断是否只扫描系统目录)。
一:PKMS构造函数扫描文件夹之前的准备工作
1 //mSdkVersion即为系统编译的SDK版本 2 if (mSdkVersion <= 0) { 3 Slog.w(TAG, "**** ro.build.version.sdk not set!"); 4 } 5 6 mContext = context; 7 mFactoryTest = factoryTest;//是否运行在工厂模式下 8 mOnlyCore = onlyCore;//false为普通模式 9 //如果此系统是eng版,则扫描Package之后,不对package做dex优化 10 mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); 11 //用于存储与显示屏相关的一些属性,例如屏幕的宽、高、分辨率信息 12 mMetrics = new DisplayMetrics(); 13 mSettings = new Settings(context); 14 mSettings.addSharedUserLPw("android.uid.system", //字符串 15 Process.SYSTEM_UID,//系统进程使用的用户id,为1000 16 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED//标志着系统Package 17 ); 18 mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,//1001 19 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); 20 mSettings.addSharedUserLPw("android.uid.log", LOG_UID,//1007 21 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); 22 mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,//1025 23 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); 24 mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,//2000 25 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); 26 mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,//2000 27 ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
刚进入构造函数,就会遇到第一个较为复杂的数据结构Setting及它的addSharedUserLPw函数,来看看addSharedUserLPw函数的代码:
1 SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) { 2 SharedUserSetting s = mSharedUsers.get(name);//mSharedUsers是一个HashMap,key为字符串,值为SharedUserSetting对象 3 if (s != null) { 4 if (s.userId == uid) { 5 return s; 6 } 7 PackageManagerService.reportSettingsProblem(Log.ERROR, 8 "Adding duplicate shared user, keeping first: " + name); 9 return null; 10 } 11 //把key为name,值为uid的SharedUserSetting对象添加到mSharedUsers中保存 12 s = new SharedUserSetting(name, pkgFlags); 13 s.userId = uid; 14 if (addUserIdLPw(uid, s, name)) { 15 mSharedUsers.put(name, s); 16 return s; 17 } 18 return null; 19 }
这一步有什么用呢?我们都看过system应用的Manifest文件标签头,举个例子:
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="com.android.settings" 3 coreApp="true" 4 android:sharedUserId="android.uid.system">
在xml中,声明了一个名为android:sharedUserId的属性,其值为“android.uid.system”,它有两个作用:
1.两个或多个声明了同一种android:sharedUserId的APK可以共享彼此的数据,并且可运行在同一进程中。
2.通过声明特定的android:sharedUserId,该APK所在进程将被赋予指定的UID,例如本例中setting声明了system的uid,运行setting的进程就可享有system用户所在对应的权限了。
接下来就是xml文件解析的环节啦
1 String separateProcesses = SystemProperties.get("debug.separate_processes"); 2 if (separateProcesses != null && separateProcesses.length() > 0) { 3 ... 4 } else { 5 mDefParseFlags = 0; 6 mSeparateProcesses = null; 7 } 8 9 mInstaller = installer; 10 11 getDefaultDisplayMetrics(context, mMetrics); 12 13 SystemConfig systemConfig = SystemConfig.getInstance(); 14 mGlobalGids = systemConfig.getGlobalGids(); 15 mSystemPermissions = systemConfig.getSystemPermissions(); 16 mAvailableFeatures = systemConfig.getAvailableFeatures(); 17 18 synchronized (mInstallLock) { 19 // writer 20 synchronized (mPackages) { 21 //创建一个ThreadHander,实际就是创建了一个带消息循环处理的线程,该线程的工作就是:程序的安装和卸载等 22 mHandlerThread = new ServiceThread(TAG, 23 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); 24 mHandlerThread.start(); 25 mHandler = new PackageHandler(mHandlerThread.getLooper()); 26 Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); 27 //创建file对象指向用户的package安装包目录 28 File dataDir = Environment.getDataDirectory(); 29 mAppDataDir = new File(dataDir, "data"); 30 mAppInstallDir = new File(dataDir, "app"); 31 mAppLib32InstallDir = new File(dataDir, "app-lib"); 32 mAsecInternalPath = new File(dataDir, "app-asec").getPath(); 33 mUserAppDataDir = new File(dataDir, "user"); 34 mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); 35 //创建一个UserManager,与多用户相关,不同的用户的Package目录是不一样的 36 sUserManager = new UserManagerService(context, this, 37 mInstallLock, mPackages); 38 39 // Propagate permission configuration in to package manager.读取权限 40 ArrayMap<String, SystemConfig.PermissionEntry> permConfig 41 = systemConfig.getPermissions(); 42 for (int i=0; i<permConfig.size(); i++) { 43 SystemConfig.PermissionEntry perm = permConfig.valueAt(i); 44 BasePermission bp = mSettings.mPermissions.get(perm.name); 45 if (bp == null) { 46 bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN); 47 mSettings.mPermissions.put(perm.name, bp); 48 } 49 if (perm.gids != null) { 50 bp.gids = appendInts(bp.gids, perm.gids); 51 } 52 } 53 54 ... 55 56 mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false), 57 mSdkVersion, mOnlyCore); 58 ... 59 60 long startTime = SystemClock.uptimeMillis();
总结就是PKMS的扫描准备工作是,扫描并解析XML 文件,并将其中的信息保存到特定的数据结构中。
二:构造函数之扫描系统Package
PKMS的构造函数第二阶段工作就是扫描系统中APK,由于需要逐个扫描文件,因此手机上装的程序越多,PKMS的工作量就越大,启动就越慢。
1.系统库的dex优化
1 // The list of "shared libraries" we have at this point is优化需要优化的jar包 2 if (dexoptRequired == DexFile.DEXOPT_NEEDED) { 3 mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet); 4 } else { 5 mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet); 6 }
1 // Gross hack for now: we know this file doesn't contain any 2 // code, so don't dexopt it to avoid the resulting log spew.framework-res.apk定义了系统常用的资源,还有几个重要的Activity,如长按Power键后弹出的对话框 3 alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk"); 4 5 // Gross hack for now: we know this file is only part of 6 // the boot class path for art, so don't dexopt it to 7 // avoid the resulting log spew.core-libart.jar跟ART核心有关 8 alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
2.扫描系统Package
1 // Collected privileged system packages. 2 final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); 3 scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM 4 | PackageParser.PARSE_IS_SYSTEM_DIR 5 | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0); 6 7 // Collect ordinary system packages. 8 final File systemAppDir = new File(Environment.getRootDirectory(), "app"); 9 scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM 10 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); 11 12 // Collect all vendor packages. 13 File vendorAppDir = new File("/vendor/app"); 14 try { 15 vendorAppDir = vendorAppDir.getCanonicalFile(); 16 } catch (IOException e) { 17 // failed to look up canonical path, continue with original one 18 } 19 scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM 20 | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
我们可以看到PKMS 将扫描以下几个目录:
priv-app:该目录下都是默认的系统应用,settings,contact等
vendor/app:该目录下都是厂商提供的APK文件
app:系统app
这一步最重要的函数是scanDirLI(),该函数会调用PackageParser对APK文件的AndroidManifest.xml文件进行解析。PackageParser完成了从物理文件到对应数据结构的转换。我们来看看该数据结构的组成:
Package ------- +applicationInfo:ApplicationInfo +permission: ArrayList<Permission> +permissionGroups: ArrayList<PermissionGroup> +activities: ArrayList<Activity> +receivers: ArrayList<BroadcastReceiver> +providers: ArrayList<Provider> +requestedPermissions: ArrayList<String> +services: ArrayList<Service> +instrumentation: ArrayList<Instrumentation> +usesLibraries: ArrayList<String>
我们可以看到Android的四大组件,Permission,应用的Lib,applicationinfo等信息都已解析出来了。
在PackageParser扫描完一个APK之后,系统根据该APK的manifest文件创建了一个完整的Package对象,下一步就是将该Package加入到系统中,此时调用的函数是另一个scanPackageLI,主要代码如下:
1 if (pkg.packageName.equals("android")) { 2 synchronized (mPackages) { 3 if (mAndroidApplication != null) { 4 ... 5 } 6 7 // Set up information for our fall-back user intent resolution activity. 8 mPlatformPackage = pkg; 9 pkg.mVersionCode = mSdkVersion; 10 mAndroidApplication = pkg.applicationInfo; 11 12 if (!mResolverReplaced) { 13 mResolveActivity.applicationInfo = mAndroidApplication; 14 mResolveActivity.name = ResolverActivity.class.getName(); 15 mResolveActivity.packageName = mAndroidApplication.packageName; 16 mResolveActivity.processName = "system:ui"; 17 mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; 18 mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER; 19 mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; 20 mResolveActivity.theme = R.style.Theme_Holo_Dialog_Alert; 21 mResolveActivity.exported = true; 22 mResolveActivity.enabled = true; 23 mResolveInfo.activityInfo = mResolveActivity; 24 mResolveInfo.priority = 0; 25 mResolveInfo.preferredOrder = 0; 26 mResolveInfo.match = 0; 27 mResolveComponentName = new ComponentName( 28 mAndroidApplication.packageName, mResolveActivity.name); 29 } 30 } 31 }
这边对Packagename为“android”的Package进行了单独的处理。和该Package对应的APK就是framework-res.apk,这个apk除了系统资源之外还负责几个Activity:
1 ChooserActivity:当多个Activity符合某个Intent时,系统弹出此Activity,由用户选择合适的应用来处理。 2 RingtonePickerActivity:铃声选择 3 ShutdownActivity:关机前弹出的选择对话框
接下来就是对所有系统应用的处理,该段代码过于庞大,就不贴了,主要是对Package的私有财进行公有化改造。
三、扫描非系统Package
和系统应用扫描基本一致,不详述,主要是扫描/data/app 和 /data/app-private的目录Package。
四、扫尾工作
扫尾工作主要是将扫描信息再集中整理一次,比如将有些信息保存到文件中:
1 mSettings.mInternalSdkPlatform = mSdkVersion; 2 //汇总并更新和Permission相关的信息 3 updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL 4 | (regrantPermissions 5 ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL) 6 : 0)); 7 8 // 将信息写到Package.xml/ package.list 及 package-stopped.xml 文件中 9 mSettings.writeLPr(); 10 11 EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, 12 SystemClock.uptimeMillis()); 13 14 mRequiredVerifierPackage = getRequiredVerifierLPr();