性能优化之合并多个FileProvider

简介: 性能优化之合并多个FileProvider

1. 前言


最近在做Android App启动优化的工作,目前在快速定位耗时方法和合并多个ContentProvider两个方面取得了不错效果。合并多个ContentProvider很容易,但是合并多个FileProvider却很难。我发现目前网上没有合并多个FileProvider的教程,所以本文的合并多个FileProvider的方案是全网首创,一定会让你耳目一新。如果你觉得文章写的不错,帮忙分享给你的Android同事和朋友们。


说到合并多个ContentProvider,大家一定会想到Jetpack组件中的App Startup。本文借助了该库完成了多个ContentProvider的合并,本文的侧重点不在于如何使用App Startup。而在于如何合并多个FileProvider。

本文核心提纲如下:


  1. 项目启动过程遇到的问题
  2. 简述ContentProvider
  3. 简述App Startup使用
  4. 重点讲解FileProvider原理
  5. 反射方式将Uri映射硬编码
  6. 定义中转ContentProvider分发多个FileProvider的openFile请求
  7. 使用Aspectjx插桩hook掉FileProvider的getUriFromFile返回值


2.问题


测量启动耗时过程中发现项目中所有ContentProvider初始化的耗时总时间在150ms左右。为了确定这个时长是否耗时,于是我写了只有一个ContentProvider的Demo,发现耗时在20ms左右。由于相差了7倍,因此我认为项目中ContentProvider的初始化耗时是有优化的空间。通过查看编译后的AndroidManifest文件,发现声明了14个ContentProvider。从App Startup的设计初衷我们知道,它可以帮项目中的多个ContentProvider瘦身,将多个ContentProvider合并成一个ContentProvider。

在合并之前,我们需要思考一个问题。


我们自己写的ContentProvider和第三方sdk中定义的ContentProvider,都可以被合并吗?如果不是,那么什么样的ContentProvider可以被合并,什么样的又不能被合并呢?

尽管ContentProvider是古老的四大组件之一,已经有10多年的历史了,可能有的读者对它比较陌生。所以为了回答这个问题,我们得简单了解一下ContentProvider的设计目的和实现原理。


3. ContentProvider浅析


ContentProvider是Android系统中一个元老级的组件,Android系统诞生之初,它就存在了。它有以下几个特性


  1. 需要在AndroidManifest文件中注册
  2. 系统自动初始化ContentProvider,调用onCreate方法
  3. 支持进程间通信
  4. 支持增删改查操作,一般是数据库操作,但不限于此
  5. 支持调用自定义的方法


3.1 简单Demo


新建一个ByteStationContentProvider代码如下:

640.png

在AndroidManifest清单中注册:

640.png


我们注意到ByteStationContentProvider有如下方法:


  • onCreate
  • insert、query、update、delete
  • call


给出结论如下:


「如果项目中的ContentProvider只是重写了onCreate方法是可以被合并的。如果重写了增删改查和call方法,是不能被合并的。」


3.2 ContentProvider的滥用现象


自从square团队在Picasso图片加载库中使用ContentProovider自动初始化sdk后,广大的sdk开发者也学到了这招,尽量减少上层开发者的sdk初始化操作。在几年前,这也是一项黑科技,满满的逼格。但是随着项目规模的增大,对接了越来越多类似的sdk,导致启动时需要初始化越来越多的ContentProvider,拖累了App的启动速度。


Picasso使用ContentProvider自动初始化代码如下。

640.png

640.png

640.png

我们注意到Picasso的insert、query相关方法是默认空实现,没有任何的业务逻辑。那么该ContentProvider可以合并掉。


3.3 ContentProvider初始化时机


ContentProvider.onCreate方法调用介于Application.attachBaseContext和Application.onCreate之间。

640.png

代码调用图

640.png

3.4 计算ContentProvider耗时


从3.3章节可知,App中所有ContentProvider的耗时等价于Application.onCreate()开始执行的时间减去Application.attachBaseContext()执行结束的时间。

640.png

3.5 查看编译后的AndroidManifest文件


  1. 反编译查看


  1. app/build/intermediates/merged_manifests/debug/AndroidManifest.xml

640.png


4. App Startup使用


App Startup库是一个简单而且高效的应用启动初始化组件,它是基于ContentProvider实现的。由于本文重点在FileProvider的合并。所以本章节只是简单介绍它的使用。

App Startup使用分为3步:

  1. 添加依赖
  2. 实现Initializer组件
  3. AndroidManifest文件添加声明


