日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

PMS

發布時間:2023/12/20 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PMS 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

PackageMangerService源碼閱讀總結

簡介

PackageMangerService(簡稱PMS)是Android系統中負責管理所有Apk的安裝、卸載更新等工作,其運行在SystemServer進程,維護管理系統中所有的Apk包管理,那它是如何管理眾多APK的呢?它怎么知道系統中有哪些APK應用,這些Apk在什么時候安裝、卸載和更新,PMS如何感知?就算它知道了系統中所有APK的存在,如何去建立一個維護呢?
帶著以上諸多疑問,從源碼的角度,來探尋這些問題,感悟PMS的精髓所在!


淺析自己設計一個PMS包管理

管理對象 — 靜態存儲在磁盤上的APK文件
管理者 — PMS管理器,系統中是唯一的
目標對象 — 活的應用程序,界面上能顯示,能交互

首先,我作為PMS管理者,系統開機BOOT時,要掃描系統中的文件,尋找哪些是APK文件,然后針對處理,但是不可能掃描系統中所有的路徑,肯定要事先做好約束,APK文件只能放在某些特定的目錄下;還有一點,系統開機后,用戶自行安裝apk文件時,這個時候PMS不可能又去掃描一遍,最好的方法是提供一個命令接口,用戶安裝時通過調用這個命令接口,這樣PMS就可以拿到用戶安裝的APK文件,然后分析之,以下幾個路徑是PMS開機啟動時會掃描的幾個路徑:

system/app 系統自帶的應用程序
data/app 用戶程序安裝的目錄
vendor/app 廠商定制app目錄
system/priv-app/ 系統私有App,其中service可以保活,一旦被殺死就會立即恢復,缺點無法正常升級

其次,PMS拿到APK文件后,要解析分享APK內容,拿到其中重要的東西,然后再保存到內存或數據庫中,內存中用一系列賦值的數據模型建立好每個APK文件的映射,提供給Launcher程序,Launcher根據數據模型可以清楚指定有哪些應用,將其繪制到桌面上去,難點在于解析APK這塊,你需要知道APK需要哪些權限、四大組件有哪些,lib動態庫,使用的資源res文件等,解析后用內存數據模型維護好它們

實質上分析APK內容主要就是AndroidManifest.xml文件,然后把其中的內容映射到PackageParser.Package、PackageSetting中

最后,PMS在自己的進程里面,需要提供安裝、卸載更新接口,其他用戶進程通過binder跨進程來調用即可


PMS源碼瞧瞧

按照我們上面的設計內容,從源碼中去看看它是如何工作的?


PMS啟動切入點

位于SystemServer的run方法中,其中調用startBootstrapServices,啟動Boot相關的服務:

private void startBootstrapServices() {....mOnlyCore表示是加密參數,true表示只解析加密的APKmPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);.... }

進入main方法:

