Android内存优化

来源:互联网 发布:微信推送表情包 知乎 编辑:程序博客网 时间:2024/06/01 15:22

Java反射总结:http://blog.csdn.net/a396901990/article/category/2302221


写在最前:

本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总、挑选、简化后整理而成。

所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读。(本文最后我会尽量列出所参考的文章)。



内存简介:

RAM(random access memory)随机存取存储器。说白了就是内存。

一般Java在内存分配时会涉及到以下区域:

寄存器(Registers):速度最快的存储场所,因为寄存器位于处理器内部我们在程序中无法控制

栈(Stack):存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中

堆(Heap):堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器(GC)来管理。

静态域(static field):  静态存储区域就是指在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量

常量池(constant pool):虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。

非RAM存储:硬盘等永久存储空间


堆栈特点对比:

由于篇幅原因,下面只简单的介绍一下堆栈的一些特性。

:当定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

:当堆中的new产生数组和对象超出其作用域后,它们不会被释放,只有在没有引用变量指向它们的时候才变成垃圾,不能再被使用。即使这样,所占内存也不会立即释放,而是等待被垃圾回收器收走。这也是Java比较占内存的原因。


存取速度比堆要快,仅次于寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

:堆是一个运行时数据区,可以动态地分配内存大小,因此存取速度较慢。也正因为这个特点,堆的生存期不必事先告诉编译器,而且Java的垃圾收集器会自动收走这些不再使用的数据。


:栈中的数据可以共享, 它是由编译器完成的,有利于节省空间。

例如:需要定义两个变量int a = 3;int b = 3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并让a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

:例如上面栈中a的修改并不会影响到b, 而在堆中一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。


内存耗用名词解析:

VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)

PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS


OOM:


内存泄露可以引发很多的问题:

1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)

2.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)

3.直接崩溃(OutOfMemoryError)


ANDROID内存面临的问题:

1.有限的堆内存,原始只有16M

2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同

3.程序不能直接控制

4.支持后台多任务处理(multitasking)

5.运行在虚拟机之上


5R:

本文主要通过如下的5R方法来对ANDROID内存进行优化:

1.Reckon(计算)

首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆

2.Reduce(减少)

消耗更少的资源

3.Reuse(重用)

当第一次使用完以后,尽量给其他的使用

5.Recycle(回收)

返回资源给生产流

4.Review(检查)

回顾检查你的程序,看看设计或代码有什么不合理的地方。


Reckon (计算):

了解自己应用的内存使用情况是很有必要的。如果当内存使用过高的话就需要对其进行优化,因为更少的使用内存可以减少ANDROID系统终止我们的进程的几率,也可以提高多任务执行效率和体验效果。

下面从系统内存(system ram)和堆内存(heap)两个方面介绍一些查看和计算内存使用情况的方法:


System Ram(系统内存):

观察和计算系统内存使用情况,可以使用Android提供给我们的两个工具procstatsmeminfo。他们一个侧重于后台的内存使用,另一个是运行时的内存使用。

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)

点击单个条目还可以查看详细信息

meminfo:
Android还提供了一个工具叫做meminfo。它是根据PSS标准 (Proportional Set Size——实际物理内存)计算每个进程的内存使用并且按照重要程度排序。
你可以通过命令行去执行它:(adb shell dumpsys meminfo)或者使用在设备上点击Settings > Apps > Running(与Procstats不用,它也可以在老版本上运行)

更多关于Procstatsmeninfo的介绍可以参考我翻译的一篇文章:Process Stats:了解你的APP如何使用内存


Heap(堆内存):

在程序中可以使用如下的方法去查询内存使用情况


ActivityManager#getMemoryClass()

查询可用堆内存的限制

3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存(不过这算作“作弊”)


ActivityManager#getMemoryInfo(ActivityManager.MemoryInfo)
得到的MemoryInfo中可以查看如下Field的属性:
availMem:表示系统剩余内存
lowMemory:它是boolean值,表示系统是否处于低内存运行
hreshold:它表示当系统剩余内存低于好多时就看成低内存运行

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为了提高分配内存速度而缓冲的小对象,即使所有共享它的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。

android.os.Debug#getNativeHeapSize()

返回的是当前进程navtive堆本身总的内存大小

android.os.Debug#getNativeHeapAllocatedSize()

返回的是当前进程navtive堆中已使用的内存大小

android.os.Debug#getNativeHeapFreeSize()

返回的是当前进程navtive堆中已经剩余的内存大小


Memory Analysis Tool(MAT):

通常内存泄露分析被认为是一件很有难度的工作,一般由团队中的资深人士进行。不过,今天我们要介绍的 MAT(Eclipse Memory Analyzer)被认为是一个“傻瓜式“的堆转储文件分析工具,你只需要轻轻点击一下鼠标就可以生成一个专业的分析报告。

