主頁 > 移動端開發 > PackageManagerService啟動詳解(一)之整體流程分析

PackageManagerService啟動詳解(一)之整體流程分析

2021-01-29 14:36:59 移動端開發

???PackageManagerService啟動詳解(一)之整體流程分析


Android PackageManagerService系列博客目錄:

PackageManagerService啟動詳解系列博客概要
PackageManagerService啟動詳解(一)之整體流程分析



引言

??終于要拉開PKMS啟動詳解系列博客的序幕了,當然讀者最好能先閱讀一下PackageManagerService啟動詳解系列博客概要對我們的整個系列博客有一個整體的了解,在今天的博客中我們將會從整體上來介紹PKMS的啟動流程,所以我們只會重點關注PKMS的整個啟動流程,讓讀者先從整體上有一個認識,至于在原始碼分析中注釋的代碼內容,讀者可以先有一個概括,在后續的博客中我們會采取庖丁解牛的方式逐一分析,

萬事開頭難,我們只有從整體上對PKMS的啟動有了了解,才能在后續對它涉及的相關流程逐一分解,各個突破取得最后的勝利,所以讀者不要老想著一下子能吃撐胖子,慢慢來!

為了讀者心里有底,我們來個提前亮,先來直接看下PKMS的整體啟動圖,如下:


在這里插入圖片描述


注意:本篇的介紹是基于Android 7.xx平臺為基礎的(并且為了書寫簡便后續PackageManagerService統一簡稱為PKMS),其中涉及的代碼路徑如下:

--- frameworks/base/services/java/com/android/server/SystemServer.java
--- frameworks/base/services/core/java/com/android/server/pm/
	Installer.java
	PackageDexOptimizer.java     
	PackageInstallerService.java
	PackageManagerService.java
	PackageSetting.java 
	Settings.java
	
--- frameworks/base/core/java/com/android/server/SystemConfig.java



一.system_server行程啟動PKMS服務

??對Android系統啟動流程有一定了解的讀者應該知道,Android在啟動程序中會主動創建一個system_server的核心行程,然后system_server行程啟動之后會創建一系列的Java核心服務(對于這一塊還不是很熟悉的讀者,可以參見博客Android核心服務和關鍵行程啟動),而這其中就包括我們的PKMS服務,PKMS服務創建成功之后就會常駐system_server行程,通過Binder遠程呼叫的方式對外提供服務,而我們本文的重點就是介紹Android服務中PKMS服務啟動過,正所謂知己知彼方能百戰百勝,所以我們非常有必要先來看下PKMS服務的相關類圖關系,如下:


在這里插入圖片描述


好了,我們直接來看下SystemServer啟動PKMS的代碼,如下:

//【 SystemServer.java 】
public final class SystemServer {
	private boolean mOnlyCore;//此變數標志是否只加載核心應用
	...
    private void startBootstrapServices() {
    
    	...
    	//此處的Installer用于和Native層的installd進行socket通信,傳遞命令
    	Installer installer = mSystemServiceManager.startService(Installer.class);
		String cryptState = SystemProperties.get("vold.decrypt");//判斷加密模式

		/*
			注意Android正常啟動模式下,不會進入下面的分支,所以mOnlyCore為false
		*/
        if (ENCRYPTING_STATE.equals(cryptState)) {
            mOnlyCore = true;
        } else if (ENCRYPTED_STATE.equals(cryptState)) {
            mOnlyCore = true;
        } else if (mIsAlarmBoot) {
            mOnlyCore = true;
        }
        ...
        
		/*
			注意Android正常啟動模式下,工廠模式為false
		*/
        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);    
        ...	
    }
    ---
}

我們接著繼續來看PMKS的main()靜態方法,其原始碼實作邏輯如下:

//【 PackageManagerService.java 】
public class PackageManagerService extends IPackageManager.Stub {
	...
	public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
	        
	        PackageManagerServiceCompilerMapping.checkProperties();
	
			//構建一個PKMS實體
	        PackageManagerService m = new PackageManagerService(context, installer,
	                factoryTest, onlyCore);
	        m.enableSystemUserPackages();
	        //將PKMS實體添加到ServiceManager中進行管理
	        ServiceManager.addService("package", m);
	        return m;
    }
	...
}

在分析該方法之前,我們先來簡單看下它的四個入參,如下:

  • context:表示system_server行程的背景關系資訊
  • installer:Installer的實體物件,主要用于和Native層的installd進行socket通信,傳遞命令
  • factoryTest:表示是否是工廠測驗模式,在Android正常啟動模式下此值為false
  • onlyCore:表示是否只加載系統核心應用,在Android正常啟動模式下此值也為false

入參引數的含義我們搞懂了,我們接著來看main方法,可以看到它的邏輯比較簡單,主要干了如下兩件事情:

  • 通過main()傳入的引數構建PKMS物件實體
  • 將前面創建的PKMS物件實體添加到ServiceManager進行管理,這樣第三方應用就可以通過Binder呼叫PKMS服務了

可以看到這里的main方法非常簡單,只有短短幾行代碼,但是它的執行時間卻較長!我想對于從事Android系統開發的小伙伴來說,一定有想方設法的縮減這段代碼執行的時間而奮戰過(縮減開機時間!),

至于為啥此處簡簡單單的幾行代碼要耗費如此多的時間,主要原因是 PKMS在其構造方法中做了很多“重體力活”(主要是掃描應用),這也是 Android 啟動速度慢的主要原因之一,

下圖是經過優化之后的PKMS開機執行耗時時間圖,對于這快的時間通常是越短越好!在這里插入圖片描述




二.PKMS的初始化

通過前面system_server行程的一番操作,PKMS被構建了出來,此時進入它的構造方法中,開始新的征程,在它的構造方法中,主要分為五個階段,每個階段通過EventLog列印出來,如下:

