金川做網(wǎng)站公司網(wǎng)絡推廣代運營公司
Android 系統(tǒng)源碼源碼-應用安裝過程
Android 中應用安裝的過程就是解析 AndroidManifest.xml 的過程,系統(tǒng)可以從 Manifest 中得到應用程序的相關信息,比如 Activity、Service、Broadcast Receiver 和 ContentProvider 等。這些工作都是由 PackageManageService 負責的,也就是所謂的 PMS. 它跟 AMS 一樣都是一種遠程的服務,并且都是在系統(tǒng)啟動 SystemServer 的時候啟動的。下面我們通過源代碼來分析下這個過程。
1、啟動 PMS 的過程
系統(tǒng)在啟動 SystemServer 的過程會啟動 PMS,系統(tǒng)的啟動過程可以參考下面這篇文章學習,
Android 系統(tǒng)源碼-1:Android 系統(tǒng)啟動流程源碼分析
在啟動 SystemServer 的時候會調用 startBootstrapServices()
方法啟動引導服務。PMS 就是在這個方法中啟動的,
private void startBootstrapServices() {// ...mPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);mFirstBoot = mPackageManagerService.isFirstBoot();mPackageManager = mSystemContext.getPackageManager();// ...}
可以看出,系統(tǒng)是通過調用 PMS 的 main 方法來將其啟動起來的。其 main 方法會先實例化一個 PMS 對象,然后調用 ServiceManager 的靜態(tài)方法將其注冊到 ServiceManager 中進行管理。
public static PackageManagerService main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {PackageManagerServiceCompilerMapping.checkProperties();PackageManagerService m = new PackageManagerService(context, installer,factoryTest, onlyCore);m.enableSystemUserPackages();ServiceManager.addService("package", m);final PackageManagerNative pmn = m.new PackageManagerNative();ServiceManager.addService("package_native", pmn);return m;}
當我們需要使用 PMS 解析 APK 的時候就會從 ServiceManager 中獲取。
在 PMS 的構造方法中有許多工作要完成。一個 APK 安裝的主要分成下面幾個步驟,
- 拷貝文件到指定的目錄:默認情況下,用戶安裝的 APK 首先會被拷貝到
/data/app
目錄下,/data/app
目錄是用戶有權限訪問的目錄,在安裝 APK 的時候會自動選擇該目錄存放用戶安裝的文件,而系統(tǒng)的 APK 文件則被放到了/system
分區(qū)下,包括/system/app
,/system/vendor/app
,以及/system/priv-app
等等,該分區(qū)只有 ROOT 權限的用戶才能訪問,這也就是為什么在沒有 Root 手機之前,我們沒法刪除系統(tǒng)出場的 APP 的原因了。 - 解壓縮 APK,拷貝文件,創(chuàng)建應用的數(shù)據(jù)目錄:為了加快 APP 的啟動速度,APK 在安裝的時候,會首先將 APP 的可執(zhí)行文件 dex 拷貝到
/data/dalvik-cache
目錄,緩存起來。然后,在/data/data/
目錄下創(chuàng)建應用程序的數(shù)據(jù)目錄 (以應用的包名命名),存放在應用的相關數(shù)據(jù),如數(shù)據(jù)庫、XML 文件、Cache、二進制的 so 動態(tài)庫等。 - 解析 APK 的 AndroidManifest.xml 文件。
public PackageManagerService(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {// ....synchronized (mInstallLock) {synchronized (mPackages) {// Expose private service for system components to use.LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());sUserManager = new UserManagerService(context, this,new UserDataPreparer(mInsstaller, mInstallLock, mContext, mOnlyCore), mPackages);mPermissionManager = PermissionManagerService.create(context,new DefaultPermissionGrantedCallback() {@Overridepublic void onDefaultRuntimePermissionsGranted(int userId) {synchronized(mPackages) {mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);}}}, mPackages /*externalLock*/);mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);}}// ...mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*");DexManager.Listener dexManagerListener = DexLogger.getListener(this, installer, mInstallLock);mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock, dexManagerListener);mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);// ...synchronized (mInstallLock) {synchronized (mPackages) {// 創(chuàng)建消息mHandlerThread = new ServiceThread(TAG,Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);mHandlerThread.start();mHandler = new PackageHandler(mHandlerThread.getLooper());// ...// 掃描各個目錄獲取 APK 文件:VENDOR_OVERLAY_DIR // framework 文件夾:frameworkDir// 系統(tǒng)文件夾:privilegedAppDir systemAppDir// 供應商的包:Environment.getVendorDirectory()// 原始設備制造商的包 :Environment.getOdmDirectory()// 原始設計商的包:Environment.getOdmDirectory()// 原始產品的包:// ....mInstallerService = new PackageInstallerService(context, this);final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr();if (instantAppResolverComponent != null) {mInstantAppResolverConnection = new InstantAppResolverConnection(mContext, instantAppResolverComponent.first,instantAppResolverComponent.second);mInstantAppResolverSettingsComponent =getInstantAppResolverSettingsLPr(instantAppResolverComponent.first);} else {mInstantAppResolverConnection = null;mInstantAppResolverSettingsComponent = null;}updateInstantAppInstallerLocked(null);final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>();final int[] currentUserIds = UserManagerService.getInstance().getUserIds();for (int userId : currentUserIds) {userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList());}mDexManager.load(userPackages);} // synchronized (mPackages)} // synchronized (mInstallLock)// ....}
在構造方法中會掃描多個目錄來獲取 APK 文件,上述注釋中我們已經(jīng)給出了這些目錄,及其獲取的方式。當掃描一個路徑的時候會使用 scanDirLI()
方法來完成掃描工作。
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {final File[] files = scanDir.listFiles();if (ArrayUtils.isEmpty(files)) {return;}try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, mParallelPackageParserCallback)) {int fileCount = 0;for (File file : files) {final boolean isPackage = (isApkFile(file) || file.isDirectory())&& !PackageInstallerService.isStageName(file.getName());if (!isPackage) {continue;}// 提交文件用來解析parallelPackageParser.submit(file, parseFlags);fileCount++;}for (; fileCount > 0; fileCount--) {// 獲取解析的結果,即從隊列阻塞隊列中獲取解析的結果ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();// ...if (throwable == null) {// TODO(toddke): move lower in the scan chain// Static shared libraries have synthetic package namesif (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {renameStaticSharedLibraryPackage(parseResult.pkg);}try {if (errorCode == PackageManager.INSTALL_SUCCEEDED) {scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags, currentTime, null);}} catch (PackageManagerException e) {errorCode = e.error;}}// 。。。}}}
從上面的代碼中可以看出,提交文件來解析以及獲取解析都是通過 ParallelPackageParser 來完成的。它使用 submit()
方法來提交文件用來解析,使用 take()
方法獲取解析的結果。這兩個方法的定義如下,
public void submit(File scanFile, int parseFlags) {mService.submit(() -> {ParseResult pr = new ParseResult();try {PackageParser pp = new PackageParser();pp.setSeparateProcesses(mSeparateProcesses);pp.setOnlyCoreApps(mOnlyCore);pp.setDisplayMetrics(mMetrics);pp.setCacheDir(mCacheDir);pp.setCallback(mPackageParserCallback);pr.scanFile = scanFile;pr.pkg = parsePackage(pp, scanFile, parseFlags);} catch (Throwable e) {pr.throwable = e;}try {mQueue.put(pr);} catch (InterruptedException e) {Thread.currentThread().interrupt();mInterruptedInThread = Thread.currentThread().getName();}});}public ParseResult take() {try {if (mInterruptedInThread != null) {throw new InterruptedException("Interrupted in " + mInterruptedInThread);}return mQueue.take();} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new IllegalStateException(e);}}
submit()
方法使用一個線程池來執(zhí)行任務,也就是上面的 mService。它會將要解析的信息封裝成 PackageParser 對象,然后把解析的結果信息封裝成 ParseResult 放進一個阻塞隊列中。當調用 take()
方法的時候會從該阻塞隊列中獲取解析的結果。
包信息的解析最終是通過 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;}long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;if (packageFile.isDirectory()) {parsed = parseClusterPackage(packageFile, flags);} else {// 是文件,所以走這條路線parsed = parseMonolithicPackage(packageFile, flags);}long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;cacheResult(packageFile, flags, parsed);return parsed;}
我們會在這方法中進入到 parseMonolithicPackage()
來對文件進行解析。
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);try {// 解析final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);pkg.setCodePath(apkFile.getCanonicalPath());pkg.setUse32bitAbi(lite.use32bitAbi);return pkg;} catch (IOException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION);} finally {IoUtils.closeQuietly(assetLoader);}}
在這個方法中會使用 parseBaseApk()
來對 APK 文件進行解析,
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)throws PackageParserException {final String apkPath = apkFile.getAbsolutePath();String volumeUuid = null;if (apkPath.startsWith(MNT_EXPAND)) {final int end = apkPath.indexOf('/', MNT_EXPAND.length());volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);}mParseError = PackageManager.INSTALL_SUCCEEDED;mArchiveSourcePath = apkFile.getAbsolutePath();XmlResourceParser parser = null;try {final int cookie = assets.findCookieForPath(apkPath);// 讀取 AndroidManifest.xmlparser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);final Resources res = new Resources(assets, mMetrics, null);final String[] outError = new String[1];// 在這里進一步解析 Manifest 的各種信息final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);pkg.setVolumeUuid(volumeUuid);pkg.setApplicationVolumeUuid(volumeUuid);pkg.setBaseCodePath(apkPath);pkg.setSigningDetails(SigningDetails.UNKNOWN);return pkg;} catch (PackageParserException e) {throw e;} catch (Exception e) {throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION);} finally {IoUtils.closeQuietly(parser);}}
這里的 ANDROID_MANIFEST_FILENAME 是一個字符串,這個字符串的定義是 AndroidManifest.xml,所以,我們找到了解析 Manifest 的地方。
然后方法會進入到 parseBaseApk()
方法中進一步對 Manifest 進行解析。其讀取操作就是基本的 XML 解析的過程。它會使用內部定義的字符串常量從 Manifest 中獲取應用的版本還有四大組件等信息。
解析完了 APK 之后會一路經(jīng)過 return 語句返回到 scanDirLI()
方法中,當從阻塞隊列中取出 Package 之后將會調用 scanPackageChildLI()
在該方法中會將解析的出的 APK 信息緩存到 PMS 中。
這樣,在系統(tǒng)啟動之后 PMS 就解析了全部的 APK 文件,并將其緩存到了 PMS 中。這樣這些應用程序還無法展示給用戶,所以需要 Launcher 桌面程序從 PMS 中獲取安裝包信息并展示到桌面上。
2、應用安裝的過程
雖然 PMS 用來負責應用的安裝和卸載,但是真實的工作卻是交給 installd 來實現(xiàn)的。 installd 是在系統(tǒng)啟動的時候,由 init 進程解析 init.rc 文件創(chuàng)建的。在早期版本的 Android 中,它使用 Socket 與 Java 層的 Installer 進行通信。在 9.0 的代碼中,它使用 Binder 與 Java 層的 Installer 進行通信。當啟動 Installd 的時候,將會調用其 main 方法,
int main(const int argc, char *argv[]) {return android::installd::installd_main(argc, argv);
}static int installd_main(const int argc ATTRIBUTE_UNUSED, char *argv[]) {int ret;int selinux_enabled = (is_selinux_enabled() > 0);setenv("ANDROID_LOG_TAGS", "*:v", 1);android::base::InitLogging(argv);SLOGI("installd firing up");union selinux_callback cb;cb.func_log = log_callback;selinux_set_callback(SELINUX_CB_LOG, cb);// 初始化全局信息if (!initialize_globals()) {exit(1);}// 初始化相關目錄if (initialize_directories() < 0) {exit(1);}if (selinux_enabled && selinux_status_open(true) < 0) {exit(1);}if ((ret = InstalldNativeService::start()) != android::OK) {exit(1);}// 加入到 Binder 線程池當中IPCThreadState::self()->joinThreadPool();LOG(INFO) << "installd shutting down";return 0;
}
在啟動 Installd 的時候會初始化各種相關的目錄,這部分內容就不展開了。然后,它會調用 IPCThreadState::self()->joinThreadPool()
一行來將當前線程池加入到 Binder 線程池當中等待通信。
當 Java 層的 Installer 需要與之通信的時候,會調用 connect()
方法與之建立聯(lián)系。其源碼如下,這里會通過 ServiceManager 獲取 installd 服務,然后將其轉換成本地的服務進行 IPC 的調用。
private void connect() {// 獲取遠程服務 IBinder binder = ServiceManager.getService("installd");if (binder != null) {try {binder.linkToDeath(new DeathRecipient() {@Overridepublic void binderDied() {connect();}}, 0);} catch (RemoteException e) {binder = null;}}if (binder != null) {// 轉成本地服務進行 IPC 調用mInstalld = IInstalld.Stub.asInterface(binder);try {invalidateMounts();} catch (InstallerException ignored) {}} else {// 重連BackgroundThread.getHandler().postDelayed(() -> {connect();}, DateUtils.SECOND_IN_MILLIS);}}
Installer 與 PMC 類似,也是一種系統(tǒng)服務,它的啟動的時刻與 PMS 基本一致,位于同一個方法中,并且其啟動時刻位于 PMS 之前。
2、從 ADB 安裝的過程
另外
有什么技術問題歡迎加我交流 qilebeaf
本人10多年大廠軟件開發(fā)經(jīng)驗,精通Android,Java,Python,前端等開發(fā),空余時間承接軟件開發(fā)設計、課程設計指導、解決疑難bug、AI大模型搭建,AI繪圖應用等。
歡迎砸單