android中的内存优化

内存泄露可以引发很多的问题:
1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)
2.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)
3.直接崩溃(OutOfMemoryError)
ANDROID内存面临的问题:
1.有限的堆内存,原始只有16M
2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同
3.程序不能直接控制
4.支持后台多任务处理(multitasking)
5.运行在虚拟机之上
我主要通过以下的5个方面(5R)来对ANDROID内存进行优化:
1.Reckon(计算)首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆
2.Reduce(减少)消耗更少的资源
3.Reuse(重用)当第一次使用完以后,尽量给其他的使用
4.Recycle(回收)返回资源给生产流
5.Review(检查)回顾检查你的程序,看看设计或代码有什么不合理的地方。
下面就从这几个方面详细说一下:
一、Reckon(计算)
了解自己应用的内存使用情况是很有必要的。如果当内存使用过高的话就需要对其进行优化,因为更少的使用内存可以减少ANDROID系统终止我们的进程的几率,也可以提高多任务执行效率和体验效果。下面从系统内存(system ram)和堆内存(heap)两个方面介绍一些查看和计算内存使用情况的方法:
1.System Ram(系统内存):
观察和计算系统内存使用情况,可以使用Android提供给我们的两个工具procstats,meminfo。他们一个侧重于后台的内存使用,另一个是运行时的内存使用。
(1)Process Stats:
Android 4.4 KitKat 提出了一个新系统服务,叫做procstats。它将帮助你更好的理解你app在后台(background)时的内存使用情况。
Procstats可以去监视你app在一段时间的行为,包括在后台运行了多久,并在此段时间使用了多少内存。从而帮助你快速的找到应用中不效率和不规范的地方去避免影响其performs,尤其是在低内存的设备上运行时。
你可以通过adb shell命令去使用procstats(adb shell dumpsys procstats –hours 3),或者更方便的方式是运行Process Stats开发者工具(在4.4版本的手机中点击Settings > Developer options > Process Stats)
android中的内存优化_第1张图片
点击单个条目还可以查看详细信息
(2)meminfo:
Android还提供了一个工具叫做meminfo。它是根据PSS标准 (Proportional Set Size——实际物理内存)计算每个进程的内存使用并且按照重要程度排序。
你可以通过命令行去执行它:(adb shell dumpsys meminfo)或者使用在设备上点击Settings > Apps > Running(与Procstats不用,它也可以在老版本上运行)
2.Heap(堆内存):
在程序中可以使用如下的方法去查询内存使用情况
(1)ActivityManager#getMemoryClass()
查询可用堆内存的限制,3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存(不过这算作“作弊”)
(2)ActivityManager#getMemoryInfo(ActivityManager.MemoryInfo)
得到的MemoryInfo中可以查看如下Field的属性:
availMem:表示系统剩余内存
lowMemory:它是boolean值,表示系统是否处于低内存运行
hreshold:它表示当系统剩余内存低于好多时就看成低内存运行
(3)android.os.Debug#getMemoryInfo(Debug.MemoryInfo memoryInfo)
得到的MemoryInfo中可以查看如下Field的属性:
dalvikPrivateDirty: The private dirty pages used by dalvik。
dalvikPss :The proportional set size for dalvik.
dalvikSharedDirty :The shared dirty pages used by dalvik.
nativePrivateDirty :The private dirty pages used by the native heap.
nativePss :The proportional set size for the native heap.
nativeSharedDirty :The shared dirty pages used by the native heap.
otherPrivateDirty :The private dirty pages used by everything else.
otherPss :The proportional set size for everything else.
otherSharedDirty :The shared dirty pages used by everything else.
dalvik:是指dalvik所使用的内存。
native:是被native堆使用的内存。应该指使用C\C++在堆上分配的内存。
other:是指除dalvik和native使用的内存。但是具体是指什么呢?至少包括在C\C++分配的非堆内存,比如分配在栈上的内存。
private:是指私有的。非共享的。
share:是指共享的内存。
PSS:实际使用的物理内存(比例分配共享库占用的内存)
PrivateDirty:它是指非共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使你的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
SharedDirty:参照PrivateDirty我认为它应该是指共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使所有共享它的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
(4)android.os.Debug#getNativeHeapSize()
返回的是当前进程navtive堆本身总的内存大小
(5)android.os.Debug#getNativeHeapAllocatedSize()
返回的是当前进程navtive堆中已使用的内存大小
(6)android.os.Debug#getNativeHeapFreeSize()
返回的是当前进程navtive堆中已经剩余的内存大小
(7)Memory Analysis Tool(MAT):
通常内存泄露分析被认为是一件很有难度的工作,一般由团队中的资深人士进行。不过,今天我们要介绍的 MAT(Eclipse Memory Analyzer)被认为是一个“傻瓜式“的堆转储文件分析工具,你只需要轻轻点击一下鼠标就可以生成一个专业的分析报告。
如下图:
android中的内存优化_第2张图片
二、Reduce(减少)主要通过以下方法减少内存使用:
(一)Bitmap:
Bitmap是内存消耗大户,绝大多数的OOM崩溃都是在操作Bitmap时产生的,下面来看看如何几个处理图片的方法:
(1)图片显示:根据需求去加载图片的大小。例如在列表中仅用于预览时加载缩略图(thumbnails ),只有当用户点击具体条目想看详细信息的时候,才显示整个图片
(2)图片大小:直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃。使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
(3)图片像素:Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存
Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(无透明度),或者ARGB_4444(有透明度)
(4)图片回收:使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。
(5)捕获异常:经过上面这些优化后还会存在报OOM的风险,所以下面需要一道最后的关卡——捕获OOM异常
(二)修改对象引用类型:
引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。
如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,对于软引用或者弱引用的Bitmap缓存方案,现在已经不推荐使用了。
(三)其他减少内存使用的方法
(1)对常量使用static final修饰符
(2)静态方法代替虚拟方法
(3)减少不必要的全局变量
(4)避免创建不必要的对象
(5)避免内部Getters/Setters
(6)避免使用浮点数
(7)使用实体类比接口好
(8)免使用枚举
(9)for循环时,访问成员变量比访问本地变量慢得多,永远不要在for的第二个条件中调用任何方法,在java1.5中引入的for-each语法(例如for (Foo a : mArray))编译器还会在每次循环中产生一个额外的对本地变量的存储操作(如上面例子中的变量a),这样会比普通循环多出4个字节,速度要稍微慢一些
(10)了解并使用类库
三、Reuse(重用)
重用是减少内存消耗的重要手段之一,核心思路就是将已经存在的内存资源重新使用而避免去创建新的,最典型的使用就是缓存(Cache)和池(Pool)。
(一)Bitmap缓存:Bitmap缓存分为两种:一种是内存缓存,一种是硬盘缓存。
内存缓存(LruCache):
以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。系统提供的LruCache类是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。
注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。
硬盘缓存(DiskLruCache):
一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。
在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。
注意:如果访问图片的次数非常频繁,那么ContentProvider可能更适合用来存储缓存图片,例如Image Gallery这样的应用程序。更多关于内存缓存和硬盘缓存的内容请看Google官方教程https://developer.android.com/develop/index.html
(二)图片缓存的开源项目:
对于图片的缓存现在都倾向于使用开源项目,这里我列出几个我搜到的:
1. Android-Universal-Image-Loader 图片缓存
目前使用最广泛的图片缓存,支持主流图片缓存的绝大多数特性。
项目地址:https://github.com/nostra13/Android-Universal-Image-Loader
2. picasso square开源的图片缓存
项目地址:https://github.com/square/picasso
特点:
(1)可以自动检测adapter的重用并取消之前的下载
(2)图片变换
(3)可以加载本地资源
(4)可以设置占位资源
(5)支持debug模式
3. ImageCache 图片缓存,包含内存和Sdcard缓存
项目地址:https://github.com/Trinea/AndroidCommon
特点:
(1)支持预取新图片,支持等待队列
(2)包含二级缓存,可自定义文件名保存规则
(3)可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法
(4)可方便的保存及初始化恢复数据
(5)支持不同类型网络处理
(6)可根据系统配置初始化缓存等
4. Android 网络通信框架Volley
项目地址:https://android.googlesource.com/platform/frameworks/volley
我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在2013年的Google I/O发布了Volley。Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。
特点:
(1)JSON,图像等的异步下载;
(2)网络请求的排序(scheduling)
(3)网络请求的优先级处理
(4)缓存
(5)多级别取消请求
(6)和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)
(三)Adapter适配器
在Android中Adapter使用十分广泛,特别是在list中。所以adapter是数据的 “集散地” ,所以对其进行内存优化是很有必要的。主要使用convertView和ViewHolder来进行缓存处理
(四)池(PooL)
1.对象池:
对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。
2.线程池:
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。java提供了ExecutorService和Executors类,我们可以应用它去建立线程池。
(五)注意:要根据情况适度使用缓存,因为内存有限。能保存路径地址的就不要存放图片数据,不经常使用的尽量不要缓存,不用时就清空。
四、Recycle(回收)
回收可以说是在内存使用中最重要的部分。因为内存空间有限,无论你如何优化,如何节省内存总有用完的时候。而回收的意义就在于去清理和释放那些已经闲置,废弃不再使用的内存资源和内存空间。
(一)垃圾回收(GC):
1、Java垃圾回收器:
Java技术提供了一个系统级的线程,即垃圾收集器线程(Garbage Collection Thread),来跟踪每一块分配出去的内存空间,当Java 虚拟机(Java Virtual Machine)处于空闲循环时,垃圾收集器线程会自动检查每一快分配出去的内存空间,然后自动回收每一快可以回收的无用的内存块。
2、作用:
1.清除不用的对象来释放内存:
2.消除堆内存空间的碎片:
3、垃圾回收器优点:
1.减轻编程的负担,提高效率:
2.它保护程序的完整性:
4、垃圾回收器缺点:
1.占用资源时间:
2.不可预知:
3.不确定性:
4.不可操作
5、finalize():
每一个对象都有一个finalize方法,这个方法是从Object类继承来的。
当垃圾回收确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