4.1 添加依赖

640.png


4.2 实现Initializer组件

640.png

实现Initializer接口要求重写两个方法:


  1. create()方法中,我们可以把原先在ContentProvider中初始化的代码,放在这里。
  2. dependencies()方法表示当前初始化,是否依赖其它的Initializer组件,如果依赖的话,会先初始化它们。


4.3 AndroidManifest文件添加声明

640.png


使用还是蛮简单的。更多信息请查看官方文档。

https://developer.android.com/topic/libraries/app-startup


4.4 移除第三方sdk中存在的ContentProvider


假设第三方sdk的AndroidManifest文件中声明了一个名叫ShareContentProvider的Provider

640.png

要移除它,需要在app项目中的AndroidManifest中声明一个相同的Provider,并加上tools:node="remove"。

640.png


5. FileProvider浅析


5.1 从调用安装界面讲起


  • 「Android6.0以及之前版本安装apk代码如下」

640.png

  • 「Android7.0+安装apk代码如下」
  1. 自定义FileProvider

640.png

. AndroidManifest文件中注册FileProvider

640.png

res/xml文件夹新建toutiao.xml文件

640.png

调用安装程序

640.png

「我们可以看到它们的区别在于分别使用Uri.fromFile()和FIleProvider.getUriFromFile()获取文件的Uri。」

640.png

「打印结果对应的值如下表:」

方式
Uri file:///storage/emulated/0/toutiao/toutiao.apk
FileProvider content://com.toutiao.install/bytedance/toutiao.apk


「Uri方式获取到的文件路径很容易被猜出文件所在位置,这样暴露给第三方程序,可能会带来风险。而FileProvider获取到的文件路径就不容易暴露文件所在位置。引入FileProvider机制的原因就是为安全考虑。」


5.2 xml的tag对应的文件存储位置


640.png


「下图表示各种tag对应的文件路径」


NAME VALUE PATH
TAG_ROOT_PATH root-path /
TAG_FILES_PATH files-path /data/user/0/com.xxx/files
TAG_CACHE_PATH cache-path /data/user/0/com.xxx/cache
TAG_EXTERNAL external-path /storage/emulated/0
TAG_EXTERNAL_FILES external-files-path /storage/emulated/0/Android/data/com.xxx/files
TAG_EXTERNAL_CACHE external-cache-path /storage/emulated/0/Android/data/com.xxx/cache


5.3 xml中Uri和路径的映射表


  1. FileProvider的sCache

640.png

FileProvider有一个静态变量sCache。key存放的是FileProvider的authority,value存放的PathStrategy代表的是FileProvider对应的xml中的内容。


  1. SimplePathStrategy的mRoots

640.png

mRoots也是hashMap,key对应的是xml中的name节点。value对应的是tag+path的组合。


<files-path name="apk" path="."/>

key: apk


value: /data/user/0/com.xxx/files


「sCache和mRoots对应关系如下图:」

640.png

5.4 FileProvider解析xml过程


  1. FileProvider自动安装后调用attachInfo
  2. 调用parsePathStrategy解析xml
  3. getPathStrategy方法将解析的PathStrategy放入sCache

640.png

6. 合并FileProvider


合并FileProvider,我们需要将第三方sdk定义的FileProvider从AndroidManifest文件中移除掉。但是这样做我们将面临两个问题。


  1. xml中的Uri和文件路径映射无法写入到FileProvider的sCache中
  2. 进程间文件共享,是通过寻找到Uri中的authority对应的ContentProvider。调用它的openFile方法实现的,如果不声明ContentProvider会导致文件共享失败

解决方案如下:


  1. 通过反射,将xml中各项映射,硬编码的方式写入到FileProvider的sCache中
  2. 定义一个中转ContentProvider,将它声明在AndroidManifest文件中,接管所有FileProvider的openFile方法
  3. 通过Aspect插桩方式,将所有FileProvider的getUriForFile()返回的Uri的authority hook成中转ContentProvider的authority

6.1 反射硬编码写入映射


  1. 通过反射将映射关系写入sCache中

640.png

将本应该AndroidManifest中注册的FileProvider的authority信息和xml信息硬编码注册到sCache中

640.png

6.2 定义中转ContentProvider


authorities是com.peter.dispatch。主要是中转作用

640.png

6.3 Aspect hook authority


6.3.1  根目录build.gradle添加aspectjx

640.png

6.3.3 hook FileProvider.getUriFromFile

640.png