如下图:


关于详细的MAT使用我推荐下面这篇文章:使用 Eclipse Memory Analyzer 进行堆转储文件分析


写在最后:

我准备将文章分为上、中、下三部分。在下两篇文章中会将剩余的Reduce,Reuse,Recycle,Review总结完。

ReduceReuse请看第二篇文章:

因为内存知识很零散,而且我也是现学现卖,所以为了尽可能的搜集更多的资料和保证内容的准确性更新速度可能慢点。因为中间还要写点别的烂七八糟的。

写这篇文章的目的就是想弄一个大汇总,将零散的内存知识点总结一下,如果有错误、不足或建议都希望告诉我。


参考文章:

AnDevCon开发者大会演讲PPT:Putting Your App on a Memory Diet

深入Java核心 Java内存分配原理精讲(http://developer.51cto.com/art/201009/225071.htm)

Process Stats: Understanding How Your App Uses RAM(http://blog.csdn.net/a396901990/article/details/38390135)

Android中如何查看内存(http://blog.csdn.net/hudashi/article/details/7050897)

Android内存性能优化(内部资料总结)(http://www.2cto.com/kf/201405/303276.html)


写在最前:

本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总、挑选、简化后整理而成。

所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读。(本文最后我会尽量列出所参考的文章)。


OOM:


内存泄露可以引发很多的问题:

1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)

2.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)

3.直接崩溃(OutOfMemoryError)


ANDROID内存面临的问题:

1.有限的堆内存,原始只有16M

2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同

3.程序不能直接控制

4.支持后台多任务处理(multitasking)

5.运行在虚拟机之上


5R:

本文主要通过如下的5R方法来对ANDROID内存进行优化:


1.Reckon(计算)

首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆

2.Reduce(减少)

消耗更少的资源

3.Reuse(重用)

当第一次使用完以后,尽量给其他的使用

5.Recycle(回收)

返回资源给生产流

4.Review(检查)

回顾检查你的程序,看看设计或代码有什么不合理的地方。



Reckon:


关于内存简介,和Reckon(内存计算)的内容请看上一篇文章:ANDROID内存优化(大汇总——上)



Reduce :


Reduce的意思就是减少,直接减少内存的使用是最有效的优化方式。

下面来看看有哪些方法可以减少内存使用:


Bitmap
Bitmap是内存消耗大户,绝大多数的OOM崩溃都是在操作Bitmap时产生的,下面来看看如何几个处理图片的方法:


图片显示:

我们需要根据需求去加载图片的大小。

例如在列表中仅用于预览时加载缩略图(thumbnails )。

只有当用户点击具体条目想看详细信息的时候,这时另启动一个fragment/activity/对话框等等,去显示整个图片


图片大小:

直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃。 
使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。 
属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。 

[java] view plaincopyprint?
  1. BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();  
  2. bitmapFactoryOptions.inJustDecodeBounds = true;  
  3. bitmapFactoryOptions.inSampleSize = 2;  
  4. // 这里一定要将其设置回false,因为之前我们将其设置成了true    
  5. // 设置inJustDecodeBounds为true后,decodeFile并不分配空间,即,BitmapFactory解码出来的Bitmap为Null,但可计算出原始图片的长度和宽度    
  6. options.inJustDecodeBounds = false;  
  7. Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);  


图片像素:

Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存 
ARGB_4444:每个像素占用2byte内存 
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存 

Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(565没有透明度属性),如下:
[java] view plaincopyprint?
  1. publicstaticBitmapreadBitMap(Contextcontext, intresId) {  
  2.     BitmapFactory.Optionsopt = newBitmapFactory.Options();  
  3.     opt.inPreferredConfig = Bitmap.Config.RGB_565;  
  4.     opt.inPurgeable = true;  
  5.     opt.inInputShareable = true;  
  6.     //获取资源图片   
  7.     InputStreamis = context.getResources().openRawResource(resId);  
  8.     returnBitmapFactory.decodeStream(is, null, opt);  
  9. }  

图片回收:

使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。

下面是释放Bitmap的示例代码片段。

[java] view plaincopyprint?
  1. // 先判断是否已经回收  
  2. if(bitmap != null && !bitmap.isRecycled()){  
  3.     // 回收并且置为null  
  4.     bitmap.recycle();  
  5.     bitmap = null;  
  6. }  
  7. System.gc();  

捕获异常:

经过上面这些优化后还会存在报OOM的风险,所以下面需要一道最后的关卡——捕获OOM异常:

[java] view plaincopyprint?
  1. Bitmap bitmap = null;  
  2. try {  
  3.     // 实例化Bitmap  
  4.     bitmap = BitmapFactory.decodeFile(path);  
  5. catch (OutOfMemoryError e) {  
  6.     // 捕获OutOfMemoryError,避免直接崩溃  
  7. }  
  8. if (bitmap == null) {  
  9.     // 如果实例化失败 返回默认的Bitmap对象  
  10.     return defaultBitmapMap;  
  11. }  



修改对象引用类型:


引用类型:

引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。

强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;

弱引用(WeakReference)   

弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 


虚引用(PhantomReference)   

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。  


软引用和弱引用的应用实例:

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

下面以使用软引用为例来详细说明(弱引用的使用方式与软引用是类似的):

假设我们的应用会用到大量的默认图片,而且这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。

首先定义一个HashMap,保存软引用对象。

[java] view plaincopyprint?
  1. private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();  
再来定义一个方法,保存Bitmap的软引用到HashMap。

[java] view plaincopyprint?
  1. public void addBitmapToCache(String path) {  
  2.        // 强引用的Bitmap对象  
  3.        Bitmap bitmap = BitmapFactory.decodeFile(path);  
  4.        // 软引用的Bitmap对象  
  5.        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);  
  6.        // 添加该对象到Map中使其缓存  
  7.        imageCache.put(path, softBitmap);  
  8.    }  
获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。
[java] view plaincopyprint?
  1. public Bitmap getBitmapByPath(String path) {  
  2.         // 从缓存中取软引用的Bitmap对象  
  3.         SoftReference<Bitmap> softBitmap = imageCache.get(path);  
  4.         // 判断是否存在软引用  
  5.         if (softBitmap == null) {  
  6.             return null;  
  7.         }  
  8.         // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空  
  9.         Bitmap bitmap = softBitmap.get();  
  10.         return bitmap;  
  11.     }  
使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。

需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。


到底什么时候使用软引用,什么时候使用弱引用呢?

个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。

还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。


其他小tips:

对常量使用static final修饰符

让我们来看看这两段在类前面的声明:

static int intVal = 42;
static String strVal = "Hello, world!";
编译器会生成一个叫做clinit的初始化类的方法,当类第一次被使用的时候这个方法会被执行。方法会将42赋给intVal,然后把一个指向类中常量表 的引用赋给strVal。当以后要用到这些值的时候,会在成员变量表中查找到他们。 下面我们做些改进,使用“final”关键字:

static final int intVal = 42;
static final String strVal = "Hello, world!";

现在,类不再需要clinit方法,因为在成员变量初始化的时候,会将常量直接保存到类文件中。用到intVal的代码被直接替换成42,而使用strVal的会指向一个字符串常量,而不是使用成员变量。

将一个方法或类声明为final不会带来性能的提升,但是会帮助编译器优化代码。举例说,如果编译器知道一个getter方法不会被重载,那么编译器会对其采用内联调用。

你也可以将本地变量声明为final,同样,这也不会带来性能的提升。使用“final”只能使本地变量看起来更清晰些(但是也有些时候这是必须的,比如在使用匿名内部类的时候)。

静态方法代替虚拟方法

如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。


减少不必要的全局变量

尽量避免static成员变量引用资源耗费过多的实例,比如Context

因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。 你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。 

避免创建不必要的对象

最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。

对于所有所有基本类型的组合:int数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比 (int,int)对象数组性能要好很多。

总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。


避免内部Getters/Setters
在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。

避免使用浮点数

通常的经验是,在Android设备中,浮点数会比整型慢两倍。


使用实体类比接口好

假设你有一个HashMap对象,你可以将它声明为HashMap或者Map:

Map map1 = new HashMap();HashMap map2 = new HashMap();

哪个更好呢?

按照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式系统。调用一个接口的引用会比调用实体类的引用多花费一倍的时间。如果HashMap完全适合你的程序,那么使用Map就没有什么价值。如果有些地方你不能确定,先避免使用Map,剩下的交给IDE提供的重构功能好了。(当然公共API是一个例外:一个好的API常常会牺牲一些性能)


避免使用枚举

枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。

使用枚举变量可以让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。但是当性能方面有所限制的时候,你就应该避免这种做法了。


for循环

访问成员变量比访问本地变量慢得多,如下面一段代码:

[java] view plaincopyprint?
  1. for(int i =0; i < this.mCount; i++)  {}  

永远不要在for的第二个条件中调用任何方法,如下面一段代码:

[java] view plaincopyprint?
  1. for(int i =0; i < this.getCount(); i++) {}  

对上面两个例子最好改为:

[java] view plaincopyprint?
  1. int count = this.mCount; / int count = this.getCount();  
  2. for(int i =0; i < count; i++)  {}  
在java1.5中引入的for-each语法。编译器会将对数组的引用和数组的长度保存到本地变量中,这对访问数组元素非常好。 但是编译器还会在每次循环中产生一个额外的对本地变量的存储操作(如下面例子中的变量a),这样会比普通循环多出4个字节,速度要稍微慢一些:

[java] view plaincopyprint?
  1. for (Foo a : mArray) {  
  2.     sum += a.mSplat;  
  3. }  

了解并使用类库

选择Library中的代码而非自己重写,除了通常的那些原因外,考虑到系统空闲时会用汇编代码调用来替代library方法,这可能比JIT中生成的等价的最好的Java代码还要好。

当你在处理字串的时候,不要吝惜使用String.indexOf()String.lastIndexOf()等特殊实现的方法。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。

System.arraycopy方法在有JIT的Nexus One上,自行编码的循环快9倍。

android.text.format包下的Formatter类,提供了IP地址转换、文件大小转换等方法;DateFormat类,提供了各种时间转换,都是非常高效的方法。

TextUtils类,对于字符串处理Android为我们提供了一个简单实用的TextUtils类,如果处理比较简单的内容不用去思考正则表达式不妨试试这个在android.text.TextUtils的类

高性能MemoryFile类,很多人抱怨Android处理底层I/O性能不是很理想,如果不想使用NDK则可以通过MemoryFile类实现高性能的文件读写操作。MemoryFile适用于哪些地方呢?对于I/O需要频繁操作的,主要是和外部存储相关的I/O操作,MemoryFile通过将 NAND或SD卡上的文件,分段映射到内存中进行修改处理,这样就用高速的RAM代替了ROM或SD卡,性能自然提高不少,对于Android手机而言同时还减少了电量消耗。该类实现的功能不是很多,直接从Object上继承,通过JNI的方式直接在C底层执行。



Reuse:


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来进行缓存处理

[java] view plaincopyprint?
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     ViewHolder vHolder = null;  
  4.     //如果convertView对象为空则创建新对象,不为空则复用    
  5.     if (convertView == null) {  
  6.         convertView = inflater.inflate(..., null);  
  7.         // 创建 ViewHodler 对象    
  8.         vHolder = new ViewHolder();  
  9.         vHolder.img= (ImageView) convertView.findViewById(...);  
  10.         vHolder.tv= (TextView) convertView.findViewById(...);  
  11.         // 将ViewHodler保存到Tag中(Tag可以接收Object类型对象,所以任何东西都可以保存在其中)  
  12.         convertView.setTag(vHolder);  
  13.     } else {  
  14.         //当convertView不为空时,通过getTag()得到View    
  15.         vHolder = (ViewHolder) convertView.getTag();  
  16.     }  
  17.     // 给对象赋值,修改显示的值    
  18.     vHolder.img.setImageBitmap(...);  
  19.     vHolder.tv.setText(...);  
  20.     return convertView;  
  21. }  
  22. //将显示的View 包装成类    
  23. static class ViewHolder {  
  24.     TextView tv;  
  25.     ImageView img;  
  26. }  


