一、Android内存管理

1.1Dalvik

Dalvik虚拟机是Android程序的虚拟机,是Android中Java程序的运行基础。其指令集基于寄存器架构,执行其特有的文件格式——dex字节码来完成对象生命周期管理、堆栈管理、线程管理、安全异常管理、垃圾回收等重要功能。
Dalvik虚拟机的内存大体上可以分为 Java ObjectHeap、BitmapMemory和NativeHeap三种。

JavaObject Heap:用于分配对象

BitmapMemory:用来处理图像,≥Android3.0,归到ObjectHeap

NativeHeap: malloc分配,受系统限制

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

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

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

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

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

常量池(constantpool):虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(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

ANDROID内存面临的问题:

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

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

3.程序不能直接控制

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

5.运行在虚拟机之上

1.2查看最大内存限制

ActivityManager#getMemoryClass()

查询可用堆内存的限制

ActivityManagermActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);int memoryClass = am.getMemoryClass();             //96MB [Mi2S,Android 4.1]

3.0(HoneyComb)以上的版本可以通过AndroidManifest.xml文件中设置来申请更多的堆内存(不过这算作作弊

AndroidManifest.xml中添加

int largeMemoryClass = am.getLargeMemoryClass();           //384MB  [Mi2S,Android 4.1]

ActivityManager#getMemoryInfo(ActivityManager.MemoryInfo)

得到的MemoryInfo中可以查看如下Field的属性:

availMem:表示系统剩余内存

lowMemory:它是boolean值,表示系统是否处于低内存运行

hreshold:它表示当系统剩余内存低于好多时就看成低内存运行

android.os.Debug#getNativeHeapSize()

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

android.os.Debug#getNativeHeapAllocatedSize()

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

android.os.Debug#getNativeHeapFreeSize()

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

1.3垃圾回收机制

2.3之前:

1.Stop-the-world :也就是垃圾收集线程在执行的时候,其它的线程都停止;

2.Full heap collection:一次收集完全部的垃圾;

3.Pause time:≥100ms:一次垃圾收集造成的程序中止时间通常都大于100ms

2.3以及更高的版本中:

1.Cocurrent:大多数情况下,垃圾收集线程与其它线程是并发执行的;

2.Partial collection:一次可能只收集一部分垃圾;

3.Pause time:≤5ms:一次垃圾收集造成的程序中止时间通常都小于5ms

1.4OutOfMemory Error

主要原因:

1.内存泄露:程序中存在对无用对象的引用,导致GC无法回收。

2.内存超限:保存了多个耗用内存过大的对象(如Bitmap)。

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

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

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

3.直接崩溃(OutOfMemoryError

二、内存优化

2.1 Coding

Tip 1:使用优化过的数据容器。

SparseArray,SparseBooleanArray,LongSparseArray,代替HashMap

前提:KeyInteger类型

原因:

HashMap是内存低效的,因为每一个mapping都需要单独的entry(如下图)。


每个元素多占用8byte内存(多了nexthash两个成员变量)。AutoBoxintInteger,导致产生另一个对象】也会额外加4byteEntry对象本身至少16byte

SparseArray可以避免AutoBox,查找方法为二分查找,效率比HashMap低一些,但百量级以内性能差距不大。

HashMap hashMap = new HashMap();

替换为

SparseArraysparseArray = new SparseArray();

Tip 2:使用IntentService替代Service。

 说明:

Service是一个没有界面的服务,但其实它不是后台的,所有的代码默认在UI线程执行。若要执行耗时操作,需要新开线程或者使用AsyncTask

IntentService继承自Service,所以它也是一个服务。用来处理异步请求。

IntentService使用队列的方式将请求的Intent加入队列,然后开启一个workerthread(线程)来处理队列中的Intent,对于异步的startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的workerthread中处理,不会阻塞应用程序的主线程。

IntentServiceService区别:
1,Service
默认在UI线程执行;而IntentServiceonHandleIntent方法在后台执行。
2,Service
start后,如果没有手动stop会一直存在;而IntentService在执行完后自动退出。

IntentService优势:

新开线程,非UI线程;

顺序处理Intent

执行完自动退出。

•Android进程处理器会尽可能的不kill掉你。

Tip 3:尽量避免使用Enum

枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积,相对于静态常量来说需要两倍甚至更多的内存。

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

Tip 4:使用混淆器移除不必要的代码。

ProGuard工具通过移除无用代码,使用语意模糊来保留类,字段和方法来压缩,优化和混淆代码。可以使你的代码更加完整,更少的RAM映射页。

Tip 5:尽量不要因一两个特性而使用大体积类库。

Tip 6:频繁修改时使用 StringBuffer(Thread-Safe)或StringBuilder(Thread-Unsafe)。

使用String修改字符串时,若修改后字符串在字符串常量区不存在,便会新生成一个String对象。

Tip 7:对于常量,请尽量使用static final。

如果使用final定义常量之后,会减少编译器在类生成时初始化方法调用时对常量的存储,对于int型常量,将会直接使用其数值来进行替换,而对于String对象将会使用相对廉价的“stringconstant”指令来替换字段查找表。虽然这个方法并不完全对所有类型都有效,但是,将常量声明为staticfinal绝对是一个好的做法。

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

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方法不会被重载,那么编译器会对其采用内联调用。

Tip 8:对象不用时最好显式置为Null。

对象不用时最好显式置为Null可以减少GC开销。

Tip 9:防止对象被强引用着。

防止对象被一个正在运行的线程、一个类中的static变量强引用着。

警惕:静态变量引起内存泄露

classA{  Context context;  public A(Context context){    this.context= context;  }}static A a;@Override protected voidonCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  a =newA(this);}

这段代码中有一个静态的A对象。代码片段a = new A(this)对A对象进行了初始化。这时A对象拥有了当前Activity对象的引用,Activity又引用了整个页面中所有的对象。

如果当前的Activity被重新创建(比如横竖屏切换,默认情况下整个Activity会被重新创建),由于A引用了第一次创建的Activity,就会导致第一次创建的Activity不能被垃圾回收器回收,从而导致第一次创建的Activity中的所有对象都不能被回收。这个时候,一部分内存就浪费掉了。

Tip 10:防止对象JNI中的指针引用着。

防止在Java层创建一个对象,同时也在C++层创建一个对象,然后通过JNI让这两个对象相互引用。

Tip 11:警惕使用Activity Context引起内存泄露。

应该尽量使用Application Context

在Android中,ApplicationContext的生命周期和应用的生命周期一样长,而不是取决于某个Activity的生命周期。如果想保持一个长期生命的对象,并且这个对象需要一个Context,就可以使用Application对象。

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

Tip 12:静态方法代替虚拟方法。

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

2.2 Bitmap

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

Tip 1:捕获异常

Bitmapbitmap = null; try{     // 实例化Bitmap     bitmap =BitmapFactory.decodeFile(path); }catch (OutOfMemoryError e) {     // 捕获OutOfMemoryError,避免直接崩溃 } if(bitmap == null) {     // 返回默认的Bitmap对象     return defaultBitmapMap; }

因为Bitmap是吃内存大户,为了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。

OutOfMemoryError是一种Error,而不是Exception。

Tip 2:缓存通用的图像【该方法测试无效】

应用场景:默认头像。有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。

此方法无效!因为:

因为Android自带资源文件缓存机制:

Resource.java类中有LongSparseArray> mDrawableCache

每次会new 一个Drawable,但内部bitmap还是指向cache中的。

Tip 3:压缩图片

BitmapFactory.Optionsoptions = new BitmapFactory.Options();options.inSampleSize= 2;

如果图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程中,就会发生OutOfMemory异常。

使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4。

如果知道图片的像素过大,就可以对其进行缩小。那么如何才知道图片是否过大呢?

使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。

        Bitmap pic = null;        BitmapFactory.Options op = newBitmapFactory.Options();        op.inJustDecodeBounds =true;         pic = BitmapFactory.decodeFile(filePath,op);        float wRatio = (float) Math.ceil(op.outWidth / 720f);        float hRatio = (float) Math.ceil(op.outHeight / 1280f);        if (wRatio > 1 || hRatio > 1) {            op.inSampleSize = (int) Math.max(wRatio, hRatio);        }        op.inJustDecodeBounds =false;        try {            pic = BitmapFactory.decodeFile(filePath,op);        } catch (OutOfMemoryError e) {            e.printStackTrace();        }

先获取图片真实的宽度和高度,然后判断是否需要缩小。如果不需要缩小,设置inSampleSize的值为1。如果需要缩小,则动态计算并设置inSampleSize的值,对图片进行缩小。

Tip 4:及时回收Bitmap的内存(≤Android 2.3.3,API10)

Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。

仔细查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存。从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的。

// 先判断是否已经回收 if(bitmap != null && !bitmap.isRecycled()){     // 回收并且置为null     bitmap.recycle();     bitmap = null; } System.gc();

Tip 5 BitmapFactory.Options.inBitmap (≥Android 3.0,API11)

作用:内存重用

Reuse重用,减少内存消耗的重要手段之一。

核心思路就是将已经存在的内存资源重新使用而避免去创建新的,最典型的使用就是缓存(Cache池(Pool

 

Bitmap缓存分为两种:

一种是内存缓存,一种是硬盘缓存。

内存缓存(LruCache):

以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。系统提供的LruCache类是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。

注意以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(APILevel 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。

 

硬盘缓存(DiskLruCache):

一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。

在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。

注意:如果访问图片的次数非常频繁,那么ContentProvider可能更适合用来存储缓存图片,例如Image Gallery这样的应用程序。

更多关于内存缓存和硬盘缓存的内容请看Google官方教程https://developer.android.com/develop/index.html

 

定义和存储:

Set> mReusableBitmaps;        //声明private LruCache mMemoryCache; // If you're running on Honeycomb or newer, create a// synchronized HashSet of references to reusable bitmaps.if (Utils.hasHoneycomb()) {         //Android版本判断    mReusableBitmaps =            Collections.synchronizedSet(new HashSet>());      //定义} mMemoryCache = new LruCache(mCacheParams.memCacheSize) {     // Notify the removed entry that is no longer being cached.    @Override    protected void entryRemoved(boolean evicted, String key,            BitmapDrawable oldValue, BitmapDrawable newValue) {        if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {            // The removed entry is a recycling drawable, so notify it            // that it has been removed from the memory cache.            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);        } else {            // The removed entry is a standard BitmapDrawable.            if (Utils.hasHoneycomb()) {                // We're running on Honeycomb or later, so add the bitmap                // to a SoftReference set for possible use with inBitmap later.                mReusableBitmaps.add                        (new SoftReference(oldValue.getBitmap()));    //存入从LRUCache中删除的对象            }        }    }....}
使用方法详见: https://developer.android.com/training/displaying-bitmaps/manage-memory.html#inBitmap

限制: 

 before Android4.4 (API level 19):宽度及高度必须完全相等

 after:尺寸小于等于既有内存

 

图片缓存的开源项目:

对于图片的缓存现在都倾向于使用开源项目,这里我列出几个我搜到的:

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)可选择多种缓存算法(FIFOLIFOLRUMRULFUMFU13)或自定义缓存算法
(4)可方便的保存及初始化恢复数据
(5)支持不同类型网络处理
(6)可根据系统配置初始化缓存等

4. Android 网络通信框架Volley

项目地址:https://android.googlesource.com/platform/frameworks/volley

我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoaderHttpURLConnectionAsyncTaskHTTPClientApache)等,在2013年的GoogleI/O发布了VolleyVolleyAndroid平台上的网络通信库,能使网络通信更快,更简单,更健壮。

特点:

(1)JSON,图像等的异步下载;

(2)网络请求的排序(scheduling

(3)网络请求的优先级处理

(4)缓存

(5)多级别取消请求

(6)Activity和生命周期的联动(Activity结束时同时取消所有网络请求)

Tip 6:设置像素模式

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

Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。所以在对图片效果不是特别高的情况下使用RGB_565565没有透明度属性),如下:

public static Bitmap readBitMap(Context context, int resId) {  BitmapFactory.Options opt = new BitmapFactory.Options();  opt.inPreferredConfig = Bitmap.Config.RGB_565;  opt.inPurgeable = true;  opt.inInputShareable = true;  //获取资源图片  InputStream is = context.getResources().openRawResource(resId);  return BitmapFactory.decodeStream(is, null, opt);}

2.3 ListView

Tip 1:从ContentView获取缓存的view

如果不使用缓存convertView的话,调用getView时每次都会重新创建View,这样之前的View可能还没有销毁,加之不断的新建View势必会造成内存泄露。

Tip:使用ViewHolder模式来避免没有必要的调用findViewById()

ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById()。

下面算是一个标准的使用模版:

主要使用convertViewViewHolder来进行缓存处理

   @Override    publicViewgetView(int position, View convertView, ViewGroup parent){         ViewHolder vHolder = null;         //如果convertView对象为空则创建新对象,不为空则复用           if (convertView ==null) {             convertView = inflater.inflate(...,null);             // 创建 ViewHodler对象               vHolder = new ViewHolder();             vHolder.img= (ImageView)convertView.findViewById(...);             vHolder.tv= (TextView)convertView.findViewById(...);             // 将ViewHodler保存到Tag中(Tag可以接收Object类型对象,所以任何东西都可以保存在其中)             convertView.setTag(vHolder);         } else {             //当convertView不为空时,通过getTag()得到View               vHolder = (ViewHolder)convertView.getTag();         }         // 给对象赋值,修改显示的值           vHolder.img.setImageBitmap(...);         vHolder.tv.setText(...);         return convertView;    }    // 将显示的View包装成类   staticclassViewHolder {        TextView tv;        ImageView img;    }

2.4 对象引用类型

引用类型:

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

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

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

Map> imageCache = new HashMap>();

弱引用(WeakReference   

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

Map>cacheMap = new HashMap>();

不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

虚引用(PhantomReference   

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

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

注意:对于SoftReference(软引用)或者WeakReference(弱引用)Bitmap缓存方案,现在已经不推荐使用了。自Android2.3版本(APILevel 9)开始,垃圾回收器更着重于对软/弱引用的回收,所以下面的内容可以选择忽略。

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

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

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

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

private Map> imageCache = new HashMap>();

再来定义一个方法,保存Bitmap的软引用到HashMap

    public void addBitmapToCache(Stringpath) {        // 强引用的Bitmap对象        Bitmap bitmap =BitmapFactory.decodeFile(path);        // 软引用的Bitmap对象        SoftReference softBitmap= newSoftReference(bitmap);        // 添加该对象到Map中使其缓存        imageCache.put(path, softBitmap);    }

获取的时候,可以通过SoftReferenceget()方法得到Bitmap对象。

   publicBitmap getBitmapByPath(String path) {        // 从缓存中取软引用的Bitmap对象        SoftReferencesoftBitmap = imageCache.get(path);        // 判断是否存在软引用        if (softBitmap ==null) {            return null;        }        // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空        Bitmap bitmap = softBitmap.get();        return bitmap;    }

使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。

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

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

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

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

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

案例:用双缓存技术优化listview异步加载网络图片

详见:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0920/1554.html

2.5 UI

Tip 1:利用系统资源

·       系统定义的id:如@android:id/list

·       系统的图片资源:如@*android:drawable/ic_menu_attachment

·       系统的文字串资源:如@android:string/yes

·       系统的Style:如android:textAppearance="?android:attr/textAppearanceMedium“

·       系统的颜色定义:如android:background ="@android:color/transparent

说明:

Android系统本身有很多的资源,包括各种各样的字符串、图片、动画、样式和布局等等,这些都可以在应用程序中直接使用。这样做的好处很多,既可以减少内存的使用,又可以减少部分工作量,也可以缩减程序安装包的大小。

Android中没有公开的资源,在xml中直接引用会报错。除了去找到对应资源并拷贝到我们自己的应用目录下使用以外,我们还可以将引用“@android”改成“@*android”解决。

Tip 2:通用模块抽离

 

Tip 3ViewStub

ViewStub是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。

View的初始可见设为View.GONE,Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。

Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。

需要注意的是:

ViewStub只能Inflate一次,之后ViewStub对象会被置为空。

ViewStub只能用来Inflate一个布局文件,而不是某个具体的View

2.6池(PooL

对象池:

对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。

并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

线程池:

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

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

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

通常可以建立如下4种:

/**每次只执行一个任务的线程池*/ ExecutorServicesingleTaskExecutor =  Executors.newSingleThreadExecutor(); /**每次执行限定个数个任务的线程池*/ ExecutorServicelimitedTaskExecutor = Executors.newFixedThreadPool(3); /**所有任务都一次性开始的线程池*/ ExecutorServiceallTaskExecutor = Executors.newCachedThreadPool(); /**创建一个可在指定时间里执行任务的线程池,亦可重复执行*/ ExecutorServicescheduledTaskExecutor = Executors.newScheduledThreadPool(3);

三、Android内存监测方法

3.1 adb

adb rootadb shell procrank | grep"com...."【包名】查看概要内存使用情况

或者

adb shell dumpsys meminfo com.*【包名】查看较详细内存使用情况

我们比较关注的几项:
Pss列的TOTALAPP使用的总内存,也就是在设置中正在运行里面看到的进程使用内存。
Pss
列的Dalvik表示APP使用的堆内存。
Pss
列的Otherdev表示其它设备(如显卡)使用的内存,4.2系统上,开启硬件加速后,这个值会变得很大。

3.2 DDMS


3.3 Memory Analyzer(MAT)

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

如下图:

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

3.4 Native内存泄露检测

调不尽的内存泄漏,用不完的Valgrind

详见:http://blog.csdn.net/yangzhiloveyou/article/details/7935078

 

更多相关文章

  1. OKHttp3的简单使用(转)
  2. Android(安卓)Dalvikvm 内存管理理解
  3. Android(安卓)操作系统的内存回收机制(转载)
  4. Android(安卓)面试经验 - 类的加载机制
  5. android binder 进程间通信机制4-Service Manager
  6. Android解决加载大图片时内存溢出的问题
  7. davlik虚拟机内存管理之一——内存分配
  8. Android(安卓)游戏框架 libgdx 学习笔记 - 简介
  9. Android中的postDelayed的用法

随机推荐

  1. 【转】Android 文件系统的权限设置
  2. Android音效SoundPool问题:soundpool 1 no
  3. Android模拟器学framework和driver之传感
  4. android基础之EditText
  5. Android模拟器 通过本地电脑上网
  6. 【Android笔记】Activity涉及界面全屏的
  7. ?attr , ?android:attr 解析
  8. Android.9图片讲解(二)
  9. Activity学习(二):Activity的启动模式(转载)
  10. Android(安卓)下的 makefile 组织结构