Android Framework之PMS篇
84478543
8年前
<h2>1. PMS运行时的一些规则</h2> <p>PMS相关的目录与文件,以及PMS操作它们的规则。</p> <h2>/data/app</h2> <p>用户安装的第三方apk,以及app所依赖的native library都放在这里。在Android 6.0时,此目录增加了一个文件夹“oat”,用来存放此app第一次运行时由dex2oat生成的此app的oat文件。在之前的Android版本中,用户安装的app的oat文件存储在/data/dalvik-cache中。6.0时,此目录只存放系统自带的apk的oat文件。</p> <h2>/data/data</h2> <p>是系统当前用户安装的所有app的沙箱目录。该目录实际上是data/user/用户ID目录的引用。随着用户的切换,”/data/data/“也会映射为不同的用户。</p> <h2>PMS的配置文件</h2> <p>PMS会产生一些配置文件,用来记录系统当前安装的app,这些文件存储在:data/system/users/userId/</p> <ol> <li>packages.xml------->记录系统中所有已经安装的应用信息,包括基本信息,签名和权限。</li> </ol> <p><img src="https://simg.open-open.com/show/812ad676d6cbe189f7e21fcaf7b25e16.png"></p> <p>packages内容之Package标签.png</p> <p>Package标签</p> <ul> <li>Name:程序包名称</li> <li>codePath:程序包所在路径</li> <li>nativeLibraryPath:该程序所使用的native库文件路径。</li> <li>primaryCpuAbi:apk支持的abi类型(优先)</li> <li>userId:应用程序对应的Linux用户Id</li> <li>sharedUserId:若在androidManifest.xml中定义了sharedUserId,则此处使用它而非userId。</li> <li> <p>Sigs:签名信息。一个应用程序只能有一个签名。</p> </li> <li> <p>Perms:一个应用程序所申请的权限列表。androidManifest.xml中每使用一个<uses-permission>,则packages.xml中<perms>标签就会增加一项。</p> </li> </ul> <p><img src="https://simg.open-open.com/show/91bcfba8f7436e7ceeb7d8803e3b7cc1.png"></p> <p>packages内容之share-user标签.png</p> <p>Shared-user标签</p> <ul> <li> <p>定义了共享用户id对应的签名和权限</p> <p>当操作该文件的时候,总会创建备份文件packages-backup.xml。当正常操作完成的时候,会删除该备份。否则,当PMS下次启动的时候,一旦发现有backup文件,就会优先解析备份文件。当一个app被升级覆盖安装时,会使用<updated-packages>表示,当新旧版本app的包名发生改变时,会使用<renamed-package>记录。</p> </li> </ul> <p>2.packages-stoped.xml------->记录系统中被强制停止运行的app的信息。它同样可能存在一个packages-stoped-backup.xml的备份文件,当备份文件存在的时候,优先使用备份文件。因为原文件可能已经损坏了。</p> <p>3.packages.list------->保存应用的数据目录和uid信息。</p> <p>如:</p> <pre> <code class="language-java">com.qihoo.appstore 10067 0 /data/data/com.qihoo.appstore default 3002,3003,3001</code></pre> <p>第一列为app的包名,第二列为10067为此app的用户ID,第三列中的0,表示此app所属的系统用户ID,第四列为此app的数据文件目录。</p> <p>default为seinfo,SEAndroid相关机制会使用该字段。</p> <p>最后一列记录了该app所在的权限组,也就是说拥有哪些权限。</p> <h2>系统硬件特性和权限</h2> <p>PMS启动的时候会从/system/etc/permissions/中读取当前Android设备的硬件特性和设定的相关权限。</p> <p>xxx.xml 包含很多feature,用来描述手机应该支持的硬件特性,如支持camera,蓝牙等</p> <p>Platform.xml 建立上层permission同底层uid/gid的关系</p> <p>正是因为解析了该目录中的文件,所以可以通过pm命令查看features和permissions等信息。</p> <h2>多用户管理</h2> <p>PMS还要对多用户进行管理。因为安装apk的时候,可以PMS可以指定给某个特定的用户,也可以安装给全部的用户。</p> <h2>权限动态管理</h2> <p>Android M 中 允许动态授权和取消App中申请的权限。修改后会相应的写入runtime-permissions.xml</p> <h2>2. PMS的机制与实现</h2> <h2>2.1 PMS体系结构</h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/7e8ab4329c1fab9a0f260b4147ac32d7.jpg"></p> <p>PMS体系结构图</p> <h2>2.2 PMS相关代码路径</h2> <pre> <code class="language-java">frameworks/base/core/java/android/content/pm/PackageManager.java frameworks/base/core/java/android/content/pm/PackageParser.java frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java frameworks/base/services/core/java/com/android/server/pm/Settings.java frameworks/base/services/core/java/com/android/server/pm/Installer.java //Runtime-Permission相关 frameworks/base/services/core/java/com/android/server/pm/ DefaultPermissionGrantPolicy.java frameworks/base/core/java/android/os/FileObserver.java frameworks/base/core/java/android/app/ApplicationPackageManager.java frameworks/base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java frameworks/native/cmds/installd/commands.c frameworks/native/cmds/installd/installd.c frameworks/native/cmds/installd/utils.c</code></pre> <h2>2.3 PMS的类关系图</h2> <p><img src="https://simg.open-open.com/show/50d956f9128c348a80c165ee698dee94.jpg"></p> <p>PMS类关系图</p> <h2>2.4PMS的启动流程</h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/885d887dbd71f2395d43d0051e03ab1b.jpg"></p> <p>PMS的启动流程图</p> <h3>2.4.1 获取系统默认配置</h3> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { …… mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); long dexOptLRUThresholdInMinutes; if (mLazyDexOpt) { dexOptLRUThresholdInMinutes = 30; // only last 30 minutes of apps for eng builds. } else { dexOptLRUThresholdInMinutes = 7 * 24 * 60; // apps used in the 7 days for users. } mDexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000; mMetrics = new DisplayMetrics(); String separateProcesses = SystemProperties.get("debug.separate_processes"); ……</code></pre> <ol> <li>PMS启动阶段主要获取ro.build.type和debug.separate_processes两个值。</li> <li>ro.build.type:用于标记版本是eng还是usr。如果为eng,则mLazyDexOpt为true。通过mLazyDexOpt来设定dexOptLRUThresholdInMinutes的值(该值在filterRecentlyUsedApps方法中用于过滤最近使用的apps,如果大于mDexOptLRUThresholdInMills则不进行dexopt操作。usr版本为7 days,eng版本为30 mins) 。</li> <li>debug.separate_processes:用于标记是否在独立进程中运行某个程序。根据该值设置PMS.mDefParseFlags和PMS.mSeparateProcesses两个全局变量,后续scanDirLi扫描并安装APK的时候用到。</li> <li>获取系统默认显示参数<br> 通过new DisplayMetrics()获取系统默认显示参数,存入PMS.mMetrics变量中,主要用于匹配APK中的asset和resource。 <h3>2.4.2 创建并初始化Settings对象</h3> </li> </ol> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { …… 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);</code></pre> <p>Setting初始化阶段包括以下两部分工作:</p> <p>1) 调用构造函数初始化new Settings(context)</p> <p>2) 调用addSharedUserLPw方法添加6个默认共享用户ID</p> <p>1.调用构造函数初始化</p> <pre> <code class="language-java">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");//记录系统中所有已安装的apk信息,安装和卸载时会更新 mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); mPackageListFilename = new File(mSystemDir, "packages.list");//记录系统中所有已安装APK的简略信息 FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID); mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); }</code></pre> <h3>2.4.3 创建dexopt优化器对象</h3> <pre> <code class="language-java">mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(this); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper());</code></pre> <p>创建PackageDexOptimizer对象,该类主要用来执行ART中的patchoat命令,用来对oat文件的偏移值进行随机化。该类是Android M 中才有的。创建监听权限更改的监听者。因为Android M中允许动态修改App权限。</p> <h3>2.4.4 解析系统Permissions和Feature信息</h3> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { SystemConfig systemConfig = SystemConfig.getInstance(); mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); ……</code></pre> <p>SystemConfig负责解析系统Permissions和Feature信息,路经为system/etc/sysconfig和system/etc/permissions。其流程如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e4b3cd7d50228d116d3a9e4a8843cc94.jpg"></p> <p>SystemConfig解析Permissions和Feature流程</p> <p>解析完成后,Pemissions文件中各标签和SystemConfig及PMS变量的对应关系如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ed1528f1da532b8351e3b2abe4c0207c.png"></p> <p>Paste_Image.png</p> <h3>2.4.5 启动PackageHandler</h3> <p>进入mPackages同步块后首先启动PackageHandler</p> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { synchronized (mInstallLock) { // writer synchronized (mPackages) { mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); ……</code></pre> <p>PackageHandler是PMS的内部类,用于接收并处理其他模块程序发送的消息,这些消息可以来自adb或者其他应用程序</p> <p>在PackageHandler的handleMessage方法中调用doHandleMessage方法处理消息,代码如下:</p> <pre> <code class="language-java">class PackageHandler extends Handler { void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: case MCS_BOUND: case MCS_CHECK: case MCS_RECONNECT: case MCS_UNBIND: case MCS_GIVE_UP: case SEND_PENDING_BROADCAST: case START_CLEANING_PACKAGE: case POST_INSTALL: case UPDATED_MEDIA_STATUS: case WRITE_SETTINGS: case WRITE_PACKAGE_RESTRICTIONS: case CHECK_PENDING_VERIFICATION: case PACKAGE_VERIFIED: ……</code></pre> <p>以上消息主要用于APK的复制和更名操作,但这些操作并不是在PackageHandler中完成的。而是在PackageHandler的消息处理函数中会通过connectToService方法绑定到MCS服务,即DefaultContainerService。</p> <h3>2.4.6 创建data目录并初始化UserManagerService</h3> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { 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-asec mUserAppDataDir = new File(dataDir, "user");// data/user mDrmAppPrivateInstallDir = new File(dataDir, "app-private");// data/app-private sUserManager = new UserManagerService(context, this,mInstallLock, mPackages); ……</code></pre> <p>由以上代码可知,首先初始化成员变量来存放数据目录信息,然后创建UserManagerService。</p> <p>在UserManagerService的构造函数中,创建data/system/users、data/system/users/0目录和data/system/users/userlist.xml文件,然后调用readUserListLocked()方法解析userlist.xml</p> <h3>2.4.7 解析packages文件</h3> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { synchronized (mInstallLock) { // writer synchronized (mPackages) { mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),mSdkVersion, mOnlyCore);</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/64be6694abc24837c69b9f16dacd207f.jpg"></p> <p>readLPw流程</p> <h3>2.4.8 Dexopt优化判定</h3> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { synchronized (mInstallLock) { // writer synchronized (mPackages) { final ArraySet<String>alreadyDexOpted = new ArraySet<String>(); final String bootClassPath = System.getenv("BOOTCLASSPATH"); /*BOOTCLASSPATH是Android Linux的一个环境变量,可以在adb shell下用$BOOTCLASSPATH看到。BOOTCLASSPATH即系统核心JAR包的路径,直接加入alreadyDexOpted列表,表示不需要进行dexopt操作 /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:/system/framework/mediatek-common.jar:/system/framework/mediatek-framework.jar:/system/framework/mediatek-telephony-common.jar*/ final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); /*/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar*/ if (bootClassPath != null) { String[] bootClassPathElements = splitString(bootClassPath, ':'); for (String element : bootClassPathElements) { alreadyDexOpted.add(element); } } else { Slog.w(TAG, "No BOOTCLASSPATH found!"); } if (systemServerClassPath != null) { ……//同bootClassPath处理流程 } /*该段代码主要是把BOOTCLASSPATH和SYSTEMSERVERCLASSPATH里面的文件添加到alreadyDexOpted这个HashSet中,因为它们在zygote启动时已经进过Dex优化了。*/ final List<String> allInstructionSets = getAllInstructionSets();//arm arm64 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));//arm arm64 /** * Ensure all external libraries have had dexopt run on them. */ if (mSharedLibraries.size() > 0) { for (String dexCodeInstructionSet : dexCodeInstructionSets) { for (SharedLibraryEntry libEntry : mSharedLibraries.values()) { final String lib = libEntry.path; try { int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false); if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { alreadyDexOpted.add(lib); mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded); } } } } File frameworkDir = new File(Environment.getRootDirectory(), "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"); alreadyDexOpted.add(frameworkDir.getPath() + "/mediatek-res/mediatek-res.apk"); File customFrameworkDir = new File("/custom/framework"); alreadyDexOpted.add(customFrameworkDir.getPath() + "/framework-res.apk"); alreadyDexOpted.add(customFrameworkDir.getPath() + "/mediatek-res.apk"); alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar"); String[] frameworkFiles = frameworkDir.list(); 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(); // Skip the file if it is not a type we want to dexopt. if (!path.endsWith(".apk") && !path.endsWith(".jar")) { continue; } //同mSharedLibraries处理流程 ……</code></pre> <p>system/framework/framework-res.apk 、mediatek-res.apk中没有代码,不需要进行dexopt操作,直接存入alreadyDexOpted</p> <p>如果system/framework、custom/framework、system/plugin、custom/plugin目录下的APK和JAR包需要优化,调用dexopt流程进行优化</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/76f3e981e9ac2bfe658a765abe3d11ae.jpg"></p> <p>Dexopt流程</p> <h3>2.4.8 Dexopt优化判定</h3> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { synchronized (mInstallLock) { // writer synchronized (mPackages) { final ArraySet<String>alreadyDexOpted = new ArraySet<String>(); final String bootClassPath = System.getenv("BOOTCLASSPATH"); /*BOOTCLASSPATH是Android Linux的一个环境变量,可以在adb shell下用$BOOTCLASSPATH看到。BOOTCLASSPATH即系统核心JAR包的路径,直接加入alreadyDexOpted列表,表示不需要进行dexopt操作 /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:/system/framework/mediatek-common.jar:/system/framework/mediatek-framework.jar:/system/framework/mediatek-telephony-common.jar*/ final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); /*/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar*/ if (bootClassPath != null) { String[] bootClassPathElements = splitString(bootClassPath, ':'); for (String element : bootClassPathElements) { alreadyDexOpted.add(element); } } else { Slog.w(TAG, "No BOOTCLASSPATH found!"); } if (systemServerClassPath != null) { ……//同bootClassPath处理流程 } /*该段代码主要是把BOOTCLASSPATH和SYSTEMSERVERCLASSPATH里面的文件添加到alreadyDexOpted这个HashSet中,因为它们在zygote启动时已经进过Dex优化了。*/ final List<String> allInstructionSets = getAllInstructionSets();//arm arm64 final String[] dexCodeInstructionSets = getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));//arm arm64 /** * Ensure all external libraries have had dexopt run on them. */ if (mSharedLibraries.size() > 0) { for (String dexCodeInstructionSet : dexCodeInstructionSets) { for (SharedLibraryEntry libEntry : mSharedLibraries.values()) { final String lib = libEntry.path; try { int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false); if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { alreadyDexOpted.add(lib); mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded); } } } } File frameworkDir = new File(Environment.getRootDirectory(), "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"); alreadyDexOpted.add(frameworkDir.getPath() + "/mediatek-res/mediatek-res.apk"); File customFrameworkDir = new File("/custom/framework"); alreadyDexOpted.add(customFrameworkDir.getPath() + "/framework-res.apk"); alreadyDexOpted.add(customFrameworkDir.getPath() + "/mediatek-res.apk"); alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar"); String[] frameworkFiles = frameworkDir.list(); 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(); // Skip the file if it is not a type we want to dexopt. if (!path.endsWith(".apk") && !path.endsWith(".jar")) { continue; } //同mSharedLibraries处理流程 ……</code></pre> <p>system/framework/framework-res.apk 、mediatek-res.apk中没有代码,不需要进行dexopt操作,直接存入alreadyDexOpted</p> <p>如果system/framework、custom/framework、system/plugin、custom/plugin目录下的APK和JAR包需要优化,调用dexopt流程进行优化</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/76f3e981e9ac2bfe658a765abe3d11ae.jpg"></p> <p>Dexopt流程</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ae9a6a54b2c5b507730e1940da9f803d.png"></p> <p>dexopt优化后生成内容</p> <p>如果是升级系统时,则进行如下处理,如果没有进行系统升级,则忽略这段代码。</p> <pre> <code class="language-java">public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { synchronized (mInstallLock) { // writer synchronized (mPackages) { final VersionInfo ver = mSettings.getInternalVersion(); mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);//根据fingerprint判断是否是OTA升级 // when upgrading from pre-M, promote system app permissions from install to runtime mPromoteSystemApps = mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1; // save off the names of pre-existing system packages prior to scanning; we don't // want to automatically grant runtime permissions for new system apps if (mPromoteSystemApps) { Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator(); while (pkgSettingIter.hasNext()) { PackageSetting ps = pkgSettingIter.next(); if (isSystemApp(ps)) { mExistingSystemPackages.add(ps.name); } } }</code></pre> <h3>2.4.9 调用scanDirLI方法扫描并安装APK包</h3> <pre> <code class="language-java">private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { …… PackageSetting ps = null; PackageSetting updatedPkg; // reader synchronized (mPackages) { String oldName = mSettings.mRenamedPackages.get(pkg.packageName); if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) { ps = mSettings.peekPackageLPr(oldName); } if (ps == null) { ps = mSettings.peekPackageLPr(pkg.packageName); } /*这里主要是处理应用升级后包名不一致的情况,当设备第一次开机时,不存在这样的情况。其他情况下,开机会解析packages.xml,当前后有apk的包名发生变化时,该app在packages.xml中会以标签标记。而且还会把这些包名更改了的信息计入 PMS的mSettings变量的ArrayMap 类型的变量mRenamedPackages中,key是newname.*/ // 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) { /*处理系统更新后,检查是否对系统app有影响。即是否将系统app更新为更高的新版本了。是的话,要处理。*/ } private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { …… /*A new system app appeared, but we already had a non-system one of thesame 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) { deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false); ps = null; } else { /*If the newly-added system app is an older version than thealready installed version, hide it. It will be scanned later and re-added like an update. */ if (pkg.mVersionCode <= ps.versionCode) { shouldHideSystemApp = true; } 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. */ InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps)); synchronized (mInstallLock) { args.cleanUpResourcesLI(); } } } } /*此处主要处理系统升级后,系统多出一些system app与已安装的非系统包重名的情况*/ …… if (shouldHideSystemApp) { synchronized (mPackages) { mSettings.disableSystemPackageLPw(pkg.packageName); } } /*如果扫描的系统app需要被隐藏,那么通过mSettings.disableSystemPackageLPw方法将其信息记录在mSettings的mDisabledSysPackages中。*/</code></pre> <h2>解析一个app有多个apk的情况</h2> <p>Split APK是Google为解决65536上限,以及APK安装包越来越大等问题,在Android L中引入的机制。</p> <p>Split APK可以将一个庞大的APK,按屏幕密度,ABI等形式拆分成多个独立的APK,在应用程序更新时,不必下载整个APK,只需单独下载某个模块即可安装更新。</p> <p>Split APK将原来一个APK中多个模块共享同一份资源的模型分离成多个APK使用各自的资源,并且可以继承Base APK中的资源,多个APK有相同的data,cache目录,多个dex文件,相同的进程,在Settings.apk中只显示一个APK,并且使用相同的包名。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/eb87f99e2a865e59672cf540fd44832b.jpg"></p> <p>image011.jpg</p> <p>想了解更多关于Split APK机制,请参考如下文章</p> <p><a href="/misc/goto?guid=4959741011260503589" rel="nofollow,noindex">Android动态部署一:Google原生Split APK浅析</a><img src="https://simg.open-open.com/show/7aed496887db0c49734062866cbd7d3d.jpg"></p> <p>解析app时序图1</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/001372f92df218036a24025e153ace80.jpg"></p> <p>解析app时序图2</p> <h3>2.4.10 扫描用户安装的app</h3> <pre> <code class="language-java">/*处理有升级包的系统应用,也就是执行过OTA升级后,第一次启动时,需要关心的逻辑*/ // Prune any system packages that no longer exist. final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>(); if (!mOnlyCore) { Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator(); while (psit.hasNext()) { PackageSetting ps = psit.next(); /* If this is not a system app, it can't be a disable system app. */ if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { continue;//忽略普通应用 } /* If the package is scanned, it's not erased. */ final PackageParser.Package scannedPkg = mPackages.get(ps.name); if (scannedPkg != null) { // packages.xml中<updated-package>修饰的package会被记录到mSettings中的disable列表中去 // 这说明扫描的系统app是带有升级包的 if (mSettings.isDisabledSystemPackageLPr(ps.name)) { removePackageLI(ps, true);//将其从mPackages中移除 mExpectingBetter.put(ps.name, ps.codePath);// 将其添加到mExpectingBetter,后续处理 } continue; } // 运行到这里说明packages.xml中记录的app,但此时还未扫描到。 if (!mSettings.isDisabledSystemPackageLPr(ps.name)) { // 如果这个app在packages.xml也不属于<updated-package> // 意味着这个应用是残留在packages.xml中的,可能还会剩下沙箱数据,因此也要删掉 psit.remove(); removeDataDirsLI(null, ps.name); } else { // 如果这个app在packages.xml属于<updated-package> // 将其加入possiblyDeletedUpdatedSystemApps final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.codePath == null || !disabledPs.codePath.exists()) { possiblyDeletedUpdatedSystemApps.add(ps.name); } } } } //扫描并删除未成功安装的apk包(针对第三方app) //look for any incomplete package installations ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr(); //clean up list for(int i = 0; i < deletePkgsList.size(); i++) { cleanupInstallFailedPackage(deletePkgsList.get(i)); } // 删除临时文件 deleteTempPackageFiles(); // 把从mSettings中没有关联任何应用的SharedUserSetting对象删掉 mSettings.pruneSharedUsersLPw(); //开始扫描用户安装的app,即data/app和data/priv-app scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0); scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanFlags | SCAN_REQUIRE_KNOWN, 0); 处理possiblyDeletedUpdatedSystemApps。它里面存储的是在packages.xml中被标记为,但是前面又没有扫描到的其apk文件的app。 for (String deletedAppName : possiblyDeletedUpdatedSystemApps) { // 在扫描了用户app目录之后,再次尝试查找是否有这些app PackageParser.Package deletedPkg = mPackages.get(deletedAppName); mSettings.removeDisabledSystemPackageLPw(deletedAppName); String msg; // 依旧没有,那么就删除他们的数据目录 if (deletedPkg == null) { msg = "Updated system package " + deletedAppName + " no longer exists; wiping its data"; removeDataDirsLI(null, deletedAppName); } else { // 找到了,说明是在用户app目录中找到的,那么移除系统权限 msg = "Updated system app + " + deletedAppName + " no longer present; removing system privileges for " + deletedAppName; deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName); deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM; } logCriticalInfo(Log.WARN, msg); } // 处理存放到mExpectingBetter是那些带有升级包的系统应用 for (int i = 0; i < mExpectingBetter.size(); i++) { final String packageName = mExpectingBetter.keyAt(i); if (!mPackages.containsKey(packageName)) { final File scanFile = mExpectingBetter.valueAt(i); logCriticalInfo(Log.WARN, "Expected better " + packageName + " but never showed up; reverting to system"); //确保是在 ///system/priv-app、system/app、vendor/app、oem/app这四个目录中。 final int reparseFlags; if (FileUtils.contains(privilegedAppDir, scanFile)) { reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR | PackageParser.PARSE_IS_PRIVILEGED; } else if (FileUtils.contains(systemAppDir, scanFile)) { reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR; } else if (FileUtils.contains(vendorAppDir, scanFile)) { reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR; } else if (FileUtils.contains(oemAppDir, scanFile)) { reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR; } else { Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile); continue; } // 会将其加入mSettings的mPackages中 mSettings.enableSystemPackageLPw(packageName); /// 重新扫描这些文件 try { scanPackageLI(scanFile, reparseFlags, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse original system package: " + e.getMessage()); } } } } // 清除mExpectingBetter mExpectingBetter.clear();</code></pre> <p>接下来的代码作用是更新所有应用的动态库路径,如果是OTA升级导致前后SDK版本不一致,还要进行权限重新检查,并且删除app oat cache目录。更新数据库版本,调用mSettings.writeLPr更新package.xml、package.list、runtime-permission.xml等文件。</p> <h3>2.4.11 调用mSettings.writeLPr更新package.xml</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/3a4f6c7d858b067fe3118e2efdac5f97.jpg"></p> <p>ddfefff.jpg</p> <p>最后创建mInstallerService对象:</p> <pre> <code class="language-java">mInstallerService = new PackageInstallerService(context, this);</code></pre> <p>PMS构造方法的执行过程就是先读取保存在packages.xml中记录的系统关机前记录所有安装的app信息,保存在mSettings中的mPackages中。</p> <p>然后扫描指定的若干目录中的app,并把信息记录在PMS的mPackages中。最后对两者进行对比,看是否能发现有升级的app,然后进行相关处理,最后在写入packages.xml中。</p> <h2>2.5 PMS类图补充</h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/3233e36d8824816211067c2eed1ade47.jpg"></p> <p>PMS类图补充</p> <h2>2.6 PMS启动过程中使用的核心组件Installer</h2> <p>PMS启动过程中使用了Installer的多个方法。Android APK的安装和卸载主要是由Installer和Installd完成的。</p> <p>Installer是Java层提供的Java API接口,Installd则是init进程启动的Daemon Service。Installer与Installd通过Socket通信,Installer是Socket的Client端,Installd则是Socket的Server端。通过Socket通信,将Installer的API调用转化为Installd中具体命令,这种转化关系通过cmds[]数组配置和映射。Installer和Installd的关系如图所示:</p> <p><img src="https://simg.open-open.com/show/dbd6897b175d02888a40fd34ce622d95.jpg"></p> <p>Installer方法映射图</p> <h2>2.7 Dexopt优化原则</h2> <p>PMS的启动流程执行完后,将会进入到mPackageManagerService.performBootDexOpt()流程,此处PMS会按一定的顺序对app做dexopt,规则流程如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/338be724067e836a8b90ccab261516f9.jpg"></p> <p>Dexopt优化原则</p> <h2>2.8 首次开机Runtime-permission生成机制</h2> <p><img src="https://simg.open-open.com/show/8178efbf201189e0fa7d8b0723a7043a.jpg"></p> <p>Runtime Permission时序图</p> <h2>3. APK的安装过程</h2> <p>当Android存储中的一个apk文件时,实际上是调用了Packageinstaller来完成的。安装过程中具体界面如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a968d4096b6225e1ba8ae31874b01b17.png"></p> <p>apk安装过程界面</p> <p>Packageinstaller内部也是对PMS的调用,安装时,会先调用PMS相关接口,解析APK文件,也就是其AndroidMainifest.xml文件,这样就得到了该app的组件,权限,包名等信息。然后以包名为key,检查该app是否已经安装,安装的话,设置replace的flag:INSTALL_REPLACE_EXISTING。如果是之前没安装过的,那么会弹出一个activity,显示该app有哪些权限,底部有两个Button:”取消”和“安装”。点击”安装”,就开始安装了。如果该app之前安装过了,弹出的Activity中会提示:“你要安装此应用的新版本吗?。。。。”,最后还会罗列出新app相比已经安装在设备上的app的权限有哪些变化,比如新添加了哪些权限等等。底部同样会提供两个Button:”取消”和“安装”。点击”安装”,就开始安装了。</p> <p>当点击”安装”button之后,实际上跳转到PackageInstaller的InstallAppProgress这个activity了。其真正安装开始于</p> <pre> <code class="language-java">pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags, installerPackageName, verificationParams, null);</code></pre> <p>其代码实现:Android6.0/frameworks/base/core/java/android/app/ApplicationPackageManager.java</p> <pre> <code class="language-java">public void installPackageWithVerificationAndEncryption(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { installCommon(packageURI, observer, flags, installerPackageName, verificationParams, encryptionParams); }</code></pre> <p>内部直接又调用了installCommon方法:</p> <pre> <code class="language-java">private void installCommon(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { if (!"file".equals(packageURI.getScheme())) { throw new UnsupportedOperationException("Only file:// URIs are supported"); } if (encryptionParams != null) { throw new UnsupportedOperationException("ContainerEncryptionParams not supported"); } final String originPath = packageURI.getPath(); try { mPM.installPackage(originPath, observer.getBinder(), flags, installerPackageName, verificationParams, null); } catch (RemoteException ignored) { } }</code></pre> <p>做了一系列判断后,接着调用mPM的installPackage方法。mPM就是PMS的一个代理。也就是说这里实际会调用PMS的installPackage方法:</p> <pre> <code class="language-java">public void installPackage(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride) { installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams, packageAbiOverride, UserHandle.getCallingUserId()); }</code></pre> <p>整个安装过程很复杂,大体上可分为三个过程:</p> <ol> <li>权限检查</li> <li>复制文件</li> <li>装载应用 <h2>3.1 权限检查</h2> </li> </ol> <pre> <code class="language-java">public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); //利用binder机制,获取安装发起进程的uid final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser"); ……</code></pre> <p>先检查权限:</p> <pre> <code class="language-java">void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission, boolean checkShell, String message) { if (userId < 0) { throw new IllegalArgumentException("Invalid userId " + userId); } if (checkShell) { enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); } if (userId == UserHandle.getUserId(callingUid)) return; if (callingUid != Process.SYSTEM_UID && callingUid != 0) { if (requireFullPermission) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); } else { try { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); } catch (SecurityException se) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS, message); } } } }</code></pre> <p>这里的权限检查主要是检查进程是否有权限安装。</p> <p>继续installPackageAsUser代码:</p> <pre> <code class="language-java">public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) { …….. if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {//检查当前系统用户是否具备安装app的权限 try { if (observer != null) { observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null); } } catch (RemoteException re) { } return; } //如果是发起端进程是shell或者root,那么添加flags:PackageManager.INSTALL_FROM_ADB if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { installFlags |= PackageManager.INSTALL_FROM_ADB; } else { // 从flags中去掉INSTALL_FROM_ADB和INSTALL_ALL_USERS installFlags &= ~PackageManager.INSTALL_FROM_ADB; installFlags &= ~PackageManager.INSTALL_ALL_USERS; } UserHandle user; //创建一个当前用户的handle if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) { user = UserHandle.ALL; } else { user = new UserHandle(userId); } // Android 6.0 当权限属于运行时权限时,需要弹出框,让用户授权,对于system app,应该取消运行时权限弹框授权,而是直接授权。 // 那么就要在system app中加入INSTALL_GRANT_RUNTIME_PERMISSIONS // 我们安装第三方app,没有INSTALL_GRANT_RUNTIME_PERMISSIONS if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 && mContext.checkCallingOrSelfPermission(Manifest.permission .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { throw new SecurityException("You need the " + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); } verificationParams.setInstallerUid(callingUid); //发送INIT_COPY消息</code></pre> <p>这里主要是对当前用户是否有权限安装app进行检查,以及安装的app是仅仅为当前用户安装,还是给所有的用户安装。从以上代码可以得出,当安装进程是shell或者root时,flags中又包含了INSTALL_ALL_USERS时,才会给所有用户安装,否则大多数情况下,仅仅安装给当前的用户。当我们使用pm命令安装的时候,可以选择安装给哪个用户,也可以是全部用户,就是这个原因。</p> <pre> <code class="language-java">final File originFile = new File(originPath); final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile); final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,null, verificationParams, user, packageAbiOverride, null); mHandler.sendMessage(msg);</code></pre> <p>构造InstallParams,注意packageAbiOverride为null,然后利用Android中的Handler机制,发送给相关的线程进行安装。</p> <p>installPackageAsUser整个执行逻辑如下图所示所示。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/79a419a40fd39d2984e1f44ca0c40340.jpg"></p> <p>installPackageAsUser流程</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3cc6881654c8dd66977ccf76ef7b2da5.jpg"></p> <p>ccc.jpg</p> <p>.jpg</p> <h2>3.2 复制文件</h2> <p>前面发送了INIT_COPY消息,接下来看如何处理:</p> <pre> <code class="language-java">void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { HandlerParams params = (HandlerParams) msg.obj; int idx = mPendingInstalls.size(); if (!mBound) { //将绑定DefaultContainerService服务 if (!connectToService()) { Slog.e(TAG, "Failed to bind to media container service"); params.serviceError(); return; } else { mPendingInstalls.add(idx, params); } } …… break; }</code></pre> <p>INIT_COPY消息的处理中将绑定DefaultContainerService,因为这是一个异步的过程,要等待的绑定的结果通过onServiceConnected()返回,所以这里就将安装的参数信息放到了mPendingInstalls列表中,如果这个Service之前就绑定好了,现在就不要再次绑定了,安装信息同样要放到mPendingInstalls中。如果有多个安装请求同时到达,就可以通过mPendingInstalls列表对它们进行排队。如果列表中只有一项,说明没有更多的安装请求,因此这种情况下,需要立即发出MCS_BOUND消息,进入下一步的处理。</p> <pre> <code class="language-java">private boolean connectToService() { //("com.android.defcontainer", "com.android.defcontainer.DefaultContainerService") Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); if (mContext.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE, UserHandle.OWNER)) { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); mBound = true; //此处为MTK为解决adb install cannot response after run MTK-MTBF for a period of time而添加。当绑定未成功时,再重新绑定3次 final long DEFCONTAINER_CHECK = 1 * 1000; final Message msg = mHandler.obtainMessage(MCS_CHECK); mHandler.sendMessageDelayed(msg, DEFCONTAINER_CHECK); return true; } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return false; } final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service) { mServiceConnected = true; mServiceCheck = 0; //此处返回的service为DefaultContainerService中定义的IMediaContainerService.Stub,使用了AIDL进行的进程间通信。 //其定义于:frameworks/base/core/java/com/android/internal/app/IMediaContainerService.aidl IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs)); } ……</code></pre> <p>可以看到当绑定成功后在onServiceConnected中将一个IBinder转换成了一个IMediaContainerService.这个就是在onServiceConnected回调函数中根据参数传进来的IMediaContainerService.Stub的对象引用创建的一个远程代理对象。以后PMS务通过该代理对象访问DefaultContainerService服务。</p> <p>接下来分析MCS_BOUND消息:</p> <pre> <code class="language-java">void doHandleMessage(Message msg) { switch (msg.what) { …… case MCS_BOUND: { if (msg.obj != null) { mContainerService = (IMediaContainerService) msg.obj; } if (mContainerService == null) { if (!mBound) { for (HandlerParams params : mPendingInstalls) { params.serviceError(); } mPendingInstalls.clear(); } else { Slog.w(TAG, "Waiting to connect to media container service"); } } else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { if (params.startCopy()) { if (mPendingInstalls.size() > 0) { mPendingInstalls.remove(0); } if (mPendingInstalls.size() == 0) { if (mBound) { removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); sendMessageDelayed(ubmsg, 10000); } } else { mHandler.sendEmptyMessage(MCS_BOUND); } } } break; }</code></pre> <p>MCS_BOUND消息的处理过程就是调用InstallParams类的startCopy()方法来执行拷贝操作。只要mPendingInstalls中还有安装信息,就会重复发送MCS_BOUND消息,直到所有的应用都安装完毕,然后在发送一个延时10秒的MCS_UNBIND消息。</p> <pre> <code class="language-java">case MCS_UNBIND: { if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) { if (mBound) { disconnectService(); } } else if (mPendingInstalls.size() > 0) { mHandler.sendEmptyMessage(MCS_BOUND); } break; }</code></pre> <p>MCS_UNBIND消息的处理就简单了,当mPendingInstalls中没有安装信息的时候,就调用disconnectService断开与DefaultContainerService的连接。如果发现还有安装信息,则继续发送MCS_BOUND消息。</p> <p>接下来分析真正的拷贝方法:startCopy</p> <pre> <code class="language-java">final boolean startCopy() { boolean res; try { if (++mRetries > MAX_RETRIES) { mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { handleStartCopy(); res = true; } } catch (RemoteException e) { mHandler.sendEmptyMessage(MCS_RECONNECT); res = false; } handleReturnCode(); return res; }</code></pre> <p>startCopy()方法通过调用其子类InstallParams的handleStartCopy()来完成拷贝操作。考虑到安装过程的不确定性,startCopy主要工作是进行错误处理,当捕获到handleStartCopy跑出的异常时,startCopy将发送MCS_RECONNECT.在MCS_RECONNECT消息的处理中,将会重新绑定DefaultContainerService,如果绑定成功,那么安装过程将会重新开始。startCopy也将会再次被调用,重试的次数记录在mRetries中,当累计重试超过4次时,安装将失。如果安装失败,那么startCopy将会调用handleReturnCode()来继续处理。</p> <p>handleStartCopy操作流程如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/b2768c8d99fac240a899d8038798c559.jpg"></p> <p>InstallParms.handleStartCopy流程</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3cc6881654c8dd66977ccf76ef7b2da5.jpg"></p> <p>InstallParams和InstallArgs关系</p> <p>createInstallArgs传入的params,在本例中就是InstallParams,在它的handleStartCopy()中已经确定了安装在哪里。</p> <pre> <code class="language-java">private InstallArgs createInstallArgs(InstallParams params) { if (params.move != null) { return new MoveInstallArgs(params);//移动app } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) { return new AsecInstallArgs(params);//安装在SD卡 } else { return new FileInstallArgs(params);//安装在内部存储 } }</code></pre> <p>此处我们是安装在内部存储的,所以创建的就是FileInstallArgs了,那么调用的copyApk,自然就是FileInstallArgs的了。FileInstallArgs. copyApk主要是对apk内容进行拷贝,其数据流如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/4034f15a9f747d8d3258df07ce240df6.jpg"></p> <p>数据流向</p> <h2>3.3 装载应用</h2> <p>主要完成将dex转换为ART虚拟机的oat格式的执行文件,并为应用创建数据沙箱目录,最后把应用的信息装载进PMS的数据结构中去。</p> <p>在前面的处理MCS_BOUND时调用的HandlerParams的startCopy方法中当复制完文件之后,会调用InstallParams的handleReturnCode方法:</p> <pre> <code class="language-java">void handleReturnCode() { if (mArgs != null) { processPendingInstall(mArgs, mRet); } } private void processPendingInstall(final InstallArgs args, final int currentStatus) { mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); PackageInstalledInfo res = new PackageInstalledInfo(); res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { args.doPreInstall(res.returnCode); synchronized (mInstallLock) { installPackageLI(args, res); } args.doPostInstall(res.returnCode, res.uid); } //省略备份代码 if (!doRestore) { Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); } } }); }</code></pre> <p>processPendingInstall()方法中post了一个消息,这样安装过程将以异步的方式继续执行。在post消息中,首先是调用installPackageLI()来装载应用,接下来的一大段代码是在执行设备备份操作,备份是通过BackupManagerService来完成的,这里就不分析了。备份完成之后,通过发送POST_INSTALL消息继续处理。</p> <p>接着看installPackageLI()方法</p> <pre> <code class="language-java">private void installPackageLI(InstallArgs args, PackageInstalledInfo res) { final int installFlags = args.installFlags;//得到installFlags,里面记录了app需要安装到哪里 final String installerPackageName = args.installerPackageName; // 安装程序的包名 final String volumeUuid = args.volumeUuid; // 与sd卡安装有关,一般为null final File tmpPackageFile = new File(args.getCodePath());// 前面已经把apk拷贝到了临时阶段性文件夹/data/app/vmdl<安装回话id>.tmp/这个目录了 final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) || (args.volumeUuid != null)); // 是否安装到外部存储 boolean replace = false; // 初始化替换flag int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; if (args.move != null) { // moving a complete application; perfom an initial scan on the new install location scanFlags |= SCAN_INITIAL; } res.returnCode = PackageManager.INSTALL_SUCCEEDED; final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0); PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); pp.setDisplayMetrics(mMetrics); final PackageParser.Package pkg; try { //解析APK,也就是解析AndroidMainifest.xml文件,将结果记录在PackageParser.Package中 pkg = pp.parsePackage(tmpPackageFile, parseFlags); } catch (PackageParserException e) { res.setError("Failed parse during installPackageLI", e); return; } // Mark that we have an install time CPU ABI override. pkg.cpuAbiOverride = args.abiOverride; …… try {//收集apk的签名信息 pp.collectCertificates(pkg, parseFlags); pp.collectManifestDigest(pkg); } catch (PackageParserException e) { res.setError("Failed collect during installPackageLI", e); return; } //如果安装程序此前传入了一个清单文件,那么将解析到的清单文件与传入的进行对比。安装器的确传入了一个清单,PackageInstallerActivity中也解析了apk,那时记录了这个清单,并一并传入到这里了。这里又做了一步判断,判断两者是同一个apk. if (args.manifestDigest != null) { if (!args.manifestDigest.equals(pkg.manifestDigest)) { res.setError(INSTALL_FAILED_PACKAGE_CHANGED, "Manifest digest changed"); return; } } else if (DEBUG_INSTALL) { final String parsedManifest = pkg.manifestDigest == null ? "null" : pkg.manifestDigest.toString(); Slog.d(TAG, "manifestDigest was not present, but parser got: " + parsedManifest); } synchronized (mPackages) { // Check if installing already existing package if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { ……//处理安装已经存在的应用,将replace置为true } // 如果ps不为null,同样说明,已经存在一个同包名的程序被安装,也就是还是处理覆盖安装的情况 // 这里主要是验证包名的签名,不一致的话,是不能覆盖安装的,另外版本号也不能比安装的低,否则也不能安装 PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null) { if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) { if (!checkUpgradeKeySetLP(ps, pkg)) { res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + pkg.packageName + " upgrade keys do not match the "+ "previously installed version"); return; } } else { try { verifySignaturesLP(ps, pkg); } catch (PackageManagerException e) { res.setError(e.error, e.getMessage()); return; } } …… } //检查apk中定义的所有的权限是否已经被其他应用定义了,如果重定义的是系统应用定义的权限,那么忽略本app定义的这个权限。如果重定义的是非系统应用的权限,那么本次安装就以失败返回 int N = pkg.permissions.size(); for (int i = N-1; i >= 0; i--) { } } if (args.move != null) { // 移动app走该分支 } else if (!forwardLocked && !pkg.applicationInfo.isExternalAsec()) { scanFlags |= SCAN_NO_DEX; try { derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride, true);//设置apk的so库路径,以及主次abi的值 } catch (PackageManagerException pme) { return; } //实际为dex2oat操作,用来将apk中的dex文件转换为oat文件。需要注意的是: //Android M中,/data/dalvik_cache/只存放系统内置应用的oat文件, //用户安装的app的oat文件在,最终会在/data/app/包名/oat/<isa>/ int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */, false /* forceDex */, false /* defer */, false /* inclDependencies */); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath); return; } } //重命名,将/data/app/vmdl<安装会话id>.tmp重命名为/data/app/包名-suffix suffix为1,2…… if (!args.doRename(res.returnCode, pkg, oldCodePath)) { res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); return; } startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); if (replace) {//覆盖安装 replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, installerPackageName, volumeUuid, res); } else {//首次安装 installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, volumeUuid, res); }</code></pre> <p>执行完installPackageLI之后,返回processPendingInstall方法中,继续发送POST_INSTALL消息继续处理。代码如下:</p> <pre> <code class="language-java">sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, null, null, firstUsers); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, null, null, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, packageName, null, updateUsers); }</code></pre> <p>该消息的处理主要就是在发送广播,应用安装完成之后要通知系统中的其他应用开始处理,比如在launcher需要增加app的图标等。等发完广播,安装也就结束了,最后通过最初安装是传入的安装观察者observer返回最初的调用者。</p> <h2>4. PMS问题调试方法</h2> <h2>4.1 如何打开PMS的log开关</h2> <p>方法一</p> <ul> <li>adb shell dumpsys package log a on</li> <li>只对当次开机有效</li> </ul> <p>方法二</p> <ul> <li>PackageManagerService.java中修改DEBUG_XXX为true</li> <li>长久有效</li> </ul> <h2>4.2 pm指令的使用</h2> <p>利用adb shell命令,进入Android设备的终端,pm工具在/system/bin中,所以可以直接使用:pm <cmd></p> <h2>包名信息查询</h2> <pre> <code class="language-java">pm list packages [options] [FILTER]</code></pre> <p>打印所有的已经安装的应用的包名,如果设置了文件过滤则值显示包含过滤文字的内容.</p> <p>参数:</p> <p>-f 显示每个包的文件位置</p> <p>-d 使用过滤器,只显示禁用的应用的包名</p> <p>-e 使用过滤器,只显示可用的应用的包名</p> <p>-s 使用过滤器,只显示系统应用的包名</p> <p>-3 使用过滤器,只显示第三方应用的包名</p> <p>-i 查看应用的安装者</p> <h2>权限信息查询</h2> <p>打印所有已知的权限组</p> <pre> <code class="language-java">pm list permission-groups</code></pre> <p>打印权限:</p> <pre> <code class="language-java">pm list permissions [options] [GROUP]</code></pre> <p>参数:</p> <p>g 按组进行列出权限</p> <p>-f 打印所有信息</p> <p>-s 简短的摘要</p> <p>-d 只有危险的权限列表</p> <p>-u 只有权限的用户将看到列表用户自定义权限</p> <p>Android 6.0之后,允许授权和取消权限:</p> <pre> <code class="language-java">pm grant <package_name> <permission> pm revoke <package_name> <permission></code></pre> <p>授权和取消是针对APK中申请的权限的来说的。即APK中没有申请的权限,是没办法通过此命令添加的。</p> <h2>包路径</h2> <pre> <code class="language-java">pm path package_name</code></pre> <h2>系统硬件特性</h2> <pre> <code class="language-java">pm list features</code></pre> <h2>设备依赖的java库</h2> <pre> <code class="language-java">pm list libraries</code></pre> <h2>dump包信息</h2> <pre> <code class="language-java">pm dump package_name</code></pre> <h2>安装与卸载apk</h2> <p>安装apk</p> <pre> <code class="language-java">pm install [-lrtsfd] [-i PACKAGE] [PATH]</code></pre> <p>adb install实际上就是对pm install的封装调用。</p> <p>参数:</p> <p>-l 锁定应用程序</p> <p>-r 重新安装应用,且保留应用数据</p> <p>-t 允许测试apk被安装</p> <p>-i INSTALLER_PACKAGE_NAME 指定安装包的包名</p> <p>-s 安装到sd卡</p> <p>-f 安装到系统内置存储中(默认安装位置)</p> <p>-d 允许降级安装(同一应用低级换高级)</p> <p>-g 授予应用程序清单中列出的所有权限(只有6.0系统可用)</p> <p>卸载apk:</p> <pre> <code class="language-java">pm uninstall [options] <PACKAGE></code></pre> <p>参数:</p> <p>-k 卸载应用且保留数据与缓存(如果不加-k则全部删除)</p> <h2>清除应用数据</h2> <pre> <code class="language-java">pm clear package_name</code></pre> <h2>禁用和启用系统应用</h2> <pre> <code class="language-java">pm enable <PACKAGE_OR_COMPONENT> 使package或component可用 pm disenable <PACKAGE_OR_COMPONENT> 使package或component不可用(直接就找不到应用了) pm disenable-user [options] <PACKAGE_OR_COMPONENT> 使package或component不可用(会显示已停用)</code></pre> <h2>隐藏与恢复应用</h2> <pre> <code class="language-java">pm hide PACKAGE_OR_COMPONENT pm unhide PACKAGE_OR_COMPONENT</code></pre> <p>被隐藏应用在应用管理中变得不可见,桌面图标也会消失</p> <h2>设置和查看应用的安装位置</h2> <pre> <code class="language-java">pm set-install-location package_name pm get-install-location package_name</code></pre> <p>参数:</p> <p>0:自动-让系统决定最好的位置</p> <p>1:内部存储-安装在内部设备上的存储</p> <p>2:外部存储-安装在外部媒体</p> <h2>查看当前系统user信息</h2> <pre> <code class="language-java">pm list users</code></pre> <p>可以指的apk安装在某个user下,这样只有切换到该user时,才能显示和使用该apk。</p> <p> </p> <p>来自:http://www.jianshu.com/p/6769d1a9f4ad</p> <p> </p>