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.mzph.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…