池(PooL)

对象池:

对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。


线程池:

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。


java提供了ExecutorServiceExecutors类,我们可以应用它去建立线程池。

通常可以建立如下4种:

[java] view plaincopyprint?
  1. /** 每次只执行一个任务的线程池 */  
  2. ExecutorService singleTaskExecutor =  Executors.newSingleThreadExecutor();  
  3.   
  4. /** 每次执行限定个数个任务的线程池 */  
  5. ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);  
  6.   
  7. /** 所有任务都一次性开始的线程池 */  
  8. ExecutorService allTaskExecutor = Executors.newCachedThreadPool();  
  9.   
  10. /** 创建一个可在指定时间里执行任务的线程池,亦可重复执行 */  
  11. ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);  


更多关于线程池的内容我推荐这篇文章:http://www.xuanyusong.com/archives/2439

注意:

要根据情况适度使用缓存,因为内存有限。

能保存路径地址的就不要存放图片数据,不经常使用的尽量不要缓存,不用时就清空。


写在最后:

我准备将文章分为上、中、下三部分。

在最后一篇文章中会将剩余的Recycle,Review总结完。

因为内存知识很零散,而且我也是现学现卖,所以为了尽可能的搜集更多的资料和保证内容的准确性更新速度可能慢点。因为中间还要写点别的烂七八糟的。

写这篇文章的目的就是想弄一个大汇总,将零散的内存知识点总结一下,如果有错误、不足或建议都希望告诉我。


参考文章:

解析Android开发优化之:软引用与弱引用的应用(http://www.jb51.net/article/36627.htm)
android内存泄露优化总结(http://blog.csdn.net/imain/article/details/8560986)
Android 内存优化(http://blog.csdn.net/awangyunke/article/details/20380719)
Android开发优化之——对Bitmap的内存优化(http://blog.csdn.net/arui319/article/details/7953690)
关于android性能,内存优化(http://www.cnblogs.com/zyw-205520/archive/2013/02/17/2914190.html)

Android研究院之应用开发线程池的经典使用(http://www.xuanyusong.com/archives/2439)


0 0
原创粉丝点击