「性能优化系列」APP内存优化理论与实践

频繁创建对象实例的原代码在com.fuusy.fuperformance.memory.view.WaveView的136行。

哦~~ ,原来是自定义View时,在onDraw中频繁创建Paint所致,解决的办法就是将paint作为全局变量,在外部创建。

这个案例只是内存抖动中一个小小的缩影,当项目越来越大时,排查的工作难度也随之增加,这就要我们在平时开发时,就需要注意代码细节问题,尽可能在coding的过程中就减少内存问题。

内存抖动的注意事项:

  1. 避免在循环和频繁调用的方法中创建对象;

  2. 使用对象池,如Handler、Glide中的对象池。

2.2、内存泄漏


内存泄漏指的是程序中已分配的内存由于某种原因未释放或者无法释放,造成系统内存的浪费。

造成内存泄漏的原因有很多,比如:

  • 长生命周期对象持有短生命周期对象的强引用,从而导致短生命周期对象无法被回收;

  • 异步线程持有短生命周期对象,如Handler、网络请求或者其他工作线程持有短生命周期对象;

  • 资源未及时关闭,如BroadcastReceiver、File、Cursor等;

  • 大量使用静态变量;

当然,在实际项目中查找内存泄漏的原因的方式也有很多,比如主流工具LeakCanary、 MAT等。

2.3、Bitmap优化


Bitmap作为程序中内存占用的大户,是必须优化的对象,之前写过一篇关于Bitmap优化的文章「性能优化系列」不使用第三方库,Bitmap的优化策略,可参考查看。

Bitmap除了基本优化外,其实还需要在coding的过程中,就将Bitmap内存问题扼杀在摇篮里,本篇文章就将从图片大小监控,重复图片监控两个方向进行阐述。

2.3.1、Bitmap大小监控方案

Bitmap有一种从其尺寸上优化的手段,即当装载图片的容器例如ImageView只有100 * 100,而图片的分辨率为1800 * 800,这个时候将图片直接放置在容器上,很容易OOM,同时也是对图片和内存资源的一种浪费。当容器的宽高都很小于图片的宽高,其实就需要对图片进行尺寸上的压缩.

而比较重要的点就是如何判断Bitmap的尺寸符合图片容器?

想到的第一种方法就是可以自定义一个ImageView,在View中去判断图片以及容器的大小,如果图片太大,则进行尺寸上的压缩或优化。这种方式简单实用,确实也解决了我们的问题,但在实际开发中,除了代码侵入性强外,如果想要开发团队中的每个人加载ImageView时都使用这个控件,也是一件很难展开的事情。

为了更加解耦性和减少代码侵入性,这里介绍一种Bitmap大小的监控方案-ARTHook

ARTHook通俗来讲就是统一添加代码修改原有逻辑。基于ARTHook的框架有很多,这里介绍一个常用框架-Epic。 Epic就是ART上的 Dexposed(支持 Android 5.0 ~ 11)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析等。

下面就基于Epic框架进行Bitmap大小监控的编写。

添加依赖

dependencies {

implementation ‘me.weishu:epic:0.11.0’

}

新建一个Hook类用来编写图片大小读取,比较,继承XC_MethodHook,并实现其afterHookedMethod,在其方法内实现需要监控的对象,具体监控的方法调用后将会调用该方法。

class BitmapARTHook : XC_MethodHook() {

@Throws(Throwable::class)

override fun afterHookedMethod(param: MethodHookParam) {

super.afterHookedMethod(param)

val imageView = param.thisObject as ImageView

checkBitmap(imageView, (param.thisObject as ImageView).drawable)

}

}

在afterHookedMethod方法里实现我们需要的逻辑,这里需要判断图片大小并给出警示。那就加上图片大小的监测方法。

if (bitmap.width >= width shl 1 && bitmap.height >= height shl 1) {

warn(

bitmap.width,

bitmap.height,

width,

height,

RuntimeException(“Bitmap size too large”)

)

}

详细代码请参考fuusy/FuPerformance

写个测试来看看最终的效果。在xml中新建一个ImageView,宽高都设置为100dp,在Activity中将一张分辨率为1300* 500的图片设置到ImageView中。

val imageView = findViewById(R.id.iv_bitmap)

BitmapFactory.decodeResource(resources, R.mipmap.bitmap1).apply {

imageView.setImageBitmap(this)

}