//【 PackageManagerService.java 】
public class PackageManagerService extends IPackageManager.Stub {
	...
	public PackageManagerService(Context context, Installer installer,
	        boolean factoryTest, boolean onlyCore) {
		//寫入EventLog,標志著PKMS啟動第一階段
	    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, SystemClock.uptimeMillis());	
	    ...	    
	    synchronized (mInstallLock) {
	        synchronized (mPackages) {
	            
	            ...	 
	            //寫入EventLog,標志著PKMS啟動第二階段           
	            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, startTime);
	            
	            ...	   
	                     
	            if (!mOnlyCore) {
	            	//寫入EventLog,標志著PKMS啟動第三階段 
	                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis());
	            
	            }
	            
	            ...
	            //寫入EventLog,標志著PKMS啟動第四階段 
	            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END, SystemClock.uptimeMillis());
	            
	            ...
	            //寫入EventLog,標志著PKMS啟動第五階段 
	            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis());
	            
	            ...
	        } 
	    }
	    
	    Runtime.getRuntime().gc();
	
	    LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
	}
	...
}

可以看到Android系統根據 EventLog將PKMS的初始化分為以下幾個階段:

  • 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啟動階段

上面的幾個階段,也就我們前面通過logcat查看的標記資訊,我們后續的章節也會以這個EventLog為劃分標準,來說明在PKMS構造方法中每個階段的"歷史重任"!




三.PKMS的啟動PMS_START階段

啥也不多說了,翠花上酸菜!跑題了,直接上原始碼:

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        /************************* PKMS啟動第一階段 *************************/
        //寫入日志,標識PackageManagerService正式開始第一階段的啟動
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());

		//SDK版本檢測
        if (mSdkVersion <= 0) {
            Slog.w(TAG, "**** ro.build.version.sdk not set!");
        }

        mContext = context;

        mPermissionReviewRequired = context.getResources().getBoolean(
                R.bool.config_permissionReviewRequired);

		//開機模式是否為工廠模式,默認為false
        mFactoryTest = factoryTest;
		
        mOnlyCore = onlyCore;//默認為false,標記是否只加載核心服務

		// 用于存盤與顯示屏相關的一些屬性,例如螢屏的寬 / 高尺寸,解析度等資訊
        mMetrics = new DisplayMetrics();

		/*
			 在Settings中,創建packages.xml、packages-backup.xml、packages.list 等檔案物件    
			 這個Settings物件非常重要,我們在后續的分析中會多次看到并使用到它
			 它是Android系統已經安裝Package在記憶體中的資料映射,存盤了譬如已安裝應用的代碼位置,資料位置,簽名等資訊
		*/   
        mSettings = new Settings(mPackages);

		/*
			向Settings實體物件添加system, phone, log, nfc, bluetooth, shell這六種shareUserId到mSettings中,
			后面掃描和安裝有用!
			其中的system對應的shareUserId也就是我們經常所說的對應的Android系統權限
		*/
        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);
	
		//此處具體用途不是很清晰,忽略
        File setFile = new File(AlarmManager.POWER_OFF_ALARM_SET_FILE);
        File handleFile = new File(AlarmManager.POWER_OFF_ALARM_HANDLE_FILE);
        mIsAlarmBoot = SystemProperties.getBoolean("ro.alarm_boot", false);
        if (mIsAlarmBoot) {
			...
        } else if (setFile.exists() && handleFile.exists()) {
			...
        }
		//這塊具體用途不是很清晰,應該是和除錯行程隔離有關
        String separateProcesses = SystemProperties.get("debug.separate_processes");
        if (separateProcesses != null && separateProcesses.length() > 0) {
            if ("*".equals(separateProcesses)) {
                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
                mSeparateProcesses = null;
                Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
            } else {
                mDefParseFlags = 0;
                mSeparateProcesses = separateProcesses.split(",");
                Slog.w(TAG, "Running with debug.separate_processes: "
                        + separateProcesses);
            }
        } else {
            mDefParseFlags = 0;
            mSeparateProcesses = null;
        }
	
		/*
			installer在SystemServer中被構造,這里通過該物件與底層installd進行socket通信
			進行具體安裝與卸載的操作
		*/
        mInstaller = installer;
		//創建PackageDexOptimizer,該類用于輔助進行dex優化
        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                "*dexopt*");
        mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

		//創建OnPermissionChangeListeners物件,用于監聽權限改變!
        mOnPermissionChangeListeners = new OnPermissionChangeListeners(
                FgThread.get().getLooper());

        getDefaultDisplayMetrics(context, mMetrics);


		/*
			構建SystemConfig物件實體(單例模式)
			它主要用于獲取系統配置資訊
 			譬如共享庫,系統feather,權限等
 		*/
        SystemConfig systemConfig = SystemConfig.getInstance();
        mGlobalGids = systemConfig.getGlobalGids();
        mSystemPermissions = systemConfig.getSystemPermissions();
        mAvailableFeatures = systemConfig.getAvailableFeatures();

        mProtectedPackages = new ProtectedPackages(mContext);

        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
			/*
				構建并啟動Handler處理執行緒,用于處理應用的安裝和卸載相關事件的處理
				這里為啥要單獨開辟一個執行緒處理呢,這是因為第三方應用的安裝和卸載是一個比較耗時的操作
			*/
            mHandlerThread = new ServiceThread(TAG,
                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
            mHandlerThread.start();

			//創建一個 PackageHandler 物件,系結前面創建的HandlerThread,處理安裝和卸載
            mHandler = new PackageHandler(mHandlerThread.getLooper());
            mProcessLoggingHandler = new ProcessLoggingHandler();

			 //使用看門狗檢測當前訊息處理執行緒,如果超時則觸發看門狗機制
            Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

			//創建默認權限管理物件,用于給某些預制的 apk 給予權限,也可以撤銷!
            mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);

			/*
				創建需要掃描檢測的目錄檔案物件,為后續掃描做準備!
				這里最最常見的就是/data/app/和/data/app-private/目錄
			*/
            File dataDir = Environment.getDataDirectory();
            mAppInstallDir = new File(dataDir, "app");
            mAppLib32InstallDir = new File(dataDir, "app-lib");
            mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
            mRegionalizationAppInstallDir = new File(dataDir, "app-regional");

			//構造UserManagerService物件,創建用戶管理服務
            sUserManager = new UserManagerService(context, this, mPackages);


            //讀取權限組態檔中的資訊,保存到mPermissions這個ArrayMap中
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                    = systemConfig.getPermissions();
            for (int i=0; i<permConfig.size(); i++) {
                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
                BasePermission bp = mSettings.mPermissions.get(perm.name);
                if (bp == null) {
                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                    mSettings.mPermissions.put(perm.name, bp);
                }
                if (perm.gids != null) {
                    bp.setGids(perm.gids, perm.perUser);
                }
            }

			//獲取并處理所有共享庫資訊
            ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
            for (int i=0; i<libConfig.size(); i++) {
                mSharedLibraries.put(libConfig.keyAt(i),
                        new SharedLibraryEntry(libConfig.valueAt(i), null));
            }
			//嘗試讀取mac_permissions.xml中的selinux資訊
            mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

			/*
				讀取檔案package.xml的內容,決議后存入mSettings的mPackages中
				并且根據決議的結果判斷是否是第一次啟動,如果是第一次開機,那么是沒有相關資料的
			*/
			mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

            /* 
            	移除哪些codePath無效的Package(該Pacakge是系統App升級過來的被安裝在/data/app磁區)
            	此時需要恢復處于system目錄下的同名package
            */
            final int packageSettingCount = mSettings.mPackages.size();
            for (int i = packageSettingCount - 1; i >= 0; i--) {
                PackageSetting ps = mSettings.mPackages.valueAt(i);
                if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
                        && mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
                    mSettings.mPackages.removeAt(i);
                    mSettings.enableSystemPackageLPw(ps.name);
                }
            }

            if (mFirstBoot) {
                /* 
                	如果是第一次開機,從另外一個系統拷貝 odex 檔案到當前系統的 data 磁區
         		 	Android 7.1 引進了 AB 升級,這個是 AB 升級的特性,可以先不看!
         		 */
                requestCopyPreoptedFiles();
            }
			
			//判斷是否自定義的決議界面(存在多個滿足添加的Activiyt,彈出的選擇界面的那個)
            String customResolverActivity = Resources.getSystem().getString(
                    R.string.config_customResolverActivity);
            if (TextUtils.isEmpty(customResolverActivity)) {
                customResolverActivity = null;
            } else {
                mCustomResolverComponentName = ComponentName.unflattenFromString(
                        customResolverActivity);
            }

			//獲取掃描開始的時間
            long startTime = SystemClock.uptimeMillis();
            ...