public static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {// 自檢初始化配置參數PackageManagerServiceCompilerMapping.checkProperties();PackageManagerService m = new PackageManagerService(context, installer,factoryTest, onlyCore);m.enableSystemUserPackages();把PMS加入到binder服務列表中去,名為packageServiceManager.addService("package", m);return m; }

這里就正式進入PackageManager的領域了!


PMS構造方法

為什么要講他的構造方法?首先,大量的工作內容都是在構造方法中完成的;其次,后續的安裝、更新工作也是按照構造方法中這個套路的來執行的,所以它構造方法很重要!

構造方法中,有以下幾個事件階段,如下:

  • BOOT_PROGRESS_PMS_START
  • BOOT_PROGRESS_PMS_SYSTEM_SCAN_START
  • BOOT_PROGRESS_PMS_DATA_SCAN_START
  • BOOT_PROGRESS_PMS_SCAN_END
  • BOOT_PROGRESS_PMS_READY

BOOT_PROGRESS_PMS_START

START階段做了很多事情,依次闡述,源碼如下,精簡了部分:

Settings初始化工作,PMS中相關的配置工作類

Settings是整個系統的APK配置管理類,里面維護了所有apk的內存存儲模型,mPakcages作為參數傳遞 進去,mPackages類型為ArrayMap<String, PackageParser.Package>,key為packagename mSettings = new Settings(mPackages); 添加sharedId內容,擁有相同sharedid可以共享資源,也可以在同一個進程運行,后面會講到 mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.log", LOG_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); SystemConfig systemConfig = SystemConfig.getInstance(); 解析相應文件(/system/etc/permission和(framework/base/data/etc/下的文件), 包括platform.xml和系統支持的各種硬件模塊的feature, mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures();

SystemConfig主要工作內容是:

  • 建立底層的uid和 group id同行上層permission之間的映射,可以指定一個權限與幾個id的對應。當一個APK被授予這個權限時,它也同時屬于這幾個組。

    例如:platform.xml文件內容有 <permission name="android.permission.BLUETOOTH" ><group gid="net_bt" /></permission>即字符串藍牙權限屬于用戶組net_bt,SystemConfig讀取到藍牙權限時,會用PermissionEntry實體保存,name為android.permission.BLUETOOTH,而gid是字符串,會用Process將gid字符串轉換為整型數字保存在gids數組;也就是如下:public static final class PermissionEntry { public final String name;public int[] gids;}當我們應用程序在AndroidManifest.xml聲明了BLUETOOTH權限時,實質就是將我們加入到gids中某個用戶組里面,這樣就能夠訪問相應的文件
  • 給一些底層用戶分配權限,如給shell授予各種permission權限,把一個權限賦予uid,當進程只是用這個uid運行時,就具備了這個權限。

  • libary,系統增加的一些應用需要link擴展的jar。

  • feature,系統每增加一個硬件,都要添加相應的feature,將解析結果放入mSystemPermissions,mShareLibrariest,mSettings.mPermissions,mAvailableFeatures等幾個集合供系統查詢和權限配置使用。

  • 然后看看Settings是個什么東西?其構造方法如下:

    //傳遞進入mPackage當做一把鎖 Settings(Object lock) {參數一返回的/data目錄this(Environment.getDataDirectory(), lock); } Settings(File dataDir, Object lock) {mLock = lock;mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);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); 創建一些xml文件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);final File kernelDir = new File("/config/sdcardfs");mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;// Deprecated: Needed for migrationmStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); }

    小結:
    從以上源碼看,settings主要在data目錄下創建一些xml文件,并且后續掃描解析apk后,會把apk信息保存到這些文件中去,

    packages.xml 記錄了系統中所有安裝的應用信息,包括基本信息、簽名和權限 packages-backup.xml 對packages.xml文件的備份文件 packages.list 存儲了系統中應用的安裝信息,主要包含包名、安裝位置、UID、Debuggable屬性等 packages-stopped.xml 記錄系統強行停止的應用信息 packages-stopped-backup.xml 顧名思義,對packages-stopped文件的備份

    插入一個SharedId知識點:
    即AndroidManifest.xml里面的sharedUserId,兩個應用聲明相同的sharedUserId,就可以共享資源,如果簽名相同還會運行在同一個進程下面;UID即用戶User ID,Android中每個應用程序都有自己的一個UID,不同的UID應用程序之間是進程隔離的,但是如果我們在AndroidManifest.xml里面manifest標簽里面聲明相同的shareuserid,并且使用相同的簽名,則兩個應用程序是可以共享資源,并且運行在同一進程里面;

    回到本文源碼mSettings.addSharedUserLPw(“android.uid.system”, Process.SYSTEM_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED),我們跟進去看看:

    final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>(); //存放大于10000小于19999的UID的sharedUserSetting private final ArrayList<Object> mUserIds = new ArrayList<Object>(); //同上,只是保存小于10000的UIDsharedUidSetting private final SparseArray<Object> mOtherUserIds =new SparseArray<Object>(); SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { map獲取value SharedUserSetting s = mSharedUsers.get(name);if (s != null) {userId是SharedUserSetting成員,就是UID,這里指如果添加進來的UID和保存的相同就直接返回if (s.userId == uid) {return s;}PackageManagerService.reportSettingsProblem(Log.ERROR,"Adding duplicate shared user, keeping first: " + name);return null;}創建一個新的SharedUserSettings = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);s.userId = uid;并且把這個uid的應用添加到mOtherUserIds或mUserIds結構體中去if (addUserIdLPw(uid, s, name)) {mSharedUsers.put(name, s);return s;}return null; }private boolean addUserIdLPw(int uid, Object obj, Object name) {last為19999 if (uid > Process.LAST_APPLICATION_UID) {return false;}first為10000 如果UID大于10000的應用,添加到mUserIds結構中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) {PackageManagerService.reportSettingsProblem(Log.ERROR,"Adding duplicate user id: " + uid+ " name=" + name);return false;}mUserIds.set(index, obj);小于10000的屬于系統應用,添加到mOtherUserIds結構體中去} else {if (mOtherUserIds.get(uid) != null) {PackageManagerService.reportSettingsProblem(Log.ERROR,"Adding duplicate shared id: " + uid+ " name=" + name);return false;}mOtherUserIds.put(uid, obj);}return true;}

    總結一下,Settings類里面有三個總要成員:
    ArrayMap<String, SharedUserSetting> mSharedUsers:string保存了sharedId的描述符,SharedUserSetting保存應用信息
    ArrayList<Object> mUserIds:保存UID大于10000的應用程序,通過源碼得知,Object保存實質就是SharedUserSetting
    SparseArray<Object> mOtherUserIds:保存UID小于10000的應用程序

    我們用ps -ef查看我們應用進程時,其UID一般是Ux_axx的形式(如U0_a856),這是描述符,U表示userid,a表示appid,使用Process.myUID獲取當前應用的UID,前者的x取值為UID/10000,后者為UID-10000

    這里有一個問題?相同sharedID的不同應用在這三個數據模型內是如何保存的?答案就在SharedUserSetting

    如上圖,userId是用戶ID值,name是其描述,ArraySet<PackageSetting>是擁有相同sharedId的應用,每個應用對應一個PackageSetting,這樣系統就知道哪些app擁有相同的sharedId,綜上在Settings里面的三個成員數據結構模型可歸納為下圖:

    BOOT_PROGRESS_PMS_SYSTEM_SCAN_START

    掃描啟動階段,它會掃描某些特定目錄,解析目錄下存在的APK文件,并解析至,獲得PackageParser.Package,每個APK對應一個PackageParser.Package,最后在轉換生成PackageSetting存儲在Settings里面去;過程就是這樣,難點在于他如何去解析每一個APK,APK還會分單個APK和集群式APK,分別有不同的解析方法,解析完成后還涉及與舊版本APK對比更新等等!

    掃描的路徑主要是:

    /vendor/overlay /system/framework /system/priv-app /system/app /vendor/app /data/app /data/app-private
    什么是集群式APK?

    集群式APK其APK文件不止一個,一般由一個base.apk做基礎,其他功能模塊由不同split.apk組成,如Google提出App bundle技術就是這類型的apk,無論是單個Apk還是集群式Apk最終解析到內存數據模型都是為一個PackageParser.Package對象

    APK解析過程parser

    認清目標,解析對象是什么?最終的結果是什么?如下圖:

    源碼中,解析流程主要是:

    a. PackageManagerService.scanDirTracedLI() -> ParallelPackageParser.submit() -> PackageParser.parsePackage()
    b. return PackageParser.Package到PackageManagerService
    c. PackageManagerService.scanPackageLI(PackageParser.Package) 并得到PackageSetting
    d. Settings保存PackageSetting

    重點看一下a步驟里面PackageParser.parsePackage() 方法,方法內部又調用了PackageParser.parsePackage方法:

    public Package parsePackage(File packageFile, int flags, boolean useCaches) throws PackageParserException {有緩存就取緩存 Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;if (parsed != null) {return parsed;} 如果file是一個目錄,說明是集群式APP,使用parseClusterPackage去解析if (packageFile.isDirectory()) {parsed = parseClusterPackage(packageFile, flags);} else {單個App使用parseMonolithicPackage解析parsed = parseMonolithicPackage(packageFile, flags);} 緩存結果cacheResult(packageFile, flags, parsed);return parsed; }

    這里我們看parseClusterPackage是如何實現的?

    private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {解析APK精簡內容,只解析apk的Androidmanifest中manifest、Application、use-split和package-verify標簽下的屬性final PackageLite lite = parseClusterPackageLite(packageDir, 0);if (mOnlyCoreApps && !lite.coreApp) {throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,"Not a coreApp: " + packageDir);}// Build the split dependency tree.SparseArray<int[]> splitDependencies = null;final SplitAssetLoader assetLoader;if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {try {每個包apk依賴了其他另外的包,建立好他們的映射關系,即用到了use-split標簽splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);} catch (SplitAssetDependencyLoader.IllegalDependencyException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());}} else {assetLoader = new DefaultSplitAssetLoader(lite, flags);}try {//就是當前基礎路徑packageDirfinal File baseApk = new File(lite.baseCodePath);//解析baseApk中的AndroidManifest所有標簽final Package pkg = parseBaseApk(baseApk, assets, flags);if (pkg == null) {throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,"Failed to parse base APK: " + baseApk);}if (!ArrayUtils.isEmpty(lite.splitNames)) {final int num = lite.splitNames.length;pkg.splitNames = lite.splitNames;pkg.splitCodePaths = lite.splitCodePaths;pkg.splitRevisionCodes = lite.splitRevisionCodes;pkg.splitFlags = new int[num];pkg.splitPrivateFlags = new int[num];pkg.applicationInfo.splitNames = pkg.splitNames;pkg.applicationInfo.splitDependencies = splitDependencies;for (int i = 0; i < num; i++) {final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);//依次解析每個分包apk的AndroidManiesf文件,split apk中主要解析四大組件,其他如權限方面在parseBaseApk中已經解析//并且base apk和splitapk中組件都是放在Package的成員中,沒有特地分開parseSplitApk(pkg, i, splitAssets, flags);}}pkg.setCodePath(packageDir.getAbsolutePath());pkg.setUse32bitAbi(lite.use32bitAbi);return pkg;} finally {IoUtils.closeQuietly(assetLoader);}}

    在看看parseBaseApk方法是什么樣子?

    private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,String[] outError) throws XmlPullParserException, IOException {final String splitName;final String pkgName;try {Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);//解析出來的是基礎包名和分包包名pkgName = packageSplit.first;splitName = packageSplit.second;//基礎包base-apk的分包包名必須為空if (!TextUtils.isEmpty(splitName)) {outError[0] = "Expected base APK, but found split " + splitName;mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;return null;}} catch (PackageParserException e) {mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;return null;}if (mCallback != null) {//回調到PackageManagerService獲取到overlay資源,加入到自己的資源路徑,用于替換//overlay機制見https://blog.csdn.net/azhengye/article/details/49050631String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);if (overlayPaths != null && overlayPaths.length > 0) {for (String overlayPath : overlayPaths) {res.getAssets().addOverlayPath(overlayPath);}}}final Package pkg = new Package(pkgName);TypedArray sa = res.obtainAttributes(parser,com.android.internal.R.styleable.AndroidManifest);//解析基礎包的manifest標簽下的基本屬性code和name等pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(com.android.internal.R.styleable.AndroidManifest_versionCode, 0);pkg.baseRevisionCode = sa.getInteger(com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);pkg.mVersionName = sa.getNonConfigurationString(com.android.internal.R.styleable.AndroidManifest_versionName, 0);if (pkg.mVersionName != null) {pkg.mVersionName = pkg.mVersionName.intern();}pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);sa.recycle();一下方法主要是解析AndroidMainfest.xml中的四大組件權限等標簽return parseBaseApkCommon(pkg, null, res, parser, flags, outError);

    上面mCallback.getOverlayPaths方法,主要是為了實現Android的Overlay機制,實現動態替換資源,具體Overlay簡介請點擊,最后parseBaseApkCommon就主要是機械AndroidMainfest中相通的一些內容,如四大組件以及權限等

    小結:
    從源碼上來看解析集群式APK主要是解析每個APK的AndroidManifest.xml中的內容,并建立好各個模塊APK之間的依賴關系,解析四大組件以及權限等、讀取代碼位置、安裝路徑等,最后存放到Package變量中去,所以,最終我們理解透這個Package中的內容即可,它屬于PackageParser的內部類,如下

    public final static class Package {//表示包名public String packageName;// 表示"拆包"的包名,是個數組,每個元素代表一個"拆分"包名public String[] splitNames;//對應一個volume的uidpublic String volumeUuid;// 表示代碼的路徑,如果是單個包,則表示"base"的APK的路徑,如果是"集群"包,則表示的"集群"包的目錄。public String codePath;// "base APK"的路徑public String baseCodePath;// "拆分 APK"的路徑public String[] splitCodePaths;// "base APK"的調整版本號public int baseRevisionCode;//"拆分APK"的調整版本號public int[] splitRevisionCodes;// "拆分APK"的標志數組public int[] splitFlags;// "拆分APK"的私有標志數組public int[] splitPrivateFlags;// 是否支持硬件加速public boolean baseHardwareAccelerated;// 對應ApplicationInfo對象 對應AndroidManifest里面的Applicationpublic final ApplicationInfo applicationInfo = new ApplicationInfo();// APK安裝包中 AndroidManifest里面的<Permission>public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);// APK安裝包中 AndroidManifest里面的<PermissionGroup>public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);// APK安裝包中 AndroidManifest里面的<Activity>,這里面的Activity是不是我們通常說的Activity,而是PackageParse的內部類Activitypublic final ArrayList<Activity> activities = new ArrayList<Activity>(0);// APK安裝包中 AndroidManifest里面的<Receiver>,這里面的Activity是不是我們通常說的Activity,而是PackageParse的內部類Activitypublic final ArrayList<Activity> receivers = new ArrayList<Activity>(0);// APK安裝包中 AndroidManifest里面的<Provider>,這里面的Provider是不是我們通常說的Provider,而是PackageParse的內部類Providerpublic final ArrayList<Provider> providers = new ArrayList<Provider>(0);// APK安裝包中 AndroidManifest里面的<Service>,這里面的Service是不是我們通常說的Service,而是PackageParse的內部類Servicepublic final ArrayList<Service> services = new ArrayList<Service>(0);// APK安裝包中 AndroidManifest里面的<Instrumentation>,這里面的Instrumentation是不是我們通常說的Instrumentation,而是PackageParse的內部類Instrumentationpublic final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);// APK安裝包中請求的權限public final ArrayList<String> requestedPermissions = new ArrayList<String>();// APK安裝包中 保內廣播的Actionpublic ArrayList<String> protectedBroadcasts;// APK安裝包中 依賴庫的名字public ArrayList<String> libraryNames = null;// APK安裝包中 使用庫的名字public ArrayList<String> usesLibraries = null;// APK安裝包中 使用選項庫的名字public ArrayList<String> usesOptionalLibraries = null;// APK安裝包中 使用庫的路徑數組public String[] usesLibraryFiles = null;// APK安裝包中 某個Activity信息的集合,在AndroidManifest里面的<preferred>標簽(不在receiver里面)public ArrayList<ActivityIntentInfo> preferredActivityFilters = null;// APK安裝包中 AndroidManifest中對應"original-package"的集合public ArrayList<String> mOriginalPackages = null;// 是真實包名,通常和mOriginalPackages一起使用public String mRealPackage = null;// APK安裝包中 AndroidManifest中對應"adopt-permissions"集合public ArrayList<String> mAdoptPermissions = null;// 我們獨立的存儲應用程序元數據,以避免多個不需要的引用public Bundle mAppMetaData = null;// 版本號public int mVersionCode;// 版本名public String mVersionName;// 共享idpublic String mSharedUserId;// 共享用戶標簽public int mSharedUserLabel;// 簽名public Signature[] mSignatures;// 證書public Certificate[][] mCertificates;// dexopt的位置,以便PackageManagerService跟蹤要執行dexopt的位置public int mPreferredOrder = 0;// 需要進行的dexopt的集合public final ArraySet<String> mDexOptPerformed = new ArraySet<>(4);// 最后一次使用pakcage的時間的public long mLastPackageUsageTimeInMills;// 附加數據 用于指向PackageSettingpublic Object mExtras;// 硬件配置信息,對一個AndroidManifest里面的<uses-configuration> 標簽public ArrayList<ConfigurationInfo> configPreferences = null;//特性信息,對一個AndroidManifest里面的<uses-feature> 標簽 public ArrayList<FeatureInfo> reqFeatures = null;//特性組信息,對一個AndroidManifest里面的<feature-group> 標簽 public ArrayList<FeatureGroupInfo> featureGroups = null;// 安裝的屬性public int installLocation;// 是否是核心public boolean coreApp;// 是否是全局必要,所有用戶都需要的應用程序,無法為用戶卸載public boolean mRequiredForAllUsers;// 受限賬戶的 驗證類型public String mRestrictedAccountType;// 賬戶的類型public String mRequiredAccountType;// 對應的AndroidManifest項目摘要清單public ManifestDigest manifestDigest;//AndroidManifest中 對應<overlay> 標簽public String mOverlayTarget;//overlay對應的優先級public int mOverlayPriority;// 是否是受信任的Overlaypublic boolean mTrustedOverlay;// 下面是用來給KeySetManagerService的數據public ArraySet<PublicKey> mSigningKeys; // 簽名public ArraySet<String> mUpgradeKeySets; //升級public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping; //公鑰// 如果有abi的話,abi覆蓋public String cpuAbiOverride;

    上訴代碼中提到的Activity、Service等并不是我們開發中使用到的,而是PackageParser的內部類,想想也可以理解,這里如Activity需要保存Activity的所有信息,如名字、啟動模式、Intent過濾等以及和ApplicationInfo之間的關系,這些都不是一個Activity所能保存的;

    在重點看一下cd步驟,如何處理解析后的Package變量? 解析內容太過于復雜,重點參考這篇文章,會先判斷apk的簽名,是否已經安裝等,最后在保存到Settings實例中去,保存到Settings主要是一個PackageSetting對象,主要涉及文章前面提到的mPackages、mSharedUser、mOtherUserIds和mUserIds這三個成員中,調用最終邏輯在:
    PackageManagerService.commitPackageSettings() -> Settings.insertPackageSettingLPw() -> Settings.addPackageSettingLPw()將把此次APK的PackageSetting添加到Settings的mPackages里面去,同時根據sharedId描述符添加到對應的mSharedUser里面去,最后根據當前APK的appId大小,將SharedUserSetting替換對應的mOtherUserIds和mUserIds成員中去

    這里也是重點看一下PackageSetting是一個什么樣的對象:

    PackageSetting(String name, String realName, File codePath, File resourcePath,String legacyNativeLibraryPathString, String primaryCpuAbiString,String secondaryCpuAbiString, String cpuAbiOverrideString,int pVersionCode, int pkgFlags, int privateFlags, String parentPackageName,List<String> childPackageNames, int sharedUserId, String[] usesStaticLibraries,int[] usesStaticLibrariesVersions) {super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString,primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,pVersionCode, pkgFlags, privateFlags, parentPackageName, childPackageNames,usesStaticLibraries, usesStaticLibrariesVersions);this.sharedUserId = sharedUserId; }

    從它的構造方法可以看出來,主要包含了一個APP的基本信息,如安裝位置,lib位置、資源位置、版本信息、包名等


    BOOT_PROGRESS_PMS_DATA_SCAN_START

    這個步驟和上一個步驟類似,掃描/data/app和/data/app-private目錄下的APK


    BOOT_PROGRESS_PMS_SCAN_END

    掃描結束,當前系統下所有APK文件已經寫入到內存結構模型中去了,主要在Settings的mPackages、mSharedUser、mUserId和mOtherUserId幾個成員,現在將要把相關信息寫入到文檔packages.xml中持久化,所以在PackageManagerService的BOOT_PROGRESS_PMS_SCAN_END步驟會有如下:

    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END, SystemClock.uptimeMillis());writeLPr將會寫入信息到文檔packages.xml mSettings.writeLPr();void writeLPr() { ....try {mSettingsFilename變量是Settings構造方法創建的package.xml文件FileOutputStream fstr = new FileOutputStream(mSettingsFilename);BufferedOutputStream str = new BufferedOutputStream(fstr);//XmlSerializer serializer = XmlUtils.serializerInstance();XmlSerializer serializer = new FastXmlSerializer();serializer.setOutput(str, StandardCharsets.UTF_8.name());serializer.startDocument(null, true);serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);serializer.startTag(null, "packages");xml序列化所有PackageSettingfor (final PackageSetting pkg : mPackages.values()) {writePackageLPr(serializer, pkg);}for (final PackageSetting pkg : mDisabledSysPackages.values()) {writeDisabledSysPackageLPr(serializer, pkg);}for (final SharedUserSetting usr : mSharedUsers.values()) {serializer.startTag(null, "shared-user");serializer.attribute(null, ATTR_NAME, usr.name);serializer.attribute(null, "userId",Integer.toString(usr.userId));usr.signatures.writeXml(serializer, "sigs", mPastSignatures);writePermissionsLPr(serializer, usr.getPermissionsState().getInstallPermissionStates());serializer.endTag(null, "shared-user");} str.flush();FileUtils.sync(fstr);str.close();// New settings successfully written, old ones are no longer// needed.mBackupSettingsFilename.delete();FileUtils.setPermissions(mSettingsFilename.toString(),FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP|FileUtils.S_IWGRP,-1, -1);return;} catch(XmlPullParserException e) {Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "+ "current changes will be lost at reboot", e);} catch(java.io.IOException e) {Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "+ "current changes will be lost at reboot", e);} }

    以上就把所有apk配置信息寫入到文件中去了,后續其他應用需要包信息都可以從這里找到,也可以從Settings的成員變量中找到


    BOOT_PROGRESS_PMS_READY

    創建安裝服務PackageInstallerService,接收來自安裝程序如adb、packageinstaller等安裝請求


    PMS安裝APK

    什么是安裝?

    第一步:拷貝文件到指定的目錄:
    在Android系統中,apk安裝文件是會被保存起來的,默認情況下,用戶安裝的apk首先會被拷貝到/data/app目錄下,/data/app目錄是用戶有權限訪問的目錄,在安裝apk的時候會自動選擇該目錄存放用戶安裝的文件,而系統出場的apk文件則被放到了/system分區下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,該分區只有ROOT權限的用戶才能訪問,這也就是為什么在沒有Root手機之前,我們沒法刪除系統出場的app的原因了。
    第二步:解壓縮apk,寶貝文件,創建應用的數據目錄
    為了加快app的啟動速度,apk在安裝的時候,會首先將app的可執行文件dex拷貝到/data/dalvik-cache目錄,緩存起來。然后,在/data/data/目錄下創建應用程序的數據目錄(以應用的包名命名),存放在應用的相關數據,如數據庫、xml文件、cache、二進制的so動態庫等。
    第三步:解析apk的AndroidManifest.xml文件

    Android系統中,也有一個類似注冊表的東西,用來記錄當前所有安裝的應用的基本信息,每次系統安裝或者卸載了任何apk文件, 都會更新這個文件。這個文件位于如下目錄:/data/system/packages.xml。系統在安裝這個apk的過程中,會解析apk的 AndroidManifest.xml文件,提取出這個apk的重要信息寫入到packages.xml文件中,這些信息包括:權限、應用包名、 APK的安裝位置、版本、userID等等。由此,我們就知道了為什么一些應用市場和軟件管理類的app能夠很清楚地知道當前手機 所安裝的所有app,以及這些app的詳細信息了。另外一件事就是Linux的用戶Id和用戶組Id,以便他們可以獲得合適的運行權限。 以上都是由PackageServcieManager完成的,后面我們會重點介紹PackageServiceManager。

    第四步:顯示快捷方式
    如果這些應用程序在PackageManagerService服務注冊好了,如果我們想要在Android桌米上看到這些應用程序,還需要有一個Home應用程序,負責從PackageManagerService服務中把這些安裝好的應用程序取出來,并以友好的方式在桌面上展現出來,例如以快捷圖標的形式。在Android系統中,負責把系統中已經安裝的應用程序在桌面中展現出來的Home應用就是Launcher了。

    PackageHandler

    PackageHandler屬于PMS的內部類,是一個Handler,主要用于接收binder客戶端,如PackageInstaller或adb的安裝程序,發起跨進程調用;對于PMS暴露出來的安裝接口,如:

    public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,int installFlags, String installerPackageName, int userId) {final Message msg = mHandler.obtainMessage(INIT_COPY);final VerificationInfo verificationInfo = new VerificationInfo(null /*originatingUri*/, null /*referrer*/, -1 /*originatingUid*/, callingUid);final InstallParams params = new InstallParams(origin, null /*moveInfo*/, observer,installFlags, installerPackageName, null /*volumeUuid*/, verificationInfo, user,null /*packageAbiOverride*/, null /*grantedPermissions*/,null /*certificates*/, PackageManager.INSTALL_REASON_UNKNOWN);params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params));msg.obj = params;Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installAsUser",System.identityHashCode(msg.obj));Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",System.identityHashCode(msg.obj));發送給PackageHandlermHandler.sendMessage(msg); }

    PackageHandler屬于PMS的子線程,專門處理安裝程序!PMS內部的安裝流程就不在分析!

    備注一個重要信息,如果我們編譯了系統應用apk后,希望我們的apk能默認獲取一些權限,而不是安裝時,或者運行彈出權限提醒,人工點擊允許的話!可以在以下這個地方添加默認權限
    在PMS啟動完成后,會由Zygote調用他的systemRead()方法,在此方法內部會調用mDefaultPermissionPolicy.grantDefaultPermissions(userId),也就是給系統app添加默認權限的,進入這個類:

    private void grantDefaultSystemHandlerPermissions(int userId) {// CameraIntent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);獲取apk對應的解析包PackagePackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackageLPr(cameraIntent, userId);if (cameraPackage != null&& doesPackageSupportRuntimePermissions(cameraPackage)) {賦予權限grantRuntimePermissionsLPw(cameraPackage, CAMERA_PERMISSIONS, userId);grantRuntimePermissionsLPw(cameraPackage, MICROPHONE_PERMISSIONS, userId);grantRuntimePermissionsLPw(cameraPackage, STORAGE_PERMISSIONS, userId);} }

    本文博客參考很多博主隔壁老李頭博客,更多細節可以參考其博客!感謝!

    總結

    以上是生活随笔為你收集整理的PMS的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。