glide加载图片很慢_反编译所有图片加载库,让OOM无所遁形

码个蛋(codeegg) 第 989 次推文

作者:ZhouZhengyi

链接:https://juejin.im/post/5ea1c46851882573a25f3ec3

1.背景

最近看滴滴开源的Dokit框架中有一个大图监控的功能,可以对图片的文件大小和所占用的内存大小设置一个阈值,当图片超过该值的时候进行提示。这个功能对于我们在做APK体积压缩,内存管理的时候还是很有用的,比如当我们要从后台返回的连接中加载一张图片,这张图片的大小我们是不知道的,虽然现在大家都使用Glide等三方 图片加载框架,框架会自动对图片进行压缩,但是依然会出现压缩后所占内存超过预期的情况。这时候我们可以在开发、测试和预生产阶段使用大图监控来识别出那些超标的图片。

2.需求

在讨论如何做之前,我们必须明确我们要做什么。该大图监控框架我觉得应该实现以下功能:

  1. 能对图片的文件大小和所占用的内存大小设置阈值,超过其中之一则报警。

  2. 能够得到超标图片的详细信息,包括当前文件大小,所占用内存,图片分辨率,图片的略缩图,图片的加载地址,view的尺寸。

  3. 能够通过弹窗或者列表的方式查看当前超标的图片信息。

  4. 不论是本地加载图片还是网络加载图片都能够进行监控。

3.实现思路

要实现对图片文件大小和所占内存的监控,那么我们就得先知道图片的文件大小和加载该图片所耗费的内存。目前加载图片一般都使用第三方框架,所以可以对常用的图片加载框架进行Hook,这里主要对主流的四种图片加载框架进行Hook操作。

  1. Glide

  2. Picasso

  3. Fresco

  4. Image Loader

以从网络加载一张图片举例,当使用图片框架加载一张网络图片时,会使用OkHttp或者是HttpUrlconnection去下载该图片,这时候我们就能得到图片文件的大小。当图片框架将图片文件构造成Bitmap对象以后,我们又能得到其所占用的内存,这样我们就同时的得到了图片的文件大小和所占用的内存。那么这里我们也必须对OkHttp和HttpUrlconnection进行Hook。

既然要对三方框架进行Hook操作,那么我们如何进行Hook呢?在选择Hook的实现方案时,我对以下几种方案进行了调研。

  1. 反射+动态代理

  2. ASM

  3. AspectJ

  4. ByteBuddy

首先反射+动态代理 只能在程序运行时进行,这样会影响效率,所以暂不考虑。其他三种方案都能够在编译期进行字节码插桩,ASM直接操纵字节码,阅读起来不那么友好。AspectJ以前用过,经常出一些莫名其妙的问题,体验不是很好。ByteBuddy 封装了ASM,据说效率很高,而且使用JAVA编写,代码可读性好,只是网上的资料太少了,大部分都是那么几篇文章再转发。所以这里最后选择了ASM实现。

有了ASM进行字节码插入,那什么时候将我们编写好的字节码插入到第三方框架中呢?

9ca85412716302def59cc324d7217490.png

我们从Apk打包流程图中可以看到,在生成dex文件之前,我们可以获取到本项目和第三方库的class文件,那么我们是否可以在此处将我们编写的字节码插入呢?答案是肯定的,我们在谷歌官网上找到这么一个界面-Transform Api。

bfd1b77f9e9ba91a3723be1377339da4.png

网页上讲从Android Gralde插件1.5.0版本开始,添加了Transform API,来允许第三方插件在经过编译的class文件转换为dex文件之前对其进行操作。Gradle会按照以下顺序执行转换:JaCoCo->第三方插件->ProGuard。其中第三方插件的执行顺序与第三方插件添加顺序一致,并且第三方插件无法通过Api控制转换的执行顺序。

20c1fdb9a82a8ec5647f05f2a3366d92.png

有了Transform API +ASM我们就能够将我们自己编写的字节码插入到第三方框架的class文件中,从而在编译器完成插桩。

现在我们已经决定了用ASM在编译期通过Transform API进行插桩。那么具体该怎么实现呢?我们回想一下我们需要实现的功能,我们要对图片进行监控,为了监控我们要获取图片的数据,得到数据后发现超标图片我们要给与提示。这意味着有两部分功能,一部分负责通过插桩获取数据,另外一部分负责显示超标数据。于是整个大图监控项目我们采用Gradle自定义插件+Android Library的形式。

dc1ee367f7e3ee426897a9c1f6a93928.png
  1. largeimage-plugin:自定义Gradle插件,主要负责将我们编写的字节码插入到class文件。

  2. largeimage:Andriod Library,主要负责将获取到的图片数据进行过滤,保存超标图片并且以弹窗或者列表的形式呈现给用户。

如何创建Gralde插件项目在这里就不多说了,网上有很多教程。网上的大多数教程会告诉你把插件项目名称改为buildSrc,这样做有很多好处,尤其是在代码编写阶段,可以采用以下这种形式进行测试

apply plugin:org.zzy.largeimage.LargeImageMonitorPlugin

不需要每次编写完成以后发布到maven仓库,插件项目修改以后,会直接在使用模块体现出来。

在这里笔者自建了本地maven库,并且为了名称上的统一,并没有将插件项目的名称改为buildSrc,这两种形式都可以,大家可以根据自身的情况来使用。

4.1 插件端

如果在编译期存在很多Transform那么肯定会对编译速度有一定的影响,那么有没有什么方式可以减少这种影响?有!并发+增量编译。

在这里推荐一个开源库Hunter,它能够帮助你快速的开发插件,并且支持并发+增量编译,笔者在这里就使用了该开源库。

41b5cece4622d2f12f6735d31dad11b9.png

使用该开源库很简单,只需要在插件项目的build.gradle中引入依赖就行。

接下来为了创建我们的Transform并且将其注册到整个Transform队列中,我们需要创建一个类实现Plugin接口。

public class LargeImageMonitorPlugin implements Plugin {
@Override
public void apply(Project project) {
List taskNames = project.getGradle.getStartParameter.getTaskNames;
//如果是Release版本,则不进行字节码替换
for(String taskName : taskNames){
if(taskName.contains("Release")){
return;
}
}
AppExtension appExtension = (AppExtension)project.getProperties.get("android");
//创建自定义扩展
project.getExtensions.create("largeImageMonitor
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值