這里可以看到PKMS啟動的第一階段,原始碼洋洋灑灑就有上百行,不要害怕,不要灰心,更加不要放棄,我們先了解,后熟悉,再掌握,PKMS的知識點侄訓被我攻破的!

這里我們看到了在PKMS啟動的PMS_START階段,做了非常多的初始化作業,主要是為了后續掃描作業做準備,而這些初始化作業是非常重要也是必要的,這里我們挑出重點的一些初始化來先熟悉熟悉:

  • 首先Settings實體物件,該實體物件非常重要,它主要用來記錄上次Android終端已安裝應用相關資訊的一個管理類,如果Android終端設備是第一次啟動(如果沒有發生例外的情況)則會創建packages.xml、packages-backup.xml、packages.list等檔案,同時添加 system, phone, log, nfc, bluetooth, shell 這六種 shareUserId 到mSettings中進行管理,該物件被初始化成功之后,后面的掃描安裝都會被用到,如果應用的情況(即有新的應用安裝,卸載,升級)發生變化,該物件也會進行相應的更新
  • 初始mInstaller物件,該實體物件主要用于和Native層的installed通過socket進行互動,處理應用的安裝和卸載
  • 創建 PackageDexOptimizer物件,該物件主要用于對應用進行相關的dex優化操作
  • 創建OnPermissionChangeListeners物件,該物件主要用于監聽權限改變
  • 創建SystemConfig物件實體,它主要用于獲取系統配置資訊,譬如共享庫,系統feather,權限等
  • 構建一個ServiceThread物件實體以及關聯的PackageHandler實體物件,用來處理應用的安裝和卸載相應事物的處理
  • 創建用戶管理服務UserManagerService實體物件
  • 接著通過前面創建的Settings物件實體和SystemConfig物件實體處理已安裝應用的相關權限資訊
  • 呼叫SystemConfig實體物件相關資訊構建系統共享庫

初始化階段干的事情挺多的,這里就不細究了,后續會由專門的篇章來詳細分析的,




四.PKMS的啟動PMS_SYSTEM_SCAN_START階段

