‘壹’ android性能优化总结
常用的Android性能优化方法:
一、布局优化:
1)尽量减少布局文件的层级。
层级少了,绘制的工作量也就少了,性能自然提高。
2)布局重用 <include标签>
3)按需加载:使用ViewStub,它继承自View,一种轻量级控件,本身不参与任何的布局和绘制过程。他的layout参数里添加一个替换的布局文件,当它通过setVisibility或者inflate方法加载后,它就会被内部布局替换掉。
二、绘制优化:
基于onDraw会被调用多次,该方法内要避免两类操作:
1)创建新的局部对象,导致大量垃圾对象的产生,从而导致频繁的gc,降低程序的执行效率。
2)不要做耗时操作,抢CPU时间片,造成绘制很卡不流畅。
三、内存泄漏优化:
1)静态变量导致内存泄漏 比较明显
2)单例模式导致的内存泄漏 单例无法被垃圾回收,它持有的任何对象的引用都会导致该对象不会被gc。
3)属性动画导致内存泄漏 无限循环动画,在activity中播放,但是onDestroy时没有停止的话,动画会一直播放下去,view被动画持有,activity又被view持有,导致activity无法被回收。
四、响应速度优化:
1)避免在主线程做耗时操作 包括四大组件,因为四大组件都是运行在主线程的。
2)把一些创建大量对象等的初始化工作放在页面回到前台之后,而不应该放到创建的时候。
五、ListView的优化:
1)使用convertView,走listView子View回收的一套:RecycleBin 机制
主要是维护了两个数组,一个是mActiveViews,当前可见的view,一个是mScrapViews,当前不可见的view。当触摸ListView并向上滑动时,ListView上部的一些OnScreen的View位置上移,并移除了ListView的屏幕范围,此时这些OnScreen的View就变得不可见了,不可见的View叫做OffScreen的View,即这些View已经不在屏幕可见范围内了,也可以叫做ScrapView,Scrap表示废弃的意思,ScrapView的意思是这些OffScreen的View不再处于可以交互的Active状态了。ListView会把那些ScrapView(即OffScreen的View)删除,这样就不用绘制这些本来就不可见的View了,同时,ListView会把这些删除的ScrapView放入到RecycleBin中存起来,就像把暂时无用的资源放到回收站一样。
当ListView的底部需要显示新的View的时候,会从RecycleBin中取出一个ScrapView,将其作为convertView参数传递给Adapter的getView方法,从而达到View复用的目的,这样就不必在Adapter的getView方法中执行LayoutInflater.inflate()方法了。
RecycleBin中有两个重要的View数组,分别是mActiveViews和mScrapViews。这两个数组中所存储的View都是用来复用的,只不过mActiveViews中存储的是OnScreen的View,这些View很有可能被直接复用;而mScrapViews中存储的是OffScreen的View,这些View主要是用来间接复用的。
2)使用ViewHolder避免重复地findViewById
3)快速滑动不适合做大量异步任务,结合滑动监听,等滑动结束之后加载当前显示在屏幕范围的内容。
4)getView中避免做耗时操作,主要针对图片:ImageLoader来处理(原理:三级缓存)
5)对于一个列表,如果刷新数据只是某一个item的数据,可以使用局部刷新,在列表数据量比较大的情况下,节省不少性能开销。
六、Bitmap优化:
1)减少内存开支:图片过大,超过控件需要的大小的情况下,不要直接加载原图,而是对图片进行尺寸压缩,方式是BitmapFactroy.Options 采样,inSampleSize 转成需要的尺寸的图片。
2)减少流量开销:对图片进行质量压缩,再上传服务器。图片有三种存在形式:硬盘上时是file,网络传输时是stream,内存中是stream或bitmap,所谓的质量压缩,它其实只能实现对file的影响,你可以把一个file转成bitmap再转成file,或者直接将一个bitmap转成file时,这个最终的file是被压缩过的,但是中间的bitmap并没有被压缩。bitmap.compress(Bitmap.CompressFormat.PNG,100,bos);
七、线程优化:
使用线程池。为什么要用线程池?
1、从“为每个任务分配一个线程”转换到“在线程池中执行任务”
2、通过重用现有的线程而不是创建新线程,可以处理多个请求在创建销毁过程中产生的巨大开销
3、当使用线程池时,在请求到来时间 ,不用等待系统重新创建新的线程,而是直接复用线程池中的线程,这样可以提高响应性。
4、通过和适当调整线程池的大小 ,可以创建足够多的线程以使处理器能够保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或者失败。
5、一个App里面所有的任务都放在线程池中执行后,可以统一管理 ,当应用退出时,可以把程序中所有的线程统一关闭,避免了内存和CPU的消耗。
6、如果这个任务是一个循环调度任务,你则必须在这个界面onDetach方法把这个任务给cancel掉,如果是一个普通任务则可cancel,可不cancel,但是最好cancel
7、整个APP的总开关会在应用退出的时间把整个线程池全部关闭。
八、一些性能优化建议:
1)避免创建过多对象,造成频繁的gc
2)不要过多使用枚举,枚举占用的空间比整型大很多
3)字符串的拼接使用StringBuffer、StringBuilder来替代直接使用String,因为使用String会创建多个String对象,参考第一条。
4)适当使用软引用,(弱引用就不太推荐了)
5)使用内存缓存和磁盘缓存。
‘贰’ Android App内存优化
内存优化就是对内存问题的一个预防和解决,做内存优化能让应用挂得少、活得好和活得久。
挂的少:
“挂”指的是 Crash,内存问题导致 Crash 的具体表现就是内存溢出异常 OOM。
活得好:
活得好指的是使用流畅,Android 中造成界面卡顿的原因有很多种,其中一种就是由内存问题引起的。内存问题之所以会影响到界面流畅度,是因为垃圾回收(GC,Garbage Collection),在 GC 时,所有线程都要停止,包括主线程,当 GC 和绘制界面的操作同时触发时,绘制的执行就会被搁置,导致掉帧,也就是界面卡顿。
活得久:
活得久指的是我们的应用在后台运行时不会被干掉。Android 会按照特定的机制清理进程,清理进程时优先会考虑清理后台进程。清理进程的机制就是LowMemoryKiller。在 Android 中不同的进程有着不同的优先级,当两个进程的优先级相同时,低杀会优先考虑干掉消耗内存更多的进程。也就是如果我们应用占用的内存比其他应用少,并且处于后台时,我们的应用能在后台活下来,这也是内存优化为我们应用带来竞争力的一个直接体现。
内存占用是否越少越好?
当系统 内存充足 的时候,我们可以多用 一些获得更好的性能。当系统 内存不足 的时候,我们希望可以做到 ”用时分配,及时释放“。内存优化并不能一刀切。
我们都知道,应用程序的内存分配和垃圾回收都是由Android虚拟机完成的,在Android 5.0以下,使用的是Dalvik虚拟机,5.0及以上,则使用的是ART虚拟机。
Android虚拟机Dalvik和ART
1、内存区域划分
详细请看以下两篇文章(建议全看):
java内存四大区_JVM内存区域划分
Android 内存机制
2、内存回收
垃圾收集的标记算法(找到垃圾):
垃圾收集算法(回收垃圾):
引用类型:强引用、软引用、弱引用、虚引用
对象的有效性=可达性+引用类型
JAVA垃圾回收机制-史上最容易理解看这一篇就够了
Android:玩转垃圾回收机制与分代回收策略
android中还存在低杀机制,这种情况属于系统整机内存不足,直接把应用进程杀掉的情况。
Android后台杀死系列:LowMemoryKiller原理
1、内存溢出
系统会给每个App分配内存空间也就是heap size值,当app占用的内存加上申请的内存超过这个系统分配的内存限额,最终导致OOM(OutOfMemory)使程序崩溃。
通过命令 getprop |grep dalvik.vm.heapsize 可以获取系统允许的最大
注意:在设置了heapgrowthlimit的状况下,单个进程可用最大内存为heapgrowthlimit值。在android开发中,若是要使用大堆,须要在manifest中指定android:largeHeap为true,这样dvm heap最大可达heapsize。
关于heapsize & heapgrowthlimit
2、内存泄漏
Android系统虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会选择一些还存活的对象作为内存遍历的根节点GC Roots,通过对GC Roots的可达性来判断是否需要回收。内存泄漏就是 在当前应用周期内不再使用的对象被GC Roots引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,使实际可使用内存变小。简言之,就是 对象被持有导致无法释放或不能按照对象正常的生命周期进行释放。
Android常见内存泄漏汇总
3、内存抖动
指的是在短时间内大量的新对象被实例化,运行时可能无法承载这样的内存分配,在这种情况下就会导致垃圾回收事件被大量调用,影响到应用程序的UI和整体性能,最终可能导致卡顿和OOM。
常见情况:在一些被频繁调用的方法内不断地创建对象。例如在View 的onDraw方法内new 一些新的对象。
注意内存抖动也会导致 OOM,主要原因有如下两点:
1、Android Studio Profiler
作用
优点
内存抖动问题处理实战
理解内存抖动的概念的话,我们就能明白只要能找到抖动过程中所产生的对象及其调用栈,我们就能解决问题,刚好Android Studio 的Porfiler里面的Memory工具就能帮我们记录下我们操作过程中或静止界面所产生的新对象,并且能清晰看到这些对象的调用栈。
选择Profile 中 的Memory ,选择 Record Java/Kotlin allocations,再点击Record开始记录, Record Java/Kotlin allocations 选项会记录下新增的对象。
操作完成之后,点击如图所示的红脑按钮,停止记录。
停止记录后,我们就可以排序(点击 Allocations可以排序)看看哪些对象或基本类型在短时间被频繁创建多个,点击这些新增的对象就可以看到它的完成的调用链了,进而就找找到导致内存抖动的地方在哪里了。
2、利用DDMS 和 MAT(Memory Analyzer tool)来分析内存泄漏
我们利用工具进行内存泄漏分析主要是用对比法:
a.先打开正常界面,不做任何操作,先抓取一开始的堆文件。
b.一顿胡乱操作,回到原来操作前的界面。主动触发一两次GC,过10秒再抓取第二次堆文件。
c.通过工具对比,获取胡乱操作后新增的对象,然后分析这些新增的对象。
DDMS作用:抓取堆文件,主动触发GC。(其实也是可以用Android Studio 的Profile里面的Memory工具来抓取堆文件的,但是我这边在利用Profile 主动触发gc 的时候会导致程序奔溃,也不知道是不是手机的问题,所以没用Android Studio的Profiler)
MAT作用:对堆文件进行对比,找到多出的对象,找到对象的强引用调用链。
以下是详细的过程:
步骤1.打开DDMS,选择需要调试的应用,打开初始界面,点击下图的图标(Dump Hprof File)先获取一次堆文件。
步骤2.对应用随便操作后,回到一开始的界面,先多触发几次GC ,点击下图的图标(Cause Gc)来主动触发GC,然后再次点击 Dump Hprof File 图标来获取堆文件。
步骤3.通过Android Studio Profile 或者 DDMS mp 的堆文件无法在MAT 打开,需要借助android sdk包下的一个工具hprof-conv.exe来转换。
格式为 hprof-conv 旧文件路径名 要转换的名称;
例如:hprof-conv 2022-04-13_17-54-40_827.hprof change.hprof
步骤4.把两份堆文件导入MAT,然后选择其中第二次获取的堆文件,点击 如图所示的 Histogram查看。
步骤5.点击下图图标,Compare To Another Heap Dump ,选择另一份堆文件。
6.会得出下图所示的 Hitogram 展示,我们主要看Objects 这一列。 如下图所示 “+ 2” 则代表前面两份堆文件对比,这个对象多了两个,我们主要就是要分析这些多了出来,没有被回收的对象。
7.加入我们从增加的对象中,看到了MainActivity ,则需要从一开始打开的Hitogram 展示里面找到这个对象的调用栈。如下图所示,搜索MainActivity
8.看到下图所示解雇,然后鼠标右键点击下图红色圈圈着的MainActivity ,选择 Merger Shortest Paths to Gc Roots ,再选择 exclude all phantom/weak/soft etc.references ,就可以看到这个MainActivity 对象的强引用链,至此我们就可以找到MainActivity对象是被什么引用导致无法回收了。
3、内存泄露检测神器之LeakCanary(线下集成)
自行学习了解,接入简单,使用简单,基本可以解决大部分内存泄漏问题。
github地址 : https://github.com/square/leakcanary/
学习地址 : https://square.github.io/leakcanary/changelog/#version-22-2020-02-05
针对内存抖动的建议:
针对内存泄漏问题的建议:
针对内存溢出问题的建议(主要就是要减少内存占用):
建议参考:
深入探索 Android 内存优化(炼狱级别)
对于 优化的大方向,我们应该优先去做见效快的地方,主要有以下三部分:内存泄漏、内存抖动、Bitmap。完善监控机制也是我们的重点,能帮助我们对内存问题快速分析和处理。
参考:
深入探索 Android 内存优化(炼狱级别)
‘叁’ Android内存优化三:内存泄漏检测与监控
Android内存优化一:java垃圾回收机制
Android内存优化二:内存泄漏
Android内存优化三:内存泄漏检测与监控
Android内存优化四:OOM
Android内存优化五:Bitmap优化
Memory Profiler 是 Profiler 中的其中一个版块,Profiler 是 Android Studio 为我们提供的性能分析工具,使用 Profiler 能分析应用的 CPU、内存、网络以及电量的使用情况。
进入了 Memory Profiler 界面。
点击 Record 按钮后,Profiler 会为我们记录一段时间内的内存分配情况。
在内存分配面板中,通过拖动时间线来查看一段时间内的内存分配情况
通过搜索类或者报名的方式查看对象的使用情况
使用Memory Profiler 分析内存可以查看官网: 使用内存性能分析器查看应用的内存使用情况
对于内存泄漏问题,Memory Profiler 只能提供一个简单的分析,不能够确认具体发生问题的地方。
而 MAT 就可以帮我们做到这一点,它是一款功能强大的 Java 堆内存分析工具,可以用于查找内存泄漏以及查看内存消耗情况。
as 生成hprof文件无法被mat识别,需要进行转换
使用hprof-conv进行转换,hprof-conv位于sdkplatform-tools
ps:as导出hprof前最好先gc几次,可排除一些干扰
Histogram 可以列出内存中的对象,对象的个数以及大小; Dominator Tree 可以列出那个线程,以及线程下面的那些对象占用的空间; Top consumers 通过图形列出最大的object; Leak Suspects 通过MA自动分析泄漏的原因。
Shallow Heap就是对象本身占用内存的大小,不包含其引用的对象内存,实际分析中作用不大。常规对象(非数组)的ShallowSize由其成员变量的数量和类型决定。数组的shallow size有数组元素的类型(对象类型、基本类型)和数组长度决定。对象成员都是些引用,真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[],对象本身的内存都很小。
Retained Heap值的计算方式是将Retained Set(当该对象被回收时那些将被GC回收的对象集合)中的所有对象大小叠加。或者说,因为X被释放,导致其它所有被释放对象(包括被递归释放的)所占的heap大小。
Path To GC Roots -> exclude all phantim/weak/soft etc. references:查看这个对象的GC Root,不包含虚、弱引用、软引用,剩下的就是强引用。从GC上说,除了强引用外,其他的引用在JVM需要的情况下是都可以 被GC掉的,如果一个对象始终无法被GC,就是因为强引用的存在,从而导致在GC的过程中一直得不到回收,因此就内存泄漏了。
List objects -> with incoming references:查看这个对象持有的外部对象引用
List objects -> with outcoming references:查看这个对象被哪些外部对象引用
使用对象查询语言可以快速定位发生泄漏的Activity及Fragment
使用 MAT 来分析内存问题,效率比较低,为了能迅速发现内存泄漏,Square 公司基于 MAT 开源了 LeakCanary ,LeakCanary 是一个内存泄漏检测框架。
集成LeakCanary后,可以在桌面看到 LeakCanary 用于分析内存泄漏的应用。
当发生泄漏,会为我们生成一个泄漏信息概览页,可以看到泄漏引用链的详情。
LeakCanary 会解析 hprof 文件,并且找出导致 GC 无法回收实例的引用链,这也就是泄漏踪迹(Leak Trace)。
泄漏踪迹也叫最短强引用路径,这个路径是 GC Roots 到实例的路径。
LeakCanary 存在几个问题,不同用于线上监控功能
线上监控需要做的,就是解决以上几个问题。
各大厂都有开发线上监控方案,比如快手的 KOOM ,美团的 Probe ,字节的 Liko
快手自研OOM解决方案KOOM今日宣布开源
总结一下几点:
通过无性能损耗的 内存阈值监控 来触发镜像采集。将对象是否泄漏的判断延迟到了解析时
利用系统内核COW( Copy-on-write ,写时复制)机制,每次mp内存镜像前先暂停虚拟机,然后fork子进程来执行mp操作,父进程在fork成功后立刻恢复虚拟机运行,整个过程对于父进程来讲总耗时只有几毫秒,对用户完全没有影响。