将 content://com.toutiao.install/bytedance/toutiao.apk

「转换成」

content://com.peter.dispatch/com.toutiao.install/bytedance/toutiao.apk

6.3.4 重写中转CP的openFile

640.png

7.总结


本文主要讲解如何合并多个「FileProvider」优化App启动速度。下一篇我将讲解如何快速定位影响Application冷启动速度的慢方法。敬请关注,分享,点赞,留言。

相关文章
|
1月前
|
网络协议 Linux Android开发
深入探索Android系统架构与性能优化
本文旨在为读者提供一个全面的视角,以理解Android系统的架构及其关键组件。我们将探讨Android的发展历程、核心特性以及如何通过有效的策略来提升应用的性能和用户体验。本文不包含常规的技术细节,而是聚焦于系统架构层面的深入分析,以及针对开发者的实际优化建议。
70 1
|
8月前
|
监控 API Android开发
构建高效安卓应用:探究Android 12中的新特性与性能优化
【4月更文挑战第8天】 在本文中,我们将深入探讨Android 12版本引入的几项关键技术及其对安卓应用性能提升的影响。不同于通常的功能介绍,我们专注于实际应用场景下的性能调优实践,以及开发者如何利用这些新特性来提高应用的响应速度和用户体验。文章将通过分析内存管理、应用启动时间、以及新的API等方面,为读者提供具体的技术实现路径和代码示例。
|
8月前
|
移动开发 API 数据处理
构建高效安卓应用:探究Android 12中的新特性与性能优化策略
【4月更文挑战第23天】 随着移动设备的普及,用户对应用程序的性能和效率要求越来越高。安卓系统作为市场占有率最高的移动操作系统之一,其版本更新带来了众多性能提升和新特性。本文将深入探讨Android 12版本中引入的关键性能优化技术,并分析这些技术如何帮助开发者构建更加高效的安卓应用。我们将从最新的运行时权限、后台任务优化、以及电池使用效率等方面入手,提供具体的实践建议,旨在帮助开发者更好地利用这些新工具,以提升应用的响应速度、降低能耗,并最终提高用户的满意度。
|
8月前
|
存储 缓存 编解码
Android 性能优化: 解释Bitmap的优化策略。
Android 性能优化: 解释Bitmap的优化策略。
107 1
|
前端开发
关于flutter列表的性能优化,你必须要了解的
这里是坚果前端小课堂,大家喜欢的话,可以关注我的公众号“坚果前端,”,或者加我好友,获取更多精彩内容 嵌套列表 - ShrinkWrap 与 Slivers
649 0
关于flutter列表的性能优化,你必须要了解的
|
编解码 自然语言处理 安全
玩安卓必须要掌握的性能优化之APK极限压缩
玩安卓必须要掌握的性能优化之APK极限压缩
522 0
玩安卓必须要掌握的性能优化之APK极限压缩
|
存储 XML JavaScript
Android 性能优化篇之SharedPreferences使用优化
`SharedPreferences(以下简称SP)`是Android本地存储的一种方式,是以`key-value`的形式存储在`/data/data/项目包名/shared_prefs/sp_name.xml`里
415 0
|
编解码 Android开发
性能优化:Android中Bitmap内存大小优化的几种常见方式
性能优化:Android中Bitmap内存大小优化的几种常见方式
|
存储 缓存 编解码
「性能优化系列」不使用第三方库,Bitmap的优化策略
在Android3.0到Android7.0,Bitmap对象和像素都是放置到Java堆中,这个时候即使不调用recycle,Bitmap内存也会随着对象一起被回收。虽然Bitmap内存可以很容易被回收,但是Java堆的内存有很大的限制,也很容易造成GC。 在Android8.0的时候,Bitmap内存又重新放置到了Native中。 Bitmap造成OOM很多时候也是因为对Bitmap的资源没有得到很好的利用,同时没有做到及时的释放。
392 0
|
前端开发
flutter 中的列表的性能优化前奏
这里是坚果前端小课堂,大家喜欢的话,可以关注我的公众号“坚果前端,”,或者加我好友,获取更多精彩内容 嵌套列表 - ShrinkWrap 与 Slivers 使用 ShrinkWrap 的列表列表 下面是一些使用ListView对象呈现列表列表的代码,内部列表的shrinkWrap值设置为 true。shrinkWrap强行评估整个内部列表,允许它请求有限的高度,而不是通常的ListView对象高度,即无穷大!
288 0
flutter 中的列表的性能优化前奏