經過前面階段的準備,PKMS服務相關的初始化環境已經構建OK,現在要開始真正干活開始系統掃描階段了:

			...//第一階段相關原始碼
			
			/************************* PKMS啟動第二階段 *************************/
			//寫入開始掃描系統應用的日志
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
                    startTime);

           
            /*
            	設定掃描的引數,這些引數比較重要,因為后續系統掃描階段和data磁區掃描階段
            	呼叫的是同一套代碼,其中的許多邏輯是通過掃描引數來區分呼叫的
            */
            final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;

			//獲取Java啟動類別庫的路徑,可以通過echo $BOOTCLASSPATH查看
			final String bootClassPath = System.getenv("BOOTCLASSPATH");
			//獲取systemServer的路徑
            final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");

            if (bootClassPath == null) {
                Slog.w(TAG, "No BOOTCLASSPATH found!");
            }

            if (systemServerClassPath == null) {
                Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
            }

			/*
				獲得系統指令集合,這取決于當前Android終端是32位還是64位的
				通常的指令集有如下幾種arm64-v8a,armeabi-v7a,armeabi
			*/
            final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();
            final String[] dexCodeInstructionSets =
                    getDexCodeInstructionSets(
                            allInstructionSets.toArray(new String[allInstructionSets.size()]));


            /*
            	確保所有的共享庫都被dexopt優化
            	至于這個共享庫有那些要具體以Android終端情況為準,在不同平臺有不同情況
            */
            if (mSharedLibraries.size() > 0) {
                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                    for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
                        final String lib = libEntry.path;
                        if (lib == null) {
                            continue;
                        }
                        try {
                            //判斷共享庫是否需要執行odex操作
                            int dexoptNeeded = DexFile.getDexOptNeeded(
                                    lib, dexCodeInstructionSet,
                                    getCompilerFilterForReason(REASON_SHARED_APK),
                                    false /* newProfile */);
							// 如果需要odex操作,對共享庫進行一次預編譯(AOT)
                            if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
								// 呼叫install的dexopt命令,優化后的檔案放在/data/dalvik-cache/下面
                                mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,
                                        dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/,
                                        getCompilerFilterForReason(REASON_SHARED_APK),
                                        StorageManager.UUID_PRIVATE_INTERNAL,
                                        SKIP_SHARED_LIBRARY_CHECK);
                            }
                        } catch (FileNotFoundException e) {
                            Slog.w(TAG, "Library not found: " + lib);
                        } catch (IOException | InstallerException e) {
                            Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
                                    + e.getMessage());
                        }
                    }
                }
            }

            File frameworkDir = new File(Environment.getRootDirectory(), "framework");
			//獲取系統版本相關資訊
            final VersionInfo ver = mSettings.getInternalVersion();
			
			/*
				判斷當前Android終端是否有進行版本OTA升級,如果當前版本的指紋與歷史版本的指紋資訊不一致
				表示當前版本是一次OTA升級上來更新版本(此處指的是版本升級,而不是通常的韌體OTA更新)
			*/
            mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);

            // when upgrading from pre-M, promote system app permissions from install to runtime
            /*
            	對于舊版本升級的情況,將安裝時獲取權限變更為運行時申請權限
            	對于Android M之前版本升級上來的情況,需要將系統應用程式權限從安裝升級到運行時
            */
            mPromoteSystemApps =
                    mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;


            /*
            	 判斷當前的Android終端的版本是不是從N升級上來的
            	 對于Android N之前版本升級上來的情況,需像首次啟動一樣處理Package
            */
            mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;

			//判斷是否從Android 6.0升級上來額
            mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;


            /*
            	 在掃描之前保存Android 6.0系統已經安裝的軟體包的資訊,
            	 我們不希望自動授予系統里新的應用程式的運行時權限
			*/
            if (mPromoteSystemApps) {
                Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();
                while (pkgSettingIter.hasNext()) {
                    PackageSetting ps = pkgSettingIter.next();
                    if (isSystemApp(ps)) {
                        mExistingSystemPackages.add(ps.name);
                    }
                }
            }


            /*
            	對于包的掃描,執行的是先到先得,先被掃描到的檔案,就是最終被用到的檔案
            	當然前提是得經過一定的刷選合適的才可以
            */

			// 掃描目錄/vendor/overlay下的設備OEM廠商的應用包!
            String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);
            if (!overlayThemeDir.isEmpty()) {
                scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags
                        | PackageParser.PARSE_IS_SYSTEM
                        | PackageParser.PARSE_IS_SYSTEM_DIR
                        | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
            }
            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

             /*
             	掃描目錄/system/framework下的應用包
             	通常該目錄下只有一個被掃描的應用包framework-res.apk
             */
            scanDirTracedLI(frameworkDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED,
                    scanFlags | SCAN_NO_DEX, 0);


            // 掃描/system/priv-app下的應用包
            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
            scanDirTracedLI(privilegedAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

            // 掃描/system/app下面的應用包
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            scanDirTracedLI(systemAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            // 掃描/vendor/app目錄下的應用包
            File vendorAppDir = new File("/vendor/app");
            try {
                vendorAppDir = vendorAppDir.getCanonicalFile();
            } catch (IOException e) {
                
            }
            scanDirTracedLI(vendorAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

             // 掃描/oem/app目錄下的應用包
            final File oemAppDir = new File(Environment.getOemDirectory(), "app");
            scanDirTracedLI(oemAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

            // 掃描運營商的資源包中收集所有區域化軟體包,我們忽略
            if (RegionalizationEnvironment.isSupported()) {
				...
            }

          
            /*
            	 洗掉任何已經不再存在的系統包
            	 這類List表示的是有可能有升級包的系統應用,注意是可能,所以還需要判斷一番
           	*/
            final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
            if (!mOnlyCore) {//正常啟動會走入該分支
				 // 遍歷上一次安裝的資訊!
                Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
				while (psit.hasNext()) {
                    PackageSetting ps = psit.next();


                    //忽略掉非系統應用,即這個邏輯最對系統應用進行處理
                    if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                        continue;
                    }


                     /*
                      	注意這里的mPackages保存的是前面呼叫scanDirLI方法掃描目錄得到的應用資訊
                      	不要和mSettings.mPackages弄混了,它保存的是前面一次啟動掃描完成之后的應用安裝資訊
                     */
                    final PackageParser.Package scannedPkg = mPackages.get(ps.name);
                    if (scannedPkg != null) {

                         /*
                          如果該被掃描的應用存在于Settings的實體的disable串列中,那么,
                          說明它是有通過OTA方式進行升級更新添加的,因此,清除相應資料以便后續覆寫安裝在data磁區中的
                          能有機會掃描到
                          
						  說明一定是通過覆寫更新的,移除之前掃描的結果,保證之前用戶安裝的應用能夠被掃描!
						  “disable” 串列是package.xml中<update-package>標簽標示的應用
						 */
						if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
                            logCriticalInfo(Log.WARN, "Expecting better updated system app for "
                                    + ps.name + "; removing system app.  Last known codePath="
                                    + ps.codePathString + ", installStatus=" + ps.installStatus
                                    + ", versionCode=" + ps.versionCode + "; scanned versionCode="
                                    + scannedPkg.mVersionCode);
							// 從掃描串列mPackages中移除
                            removePackageLI(scannedPkg, true);
							// 放入mExpectingBetter串列,后面會進行處理的,
                            mExpectingBetter.put(ps.name, ps.codePath);
                        }
						//跳出回圈,確保不會被洗掉
                        continue;
                    }
					/*
						注意此處被執行的前提是scannedPkg為null,即當前在系統中沒有被掃描到
						運行到這里說明ps表示的應用不在被掃描串列mPackages中,也就是在系統中不存在
						
						出現這種情況很大可能是人為在root之后洗掉了,后者通過OTA升級把系統目錄下某個應用干掉了
					*/
                    if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
                        psit.remove();
                        logCriticalInfo(Log.WARN, "System package " + ps.name
                                + " no longer exists; it's data will be wiped");
    
                    } else {
                         /* 
                         	如果這個系統應用當前已經不在系統中,但是卻在被標記為<update-package>的不可用串列中
                        	則將其加入到possiblyDeletedUpdatedSystemApps串列中待掃描完普通應用目錄再進行處理
							
							出現這種情況很大可能是人為在root之后洗掉了,后者通過OTA升級把系統目錄下某個應用干掉了
                        */
                        final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
                        if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
                            possiblyDeletedUpdatedSystemApps.add(ps.name);
                        }
                    }
                }
            }

            
            /*
            	清理掉所有安裝不完全的應用包
            	至于為啥安裝不完全,這個可能的原因劉比較傴了
            */
            ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
            for (int i = 0; i < deletePkgsList.size(); i++) {
                final String packageName = deletePkgsList.get(i).name;
                logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + packageName);
                synchronized (mPackages) {
                    mSettings.removePackageLPw(packageName);
                }
            }

            //delete tmp files
            //  洗掉臨時檔案
            deleteTempPackageFiles();

            // Remove any shared userIDs that have no associated packages
             // 洗掉掉Settings中的沒有關聯任何應用的SharedUserSetting物件
            mSettings.pruneSharedUsersLPw();

