导航:首页 > 操作系统 > android中内存优化

android中内存优化

发布时间:2023-09-19 06:31:03

‘壹’ 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图片内存优化

2个基本原则

既然需要的内存公式已得到,那优化就显而易见了,无非就是减小的这三个参数的值,具体的策略如下:
这里我们将图片分为2种情况来探讨:

图片占用的内存 大小为:

为什么mipmap不在这种情况的考虑范围之内呢?
因为mipmap是Android系统为了避免Launcher Icon变形而添加的资源目录,也就是说,mipmap中的图片不会被缩放。所以Google也不推荐将除Launcher Icon之外的图片放在mipmap目录中。

本地图片通常都是通过Android提供的BitmapFactory来加载的, 这里看几个常用的API:

图片的优化可通过Options参数来实现(Options的介绍可参考 从fresco 看图片优化 :

inPreferredConfig的取值为Bitmap.Config类型(这里只考虑以下几种情况),它是一个枚举类型,用来设置每个像素需要的字节数:

1.jpeg和gif

2.webp

3.png8, png24, png32

网络图片通常我们都是使用开源库进行加载, 所以不需要拿到Bitmap再进行缩放或裁剪。
这时可让后台实现网络图片的裁剪,即:根据图片的请求参数返回合适的尺寸,最大也只需要控件的大小即可。
再大也没意义,不仅浪费流量,还占用内存。
如果你的APP中有很多图片,那么可对图片的宽高根据设备的内存情况进行适当的缩小:

尽量为所有分辨率创建资源 资源匹配分辨率 = 减少不必要的缩放,从而提高UI绘制效率

对于一个多图片的APP来说,图片所占内存的优化是一项必不可少的工作。
总的来说,其优化也就是通过 缩放 和指定 Bitmap.Config的值 来实现的,只是不同位置,不同格式的图片有所差异而已。

https://juejin.im/post/5af84f4b51882542714fdaa9?utm_medium=an&utm_source=weixinqun

‘叁’ 在Android开发中,有哪些好的内存优化方式

可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
2. 避免在Android里面使用Enum
Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。
3. 减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,,通常来说有以下2个措施:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异
4.Bitmap对象的复用
缩小Bitmap的同时,也需要提高BitMap对象的复用率,避免频繁创建BitMap对象,复用的方法有以下2个措施
LRUCache : “最近最少使用算法”在Android中有极其普遍的应用。ListView与GridView等显示大量图片的控件里,就是使用LRU的机制来缓存处理好的Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,
inBitMap高级特性:利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
4. 使用更小的图片
在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。

5.StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
6.避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
7. 避免对象的内存泄露
类的静态变量持有大数据对象
静态变量长期维持到大数据对象的引用,阻止垃圾回收。
非静态内部类存在静态实例
非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
资源对象未关闭
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们, 以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。 如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
解决办法: 比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭), 如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小, 对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险,记得try catch后,在finally方法中关闭连接
Handler内存泄漏
Handler作为内部类存在于Activity中,但是Handler生命周期与Activity生命周期往往并不是相同的,比如当Handler对象有Message在排队,则无法释放,进而导致本该释放的Acitivity也没有办法进行回收。

‘肆’ 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成功后立刻恢复虚拟机运行,整个过程对于父进程来讲总耗时只有几毫秒,对用户完全没有影响。

‘伍’ 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中内存优化相关的资料

热点内容
幼儿编程教育培训多少钱 浏览:401
经常生气有什么东西能解压 浏览:900
代理服务器地址和端口可以怎么填 浏览:62
unity5手游编译模型 浏览:265
安卓无人机app源码 浏览:808
pl1编程语言 浏览:801
台达plc编程换算指令大全 浏览:174
手机上的编程游戏 浏览:108
服务器密码机有什么用 浏览:477
dos磁盘命令 浏览:955
单片机cpu52的功能 浏览:691
opc服务器怎么开发 浏览:373
觅喜是个什么app 浏览:402
加密cd机 浏览:946
社保用什么app缴纳 浏览:313
nodevlinux 浏览:582
腾讯tt服务器怎么登录密码 浏览:898
windows命令提示符 浏览:352
win7管理员权限命令 浏览:729
地图app哪个适合老年人用 浏览:74