运行后在终端可以看到图片大小的提示信息。

2.3.2、重复图片监控

张邵文在高手课中提及重复图片指的是 Bitmap 的像素数据完全一致,但是有多个不同的对象存在。给出的方案是使用HAHA 库快速判断内存中是否存在重复的图片,并且将这些重复图片的 PNG、堆栈等信息输出。

需要注意的是需要使用8.0以下的机器,因为8.0以后Bitmap中的buffer已经放到native内存中。 核心代码与思路如下:

//打开hprof文件

HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);

HprofParser parser = new HprofParser(buffer);

//解析获得快照

com.squareup.haha.perflib.Snapshot snapshot = parser.parse();

snapshot.computeDominators();

//获得Bitmap Class

Collection bitmapClasses = snapshot.findClasses(“android.graphics.Bitmap”);

//获取堆数据,这里包括项目app、系统、default heap的信息,需要进行过滤

Collection heaps = snapshot.getHeaps();

long startTime = System.currentTimeMillis();

Tools.print("---------------------- 开始 ----------------------- ");

for (Heap heap : heaps) {

// 只需要分析app和default heap即可

if (!heap.getName().equals(“app”) && !heap.getName().equals(“default”)) {

continue;

}

Tools.print(“HeapName:” + heap.getName());

Map<Integer, List> map = new HashMap<>();

for (ClassObj clazz : bitmapClasses) {

//从heap中获得所有的Bitmap实例

List instances = clazz.getHeapInstances(heap.getId());

for (int i = 0; i < instances.size(); i++) {

//从GcRoot开始遍历搜索,Integer.MAX_VALUE代表无法被搜索到,说明对象没被引用可以被回收

if (instances.get(i).getDistanceToGcRoot() == Integer.MAX_VALUE) {

continue;

}

List analyzerResults;

int curHashCode = Tools.getHashCodeByInstance(instances.get(i));

AnalyzerResult result = Tools.getAnalyzerResult(instances.get(i));

result.setInstance(instances.get(i));

if (map.get(curHashCode) == null){

analyzerResults = new ArrayList<>();

}else {

analyzerResults = map.get(curHashCode);

}

analyzerResults.add(result);

map.put(curHashCode, analyzerResults);

}

}

if (map.isEmpty()){

Tools.print(“当前head暂无bitmap对象”);

}

for (Map.Entry<Integer, List> entry : map.entrySet()){

List analyzerResults = entry.getValue();

//去除size小于2的,剩余的为重复图片。

if (analyzerResults.size() < 2){

continue;

}

}

}

具体方案可参考高手课Demo github.com/simplezhli/… 以及fuusy/FuPerformance

2.4、设备分级


所谓设备分级,指的是根据不同设备环境来考虑不同的内存优化策略。目前市场上手机层出不穷,几乎每一年都会对手机性能进行提升,但是对于性能较差的手机,app应用的运行状况就会较差。

对于低端机用户可以关闭复杂的动画,或者是某些功能;使用 565 格式的图片,使用更小的缓存内存等。在现实环境下,不是每个用户的设备都跟我们的测试机一样高端,在开发过程我们要学会思考功能要不要对低端机开启、在系统资源吃紧的时候能不能做降级。- 张邵文

那如何进行设备分级?

Facebook其实开发了一个 设备年份类库,它使用简单的算法将设备的 RAM、CPU 内核和时钟速度与这些特性被认为是高端的年份相匹配。使得我们能够根据手机的硬件功能编写不同的逻辑。

| RAM | condition | Year Class |

| — | — | — |

| 768MB | 1 core | 2009 |

| | 2+ cores | 2010 |

| 1GB | <1.3GHz | 2011 |

| | 1.3GHz+ | 2012 |

| 1.5GB | <1.8GHz | 2012 |

| | 1.8GHz+ | 2013 |

| 2GB | | 2013 |

| 3GB | | 2014 |

| 5GB | | 2015 |

| more | | 2016 |

而针对设备性能,我们能做的优化就如上面张邵文所说。

  • 是否关闭动画;

  • 图片质量分级;

  • 为低性能设备设计简版应用。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,在这里免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

备注Android)**
[外链图片转存中…(img-v3XapbOM-1710676034831)]

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,在这里免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

[外链图片转存中…(img-IgkSwrpM-1710676034831)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值