到這里PKMS啟動的第二階段PMS_SYSTEM_SCAN_START到這里就告一段落了,對于最后對掃描完成之后的對掃描結果的一些處理,讀者朋友不要強硬的去理解其中的邏輯處理,一定要搞清楚系統應用的升級方法,和對已安裝應用資料的存盤的packages.xml的構成,

此處沒有搞明白沒有關系,不要灰心,相信自己,并且后續會由專門章節來分析的,

PKMS啟動的第二階段PMS_SYSTEM_SCAN_START我們大致過了下,我們來簡單總結一下該階段的主要流程如下:

  • 首先是獲取BOOTCLASSPATH對應的Java啟動類別庫的路徑和以及SYSTEMSERVERCLASSPATH對應的systemServer路徑

這里我們可以在Android終端中通過執行env或者echo $var進行查看相關的取值,并且我有寫過專門的一篇博客來介紹可以詳見博客Android獲取和設定系統環境變數指南,

在這里插入圖片描述

  • 接著獲取當前Android系統當前支持的系統指令集,至于當前你的Android系統支持那些,這個得根據具體的情況來確定,譬如32位機器,還是64位機器等,

我們可以通過shell命令進行簡單查看,當前Android終端支持那些相關的abi指令集,如下:

在這里插入圖片描述

  • 對第一階段從SystemConfig實體物件中回傳的共享庫,進行dex優化操作,優化操作之后額共享庫會在/data/dalvik-cache之下

至于我們當前終端有那些共享庫,我們也可以在終端下執行shell命令進行查看,這個每個平臺,每個芯片廠商都會有所不同,根據具體情況而定,

在這里插入圖片描述
被優化之后的相關jar包和共享庫
在這里插入圖片描述

  • 判斷當前Android版本是否是通過OTA升級來的,如果是從7.0之前版本升級過來的,需要對之前安裝的應用做一些特殊處理,這一塊主要是涉及到運行時權限有關

  • 正式開始掃描系統級應用目錄,這主要包括如下幾個目錄:

    • /vendor/overlay
    • /system/framework
    • /system/priv-app
    • /system/app
    • /vendor/app
    • /oem/app

掃描指定目錄下的apk檔案,這個是PKMS的三大核心功能,它會通過呼叫PackageParser來完成對應用App中Apk的AndroidManifest.xml的檔案的決議,生成 Application,activity,service,broadcast,provider等四大組件資訊,并將上述決議得到的四大組件資訊注冊到PKMS中,供Android系統查詢并使用,

  • 對掃描到的系統App,并且存在升級情況的App做特殊處理,暫時將其從當前掃描串列中洗掉,放入mExpectingBetter中,待掃描完非系統應用安裝目錄后再進行處理

這里可能存在如情況,就是升級完成的app被例外洗掉了,比如root之后手動洗掉了,或者其它的例外情況,網上有些說可能是OTA格式化了data磁區,這個很明顯不成立,如果都清除了data磁區,Settings實體怎么可能從data磁區中讀取packages.xml的資訊呢,

  • 清除那些已經不存在的系統應用包相關資訊,為后續掃描/data/app目錄準備

這里可能存在如下情況:
1.就是系統應用覆寫升級完成后,系統內最初的的app應用被洗掉了,比如root之后手動洗掉了
2.就是系統應用覆寫升級完成后,然后OTA升級韌體,新的韌體將該系統app洗掉了

  • 清理所有安裝不完全的應用包
  • 移除臨時檔案
  • 移除沒有和應用程式包相關聯的共享用戶id



五.PKMS的啟動PMS_DATA_SCAN_START階段

