Android應用安裝過程

Android 系統源碼源碼-應用安裝過程

Android 中應用安裝的過程就是解析 AndroidManifest.xml 的過程,系統可以從 Manifest 中得到應用程序的相關信息,比如 Activity、Service、Broadcast Receiver 和 ContentProvider 等。這些工作都是由 PackageManageService 負責的,也就是所謂的 PMS. 它跟 AMS 一樣都是一種遠程的服務,并且都是在系統啟動 SystemServer 的時候啟動的。下面我們通過源代碼來分析下這個過程。

1、啟動 PMS 的過程

系統在啟動 SystemServer 的過程會啟動 PMS,系統的啟動過程可以參考下面這篇文章學習,

Android 系統源碼-1:Android 系統啟動流程源碼分析

在啟動 SystemServer 的時候會調用 startBootstrapServices() 方法啟動引導服務。PMS 就是在這個方法中啟動的,

    private void startBootstrapServices() {// ...mPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);mFirstBoot = mPackageManagerService.isFirstBoot();mPackageManager = mSystemContext.getPackageManager();// ...}

可以看出,系統是通過調用 PMS 的 main 方法來將其啟動起來的。其 main 方法會先實例化一個 PMS 對象,然后調用 ServiceManager 的靜態方法將其注冊到 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 安裝的主要分成下面幾個步驟,

  1. 拷貝文件到指定的目錄:默認情況下,用戶安裝的 APK 首先會被拷貝到 /data/app 目錄下,/data/app 目錄是用戶有權限訪問的目錄,在安裝 APK 的時候會自動選擇該目錄存放用戶安裝的文件,而系統的 APK 文件則被放到了 /system 分區下,包括 /system/app/system/vendor/app,以及 /system/priv-app 等等,該分區只有 ROOT 權限的用戶才能訪問,這也就是為什么在沒有 Root 手機之前,我們沒法刪除系統出場的 APP 的原因了。
  2. 解壓縮 APK,拷貝文件,創建應用的數據目錄:為了加快 APP 的啟動速度,APK 在安裝的時候,會首先將 APP 的可執行文件 dex 拷貝到 /data/dalvik-cache 目錄,緩存起來。然后,在 /data/data/ 目錄下創建應用程序的數據目錄 (以應用的包名命名),存放在應用的相關數據,如數據庫、XML 文件、Cache、二進制的 so 動態庫等。
  3. 解析 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) {// 創建消息mHandlerThread = new ServiceThread(TAG,Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);mHandlerThread.start();mHandler = new PackageHandler(mHandlerThread.getLooper());// ...// 掃描各個目錄獲取 APK 文件:VENDOR_OVERLAY_DIR           // framework 文件夾:frameworkDir// 系統文件夾: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 文件,上述注釋中我們已經給出了這些目錄,及其獲取的方式。當掃描一個路徑的時候會使用 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() 方法使用一個線程池來執行任務,也就是上面的 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 之后會一路經過 return 語句返回到 scanDirLI() 方法中,當從阻塞隊列中取出 Package 之后將會調用 scanPackageChildLI() 在該方法中會將解析的出的 APK 信息緩存到 PMS 中。

這樣,在系統啟動之后 PMS 就解析了全部的 APK 文件,并將其緩存到了 PMS 中。這樣這些應用程序還無法展示給用戶,所以需要 Launcher 桌面程序從 PMS 中獲取安裝包信息并展示到桌面上。

2、應用安裝的過程

雖然 PMS 用來負責應用的安裝和卸載,但是真實的工作卻是交給 installd 來實現的。 installd 是在系統啟動的時候,由 init 進程解析 init.rc 文件創建的。在早期版本的 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() 方法與之建立聯系。其源碼如下,這里會通過 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 類似,也是一種系統服務,它的啟動的時刻與 PMS 基本一致,位于同一個方法中,并且其啟動時刻位于 PMS 之前。

2、從 ADB 安裝的過程

另外
有什么技術問題歡迎加我交流 qilebeaf
本人10多年大廠軟件開發經驗,精通Android,Java,Python,前端等開發,空余時間承接軟件開發設計、課程設計指導、解決疑難bug、AI大模型搭建,AI繪圖應用等。
歡迎砸單

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/38627.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/38627.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/38627.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

drm core

drm core初始化 /*** drm_sysfs_init - initialize sysfs helpers** This is used to create the DRM class, which is the implicit parent of any* other top-level DRM sysfs objects.** You must call drm_sysfs_destroy() to release the allocated resources.** Return: …

Linux通配符及其在文件搜索和管理中的應用

Linux通配符及其在文件搜索和管理中的應用 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 1. 了解Linux通配符 在Linux系統中&#xff0c;通配符是一種用于匹…

家政小程序的開發,帶動市場快速發展,提高家政服務質量

當下生活水平逐漸提高&#xff0c;也增加了年輕人的工作壓力&#xff0c;同時老齡化也在日益增加&#xff0c;使得大眾對家政的需求日益提高&#xff0c;能力、服務質量高的家政人員能夠有效提高大眾的生活幸福指數。 但是&#xff0c;傳統的家政服務模式存在著效率低、用戶與…

慧翰股份毛利率下滑:股權轉讓糾紛引關注,研發費用率遠弱同行還買樓?

《港灣商業觀察》施子夫 6月11日&#xff0c;慧翰微電子股份有限公司&#xff08;以下簡稱&#xff0c;慧翰股份&#xff09;IPO注冊申請獲證監會同意&#xff0c;預計公司將很快登陸深交所創業板&#xff0c;保薦機構為廣發證券。 從業績面來看&#xff0c;過去三年&#xf…

基于X86+FPGA+AI的芯片缺陷檢測方案

應用場景 隨著半導體技術的發展&#xff0c;對芯片的良率要求越來越高。然而集成電路芯片制造工藝復雜&#xff0c;其制造過程中往往產生很多缺陷&#xff0c;因此缺陷檢測是集成電路制造過程中的必備工藝。 客戶需求 小體積&#xff0c;低功耗 2 x USB,1 x LAN Core-i平臺無…

JavaScript——運算符的優先級和結合性

目錄 任務描述 相關知識 運算符的優先級 運算符的結合性 編程要求 任務描述 本關任務&#xff1a;我們將給出函數mainJs()的完整代碼&#xff0c;要求在函數體內第三句以及第五句中添加適當的括號&#xff0c;實現編程要求里面的要求。 要想完成本關任務&#xff0c;必須…

一點連接千家銀行,YonSuite讓“銀企對賬”一鍵確認

在當今數智化浪潮下&#xff0c;成長型企業面臨著前所未有的機遇與挑戰。特別是在與銀行的對接以及銀企對賬等方面&#xff0c;傳統的手動操作模式已難以滿足企業高效、安全的金融管理需求。用友YonSuite作為一款全場景SaaS應用服務&#xff0c;憑借其強大的銀企直聯功能&#…

AI在線免費視頻工具3:聲音生視頻

1、聲音生視頻 Noisee&#xff1a;通過聲音生成對應視頻&#xff0c;可以增加prompt指定生成內容相關視頻 https://noisee.ai/create

【基礎篇】第5章 Elasticsearch 數據聚合與分析

在Elasticsearch的龐大功能體系中&#xff0c;數據聚合與分析扮演著至關重要的角色&#xff0c;它使我們能夠從海量數據中提煉出有價值的信息&#xff0c;為決策提供依據。本章將深入探討Elasticsearch的聚合功能&#xff0c;從基本概念到常見類型的實踐&#xff0c;讓你掌握如…

Elasticsearch 使用誤區之二——頻繁更新文檔

在使用 Elasticsearch 時&#xff0c;頻繁更新文檔是一種常見誤區。這不僅影響性能&#xff0c;還可能導致系統資源的浪費。 理解 Elasticsearch 的文檔更新機制對于優化性能至關重要。 關于 Elasticsearch 更新操作&#xff0c;常見問題如下&#xff1a; ——https://t.zsxq.c…

Spring Cloud實戰:構建分布式系統解決方案

Spring Cloud實戰&#xff1a;構建分布式系統解決方案 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將深入探討如何使用Spring Cloud來構建分布式系統…

剖析DeFi交易產品之UniswapV4:概述篇

本文首發于公眾號&#xff1a;Keegan小鋼 UniswapV4 與 UniswapV3 相比&#xff0c;算法上并沒有什么改變&#xff0c;依然還是采用集中流動性模型&#xff0c;但架構上變化很大&#xff0c;包括功能架構&#xff0c;也包括技術架構。相比之前的版本&#xff0c;UniswapV4 最大…

百元藍牙耳機推薦2024,百元藍牙耳機排行榜盤點

在2024年面對琳瑯滿目的藍牙耳機選項&#xff0c;消費者往往難以抉擇&#xff0c;特別是在預算有限的情況下&#xff0c;如何在眾多產品中挑選出既滿足質量又符合預算的耳機成為了一個不小的挑戰。 為了幫助大家在繁多的選擇中找到真正物有所值的百元藍牙耳機&#xff0c;我們…

UnityUGUI之一:image和Rawimage

image組件的相關屬性 其中SpriteMode&#xff0c;若為單個圖片則為Single&#xff0c;圖片集則為Multiple 圖集的切割 點擊Slice可以進行自動切割 為且每個格子都可以進行單獨的九宮格切割 當圖片被九宮格切割再進行拉伸以后&#xff0c;九宮格的四角不會被拉伸 Tiled&#x…

構建支持多平臺的返利App跨平臺開發策略

構建支持多平臺的返利App跨平臺開發策略 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將討論如何構建支持多平臺的返利App&#xff0c;特別關注跨平臺…

一棵B+樹可以存放多少行數據

以MySQL InnoDB為例。InnoDB存儲引擎最小儲存單元是頁&#xff0c;一頁大小固定是16KB&#xff0c;使用該引擎的表為索引組織表。B樹葉子存的是數據&#xff0c;內部節點存的是鍵值和指針。索引組織表通過非葉子節點的二分查找法以及指針確定數據在哪個頁中&#xff0c;進而再去…

數據治理不再頭疼,篩斗數據為您打造無縫數據處理體驗

在當今數字化時代&#xff0c;數據已成為企業最寶貴的資產之一。然而&#xff0c;隨著數據量的激增和數據來源的多樣化&#xff0c;數據治理成為許多企業面臨的一大挑戰。繁瑣的數據提取、混亂的數據結構和不清晰的數據質量&#xff0c;往往讓企業陷入數據處理的泥潭。幸運的是…

如何在本地一鍵配置最強國產大模型

自從OpenAI的ChatGPT橫空出世以來&#xff0c;國內外各類大語言模型&#xff08;LLM&#xff09;層出不窮&#xff0c;其中不乏Google的Gemini、Claude、文心一言等等。相較于競爭激烈的商業模型賽道&#xff0c;以Llama為代表的開源大模型的進步速度也十分驚人。 伴隨著大語言…

CP AUTOSAR標準之MemoryAccess(AUTOSAR_CP_SWS_MemoryAccess)(更新中……)

1 簡介和功能概述 該規范描述了AUTOSAR基礎軟件模塊內存訪問(MemAcc)的功能、API和配置。 ??內存訪問模塊通過基于地址的API提供對不同內存技術設備的訪問。內存訪問模塊始終由一個或多個內存驅動程序(Mem)補充。內存訪問模塊與內存設備技術無關,可與閃存、EEPROM、RAM或相變…

Python Tkinter:開發一款文件加密解密小工具

在這個信息泄露風險日益增加的時代&#xff0c;使用文件加密工具對于保護個人隱私和企業機密至關重要。 本文介紹了一款小工具——encryptDecrypt&#xff0c;它不僅提供了一個易于使用的圖形界面&#xff0c;簡化了加密和解密過程&#xff0c;還確保了數據的安全性&#xff0c…