性能优化之合并多个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冷启动速度的慢方法。敬请关注,分享,点赞,留言。

相关文章
|
5月前
|
Android开发
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
|
2月前
|
Android开发 开发者 UED
Android项目架构设计问题之加载数据到列表如何解决
Android项目架构设计问题之加载数据到列表如何解决
26 0
|
2月前
|
存储 缓存 Java
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
34 0
|
3月前
|
缓存 监控 NoSQL
深入解析数据库性能优化:策略与实践
【7月更文挑战第23天】数据库性能优化是一个复杂而持续的过程,涉及硬件、软件、架构、管理等多个方面。通过本文的介绍,希望能够为读者提供一个全面的性能优化框架,帮助大家在实际工作中更有效地提升数据库性能。记住,优化不是一蹴而就的,需要持续的观察、分析和调整。
|
5月前
|
移动开发 API 数据处理
构建高效安卓应用:探究Android 12中的新特性与性能优化策略
【4月更文挑战第23天】 随着移动设备的普及,用户对应用程序的性能和效率要求越来越高。安卓系统作为市场占有率最高的移动操作系统之一,其版本更新带来了众多性能提升和新特性。本文将深入探讨Android 12版本中引入的关键性能优化技术,并分析这些技术如何帮助开发者构建更加高效的安卓应用。我们将从最新的运行时权限、后台任务优化、以及电池使用效率等方面入手,提供具体的实践建议,旨在帮助开发者更好地利用这些新工具,以提升应用的响应速度、降低能耗,并最终提高用户的满意度。
|
5月前
|
存储 缓存 编解码
Android 性能优化: 解释Bitmap的优化策略。
Android 性能优化: 解释Bitmap的优化策略。
79 1
|
缓存 JavaScript 前端开发
性能优化之关键渲染路径
分别从浏览器架构和最新的渲染引擎介绍了关于页面渲染的相关概念。对应连接如下。 • 页面是如何生成的(宏观角度) • Chromium 最新渲染引擎--RenderingNG • RenderingNG中关键数据结构及其角色 而今天的主角是{关键渲染路径| Critical Rendering Path}。它是影响页面在加载阶段的主要标准。
|
存储 缓存 前端开发
浅谈性能优化之图片压缩、加载和格式选择
目前市场上优化图片资源的方式有很多,如压缩图片、选择正确格式、 CDN 加速、懒加载等。
249 0
浅谈性能优化之图片压缩、加载和格式选择
|
存储 缓存 编解码
「性能优化系列」不使用第三方库,Bitmap的优化策略
在Android3.0到Android7.0,Bitmap对象和像素都是放置到Java堆中,这个时候即使不调用recycle,Bitmap内存也会随着对象一起被回收。虽然Bitmap内存可以很容易被回收,但是Java堆的内存有很大的限制,也很容易造成GC。 在Android8.0的时候,Bitmap内存又重新放置到了Native中。 Bitmap造成OOM很多时候也是因为对Bitmap的资源没有得到很好的利用,同时没有做到及时的释放。
367 0
|
Android开发
性能优化一分钟定位Android启动耗时问题
性能优化一分钟定位Android启动耗时问题