前面戰場清掃完畢,現在開始進入PKMS的PMS_DATA_SCAN_START階段,馬上投入戰斗,戰斗模式開啟:

	...//PKMS第二階段啟動相關代碼
			//開始處理非系統應用
            if (!mOnlyCore) {//Android正常啟動模式下,會進入此分支
            	//寫入相關階段啟動日志
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());

				/*
					掃描/data目錄下相關的第三方應用安裝目錄
					這里重點要注意掃描引數有發生了變化,在后續的掃描方法中會根據這個引數分貝進行不
					同的處理邏輯
				*/
                scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

                scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags
                        | PackageParser.PARSE_FORWARD_LOCK,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);

                scanDirLI(mEphemeralInstallDir, mDefParseFlags
                        | PackageParser.PARSE_IS_EPHEMERAL,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);

                // Collect all Regionalization 3rd packages.
                if (RegionalizationEnvironment.isSupported()) {
                    Log.d(TAG, "Load Regionalization 3rd apks from res packages.");
                    final List<String> packages = RegionalizationEnvironment.getAllPackageNames();
                    for (String pack : packages) {
                        File appFolder = new File(mRegionalizationAppInstallDir, pack);
                        Log.d(TAG, "Load Regionalization 3rd apks of path " + appFolder.getPath());
                        scanDirLI(appFolder, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
                    }
                }
                 
                /* 
                	進行最后的data磁區掃描的收尾作業
                	放在possiblyDeletedUpdatedSystemApps中的應用是在packge.xml中被標記成了禁用的已經升級了的系統應用
                    但是前面在掃描系統目錄下卻發現檔案卻不存在了,因此這里檢查用戶目錄下升級檔案是否還存在,然后進行處理
                */
                for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
                    PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
					// 從mSettings.mDisabledSysPackages變數中移除去此應用
                    mSettings.removeDisabledSystemPackageLPw(deletedAppName);

                    String msg;
                    if (deletedPkg == null) {
						/* 
							用戶目錄中也沒有升級包,則肯定是殘留的應用資訊,則把它的資料目錄洗掉掉
							此時無任何掃描結果,表明這個系統中已經沒有改apk了,那就刪掉它
						*/
                        msg = "Updated system package " + deletedAppName
                                + " no longer exists; it's data will be wiped";
                    } else {
                    	/*
                    		此時表明系統App,覆寫安裝的應用在data下面還存在,但是因為系統中的應用不存在了,所以
                    		此時的data磁區的應用應該要降級成為普通的第三方應用
                    	*/
                        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);
                }
                
                 /**
					確保所有在用戶data磁區的應用都顯示出來了,
					如果data磁區的無法顯示,就顯示system磁區的
                 
                 	現在來處理mExpectingBetter串列,這個串列的應用是帶有升級包的系統的應用,
                 	前面把他們從mPackages串列中清除了并放到mExpectingBetter串列
                 	最后也對它們進行掃描處理
                 */
                for (int i = 0; i < mExpectingBetter.size(); i++) {
                    final String packageName = mExpectingBetter.keyAt(i);
					/* 
						如果PMS仍然沒有掃描到mExpectingBetter串列中的apk,說明data磁區的apk無法顯示
						出現這種情況的原因,可能是由于OTA或者例外導致data磁區的覆寫安裝的應用已經丟失了
        			 	那就要顯示原來system磁區的apk!
        			 */
                    if (!mPackages.containsKey(packageName)) {
                        final File scanFile = mExpectingBetter.valueAt(i);

                        logCriticalInfo(Log.WARN, "Expected better " + packageName
                                + " but never showed up; reverting to system");

                        int reparseFlags = mDefParseFlags;
						//確保應用位于下面幾個系統應用目錄,如果不在,則不需要處理
                        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;
                        }
                        /*
                          現在把這個apk標示為系統應用,從mSettings.mDisabledSysPackages中洗掉,
                          因為在scanDirLI->scanPackageLI中會執行mSettings.disableSystemPackageLPw
                          
                          所以此時包名的標簽是只有<update-package>,執行到這步之后變成<package>標簽,
                        */
                        mSettings.enableSystemPackageLPw(packageName);

                        try {
							 // 重新掃描下系統磁區的該應用
                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
                        } catch (PackageManagerException e) {
                            Slog.e(TAG, "Failed to parse original system package: "
                                    + e.getMessage());
                        }
                    }
                }
            }
			//清空mExpectingBetter串列
            mExpectingBetter.clear();


            // 獲得存盤管理物件!
            mStorageManagerPackage = getStorageManagerPackageName();


            // 獲得開機向導應用
            mSetupWizardPackage = getSetupWizardPackageName();
            if (mProtectedFilters.size() > 0) {
                if (DEBUG_FILTERS && mSetupWizardPackage == null) {
                    Slog.i(TAG, "No setup wizard;"
                        + " All protected intents capped to priority 0");
                }
                for (ActivityIntentInfo filter : mProtectedFilters) {
                    if (filter.activity.info.packageName.equals(mSetupWizardPackage)) {
                        if (DEBUG_FILTERS) {
                            Slog.i(TAG, "Found setup wizard;"
                                + " allow priority " + filter.getPriority() + ";"
                                + " package: " + filter.activity.info.packageName
                                + " activity: " + filter.activity.className
                                + " priority: " + filter.getPriority());
                        }
                        // skip setup wizard; allow it to keep the high priority filter
                        continue;
                    }
                    Slog.w(TAG, "Protected action; cap priority to 0;"
                            + " package: " + filter.activity.info.packageName
                            + " activity: " + filter.activity.className
                            + " origPrio: " + filter.getPriority());
                    filter.setPriority(0);
                }
            }
            mDeferProtectedFilters = false;
            mProtectedFilters.clear();


            // 更新所有應用的動態庫路徑,保證他們有正確的共享庫路徑
            updateAllSharedLibrariesLPw();

			// 調整所有共享uid的package的指令集!
            for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
                adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
                        false /* boot complete */);
            }

			// 到這里,系統中所有的package都被掃描刀了,這里是更新他們上一次的掃描的相關資訊
            mPackageUsage.read(mPackages);
            mCompilerStats.read();	