Java 技术允许使用finalize方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
简单的说finalize方法是在垃圾收集器删除对象之前对这个对象调用的
6、System.gc():
我们可以调用System.gc方法,建议虚拟机进行垃圾回收工作(注意,是建议,但虚拟机会不会这样干,我们也无法预知!)
总之,在Java语言中,判断一块内存空间是否符合垃圾收集器收集的标准只有两个:
1.给对象赋予了空值null,以下再没有调用过。
2.给对象赋予了新值,既重新分配了内存空间。
最后再次提醒一下,一块内存空间符合了垃圾收集器的收集标准,并不意味着这块内存空间就一定会被垃圾收集器收集。
(二)资源的回收:
1、Thread(线程)回收:
线程中涉及的任何东西GC都不能回收(Anything reachable by a thread cannot be GC’d ),所以线程很容易造成内存泄露。因为运行中的线程是称之为垃圾回收根(GC Roots)对象的一种,不会被垃圾回收。当垃圾回收器判断一个对象是否可达,总是使用垃圾回收根对象作为参考点。
2、Cursor(游标)回收:
Cursor是Android查询数据后得到的一个管理数据集合的类,在使用结束以后。应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
(三)Receiver(接收器)回收
调用registerReceiver()后未调用unregisterReceiver().
当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册
也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory()方法取消注册
(四)Stream/File(流/文件)回收:
主要针对各种流,文件资源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。
五、Review(检查)
主要目的就是检查代码中存在的不合理和可以改进的地方
(一)Code Review(代码检查):
Code Review主要检查代码中存在的一些不合理或可以改进优化的地方。
(二)UI Review(视图检查):
Android对于视图中控件的布局渲染等会消耗很多的资源和内存,所以这部分也是我们需要注意的。
1、减少视图层级:
减少视图层级可以有效的减少内存消耗,因为视图是一个树形结构,每次刷新和渲染都会遍历一次。
1.hierarchyviewer:想要减少视图层级首先就需要知道视图层级,所以下面介绍一个SDK中自带的一个非常好用的工具hierarchyviewer。
你可以在下面的地址找到它:\sdk path\sdk\tools
android中的内存优化_第3张图片
如上图大家可以看到,hierarchyviewer可以非常清楚的看到当前视图的层级结构,并且可以查看视图的执行效率(视图上的小圆点,绿色表示流畅,黄色和红色次之),所以我们可以很方便的查看哪些view可能会影响我们的性能从而去进一步优化它。
2.ViewStub标签
此标签可以使UI在特殊情况下,直观效果类似于设置View的不可见性,但是其更大的意义在于被这个标签所包裹的Views在默认状态下不会占用任何内存空间。
3.include标签
可以通过这个标签直接加载外部的xml到当前结构中,是复用UI资源的常用标签。
4.merge标签
它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。
5.布局用Java代码比写在XML中快
一般情况下对于Android程序布局往往使用XML文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以及执行效率,可以通过Java代码执行创建,虽然Android编译过的XML是二进制的,但是加载XML解析器的效率对于资源占用还是比较大的,Java处理效率比XML快得多,但是对于一个复杂界面的编写,可能需要一些套嵌考虑,如果你思维灵活的话,使用Java代码来布局你的Android应用程序是一个更好的方法。
3、重用系统资源:
1. 利用系统定义的id
在xml文件中引用系统的id,只需要加上“@android:”前缀即可。如果是在Java代码中使用系统资源,和使用自己的资源基本上是一样的。不同的是,需要使用android.R类来使用系统的资源,而不是使用应用程序指定的R类。
2. 利用系统的图片资源
这样做的好处,一个是美工不需要重复的做一份已有的图片了,可以节约不少工时;另一个是能保证我们的应用程序的风格与系统一致。
3. 利用系统的字符串资源
如果使用系统的字符串,默认就已经支持多语言环境了。比如直接使用了@android:string/yes和@android:string/no,在简体中文环境下会显示“确定”和“取消”,在英文环境下会显示“OK”和“Cancel”。
4. 利用系统的Style
假设布局文件中有一个TextView,用来显示窗口的标题,使用中等大小字体。可以使用下面的代码片段来定义TextView的Style。

你可能感兴趣的:(android)