到這里PKMS啟動的第三階段PMS_DATA_SCAN_START到這里就告一段落了,對于最后對掃描完成之后的對掃描結果的一些處理,讀者朋友不要強硬的去理解其中的邏輯處理,一定要搞清楚系統應用的升級方法,和對已安裝應用資料的存盤的packages.xml的構成,可以看到對于系統升級應用的處理PKMS是特別有耐心的,

此處沒有搞明白沒有關系,不要灰心,相信自己,并且后續會由專門章節來分析的,

PKMS啟動的第三階段PMS_DATA_SCAN_START我們大致過了下,我們來簡單總結一下該階段的主要流程如下:

  • 掃描非系統應用磁區目錄下的應用,主要包括如下幾個目錄:

    • /data/app
    • /data/app-asec
    • /data/app-ephemeral
    • /data/app-private
  • 在系統應用和非系統應用掃描完成之后,會最后來統一處理前面一次掃描的系統App不存在的情況,或者前面一次掃描系統App存在覆寫升級安裝在data目錄下的應用,但是這次掃描沒有存在則需要重新決議system磁區的package

這里可以看到對于系統應用被洗掉,或者被覆寫升級的情況,PKMS處理起來是非常謹慎和細致的,這個也是剛開始讀者學習的時候讓人有點迷糊的地方,

  • 更新所有應用的動態庫路徑,保證他們有正確的共享庫路徑
  • 調整所有共享uid 的package的指令集
  • 系統中所有的package都被掃描刀了,最后是更新上一次的掃描的相關資訊,即將最新的掃描結果寫入packages.xml中



六.PKMS的啟動BOOT_PROGRESS_PMS_SCAN_END階段

PKMS的PMS_DATA_SCAN_START階段戰役結束,我們一鼓作氣,不做耽擱,還有三十秒抵達新的戰場開啟PKMS的BOOT_PROGRESS_PMS_SCAN_END階段作業,啥也不多說了直接抄起家伙開干!

			...//PKMS啟動前面階段相關原始碼

			//寫入關鍵日志,表明PKMS執行到了什么階段
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                    SystemClock.uptimeMillis());
                    
			/*
				輸出掃描總共消耗時間,很多Android人為了這個時間段嘔心瀝血
			*/
            Slog.i(TAG, "Time to scan packages: "
                    + ((SystemClock.uptimeMillis()-startTime)/1000f)
                    + " seconds");


            
            /* 
            	如果平臺的SDK版本和上次啟動時候發生了變化,可能permission的定義也發生了變化,因此需要重新賦予應用權限
            	這里會有一些安全問題,就是可能會有應用通過這種方式獲取那些用戶沒有顯式允許的權限
            	
				這里可能后續google會改善,說不定是Android的小哥哥是為了后續的API考核特意留下來的作業量呢
            */
            int updateFlags = UPDATE_PERMISSIONS_ALL;
            if (ver.sdkVersion != mSdkVersion) {
                Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
                        + mSdkVersion + "; regranting permissions for internal storage");
                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
            }

			//更新系統中的權限和權限樹,移除無效的權限和權限樹,同時更新應用的權限授予情況
            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
            ver.sdkVersion = mSdkVersion;


            
            /* 
            	如果這是第一次開機或從Anroid M之前的版本升級上來的,然后我們需要初始化默認應用程式給所有的系統用戶
            */
            if (!onlyCore && (mPromoteSystemApps || mFirstBoot)) {
                for (UserInfo user : sUserManager.getUsers(true)) {
                    mSettings.applyDefaultPreferredAppsLPw(this, user.id);
                    applyFactoryDefaultBrowserLPw(user.id);
                    primeDomainVerificationsLPw(user.id);
                }
            }


            // 在啟動完成前,為系統用戶準備檔案存盤,因為很多核心的系統比如設定,系統界面等等會提前啟動!
            final int storageFlags;

			/*
				StorageManager.isFileEncryptedNativeOrEmulated() 
				方法用于判斷系統是否運行在檔案加密模式,
				如果是檔案加密模式的話,storageFlags只有StorageManager.FLAG_STORAGE_DE
			*/
            if (StorageManager.isFileEncryptedNativeOrEmulated()) {
                storageFlags = StorageManager.FLAG_STORAGE_DE;
            } else {
                storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
            }
			/*
				準備資料目錄,這個地方也是有故事的
				當我們的Android終端是第一次開機的時候,掃描完成系統應用之后,肯定系統應用是沒有data資料目錄的
				所以此時該方法就派上用場了,會創建相關的data資料目錄
			*/
            reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM,
                    storageFlags);


            /*
            	 如果是執行OTA后的第一次正常啟動,需要清除代碼cache快取目錄
           	*/
            if (mIsUpgrade && !onlyCore) {
                Slog.i(TAG, "Build fingerprint changed; clearing code caches");
                for (int i = 0; i < mSettings.mPackages.size(); i++) {
                    final PackageSetting ps = mSettings.mPackages.valueAt(i);
                    if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
                        // No apps are running this early, so no need to freeze
                        clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
                                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE
                                        | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
                    }
                }
                ver.fingerprint = Build.FINGERPRINT;
            }

			//檢查默認的瀏覽器
            checkDefaultBrowser();

          
            // 當權限和其他默認設定被更新后,執行清除操作
            mExistingSystemPackages.clear();
            mPromoteSystemApps = false;

            // 更新系統資料庫版本號!
            ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;


            // 把Settings的內容保存到packages.xml中去
            mSettings.writeLPr();


            // 如果是第一次開機,或者是系統升級,對核心的系統應用執行odex操作!
			if ((isFirstBoot() || isUpgrade() || VMRuntime.didPruneDalvikCache()) && !onlyCore) {
                long start = System.nanoTime();
                List<PackageParser.Package> coreApps = new ArrayList<>();
                for (PackageParser.Package pkg : mPackages.values()) {
                    if (pkg.coreApp) {
                        coreApps.add(pkg);
                    }
                }

                int[] stats = performDexOptUpgrade(coreApps, false,
                        getCompilerFilterForReason(REASON_CORE_APP));

                final int elapsedTimeSeconds =
                        (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);
                MetricsLogger.histogram(mContext, "opt_coreapps_time_s", elapsedTimeSeconds);

                if (DEBUG_DEXOPT) {
                    Slog.i(TAG, "Dex-opt core apps took : " + elapsedTimeSeconds + " seconds (" +
                            stats[0] + ", " + stats[1] + ", " + stats[2] + ")");
                }
            }

這里我們可以看到在PKMS的BOOT_PROGRESS_PMS_SCAN_END階段主要是對掃描結束之做一些收尾作業,其大致的相關邏輯如下:

  • 如果當前的Android版本是通過OTA升級上來的,需要對相關的應用的權限做一些動態調整
  • 做一些相關的清理作業,并把最新的Settings的內容保存到packages.xml中去
  • 呼叫reconcileAppsDataLI()方法處理應用資料目錄,假如被掃描的應用沒有對應的應用資料目錄,則會進行創建和處理

這里的應用資料目錄,即/data/data/xxx目錄,這個地方讀者注意一下即可,

  • 如果當前的Android終端是第一次開機,對Android核心的應用做odex的優化處理



七.PKMS的啟動BOOT_PROGRESS_PMS_READY階段

真心肝不動了,先讓我緩緩!PKMS的啟動的最后一個征程BOOT_PROGRESS_PMS_READY階段,完成此階段意味著PKMS向Android世界宣告我已經準備好了,有本事放開那女孩沖我來好了,我們來看下這個階段,它干了些啥:

			...//前面階段原始碼
			
			//寫入PKMS啟動階段關鍵日志
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                    SystemClock.uptimeMillis());

            if (!mOnlyCore) {
                mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
                mRequiredInstallerPackage = getRequiredInstallerLPr();
                mRequiredUninstallerPackage = getRequiredUninstallerLPr();
                mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
                mIntentFilterVerifier = new IntentVerifierProxy(mContext,
                        mIntentFilterVerifierComponent);
                mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
                        PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES);
                mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
                        PackageManager.SYSTEM_SHARED_LIBRARY_SHARED);
            } else {
                mRequiredVerifierPackage = null;
                if (mOnlyPowerOffAlarm) {
                    mRequiredInstallerPackage = getRequiredInstallerLPr();
                } else {
                    mRequiredInstallerPackage = null;
                }
                mRequiredUninstallerPackage = null;
                mIntentFilterVerifierComponent = null;
                mIntentFilterVerifier = null;
                mServicesSystemSharedLibraryPackageName = null;
                mSharedSystemSharedLibraryPackageName = null;
            }

			//創建PackageInstallerService物件實體,這個物件主要用于第三方應用安裝app使用
            mInstallerService = new PackageInstallerService(context, this);

            final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
            final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr();
            if (ephemeralInstallerComponent != null && ephemeralResolverComponent != null) {
                if (DEBUG_EPHEMERAL) {
                    Slog.i(TAG, "Ephemeral activated; resolver: " + ephemeralResolverComponent
                            + " installer:" + ephemeralInstallerComponent);
                }
                mEphemeralResolverComponent = ephemeralResolverComponent;
                mEphemeralInstallerComponent = ephemeralInstallerComponent;
                setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
                mEphemeralResolverConnection =
                        new EphemeralResolverConnection(mContext, mEphemeralResolverComponent);
            } else {
                if (DEBUG_EPHEMERAL) {
                    final String missingComponent =
                            (ephemeralResolverComponent == null)
                            ? (ephemeralInstallerComponent == null)
                                    ? "resolver and installer"
                                    : "resolver"
                            : "installer";
                    Slog.i(TAG, "Ephemeral deactivated; missing " + missingComponent);
                }
                mEphemeralResolverComponent = null;
                mEphemeralInstallerComponent = null;
                mEphemeralResolverConnection = null;
            }

            mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
        } // synchronized (mPackages)
        } // synchronized (mInstallLock)

		//進行資源回收
        Runtime.getRuntime().gc();


        mInstaller.setWarnIfHeld(mPackages);

		//添加一個本地服務,供system_server行程內部使用
        LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());

在該階段關鍵性作業不多,主要是一些掃尾作業,但是在這里創建了一個比較重要的匿名Binder服務物件PackageInstallerService實體,它主要用于第三方應用安裝app使用,這個會在后續第三方應用安裝的流程中分析到,




總結

??至此PKMS的啟動階段我們就簡單的介紹完畢了,讀者可能發現了雖然這里只是簡簡單單的概括了一下,就會發現它的作業量之巨大,不過讀者也不要灰心,慢慢來,一個階段一個階段的開墾,相信自己總會拿下相關山頭的,這里我們對PKMS啟動各個階段就不總結了,因為前面已經在各個章節的最后有總結了一下,雖然在本篇博客中我們沒有深入各個階段的原始碼詳細研究但是我們已經對整體流程有了一個整體的把握,后續就是一些的研究了,正是通過PKMS在啟動階段的作業,Android終端開機開機以后才能從磚頭進入豐富多彩的Android世界,然后PKMS才能向Android世界提供各個應用的相關資訊,應用的四大組件才能被Android世界所知悉并且使用,而這些也奧秘也是我們在后續博客中會一一揭曉的,

好了,到這里PackageManagerService啟動詳解(一)之整體流程分析就告一段落了,各位青山不改綠水長流,各位江湖見!當然各位讀者的點贊和關注是我寫作路上前進的最大動力了,如果有啥不對或者不爽的也可以踩一踩也無妨!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/254093.html

標籤:其他

上一篇:vue結合elementUI專案 請求失敗(狀態碼非200)時,直接提示“系統例外,請聯系管理員”

下一篇:Android Studio RecyclerVIew的基本使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more