android 内存优化哪里,Android内存优化总结【整理】

【前段时间接到任务着手进行app的内存优化,从各种各样的渠道搜索相关资料,最后汇总整理如下。】

一、Android 内存管理

1.1 Dalvik

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

Dalvik虚拟机的内存大体上可以分为 JavaObject Heap、Bitmap Memory和Native Heap三种。

Java Object Heap:用于分配对象

Bitmap Memory:用来处理图像,≥Android 3.0, 归到Object Heap中

Native Heap: malloc分配,受系统限制

1.2 查看最大内存限制

ActivityManager mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

int memoryClass = am. getMemoryClass();              //96MB [Mi2S,Android 4.1]

可以通过在AndroidManifest.xml文件中设置来扩大内存限制:(不过不建议使用此参数,建议节省内存)

%E5%A4%A7%E5%86%85%E5%AD%98.png?version=1&modificationDate=1409194867000&api=v2

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

1.3 垃圾回收(GC)

作用:自动回收不再被引用的Java Object

image2014-8-28%2011%3A9%3A3.png?version=1&modificationDate=1409195087000&api=v2 

image2014-8-28%2011%3A9%3A10.png?version=1&modificationDate=1409195094000&api=v2

1.4 垃圾回收机制

在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.5 OutOfMemory Error

主要原因:

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

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

二、内存优化

Coding

Bitmap

ListView

SoftReference & WeakReference

UI

2.1 Coding

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

如SparseArray,SparseBooleanArray,LongSparseArray,代替HashMap

前提:Key为Integer类型

原因:

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

每个元素多占用8byte内存(多了next和hash两个成员变量)。AutoBox【int转Integer,导致产生另一个对象】也会额外加4byte。Entry对象本身至少16byte。

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

image2014-8-28%2011%3A29%3A12.png?version=1&modificationDate=1409196297000&api=v2

HashMap hashMap = new HashMap();

替换为

SparseArray sparseArray = new SparseArray();、

Tip 2:使用IntentService替代Service。

IntentService优势:

·新开线程;

·顺序处理Intent;

·执行完自动退出。

说明:

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

IntentService继承自Service,所以它也是一个服务。

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

IntentService与Service区别:

1,Service默认在UI线程执行;而IntentService的onHandleIntent方法在后台执行。

2,Service在start后,如果没有手动stop会一直存在;而IntentService在执行完后自动退出。

Tip 3:尽量避免使用Enum。

枚举相对于静态常量来说,需要两倍甚至更多的内存。如下是Enum的类型定义:

image2014-8-28%2011%3A44%3A49.png?version=1&modificationDate=1409197236000&api=v2

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

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

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

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

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

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

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

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

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

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

image2014-8-28%2016%3A7%3A15.png?version=1&modificationDate=1409212994000&api=v2

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

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

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

应该尽量使用Application Context。

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

看使用的周期是否在activity周期内,如果超出,必须用application;常见的情景包括:AsyncTask,Thread,第三方库初始化等等。

还有些情景,只能用activity:比如,对话框,各种View,需要startActivity的等。总之,尽可能使用Application。

上面由于静态变量导致的内存泄露问题,可以修改如下:

image2014-8-28%2016%3A8%3A12.png?version=1&modificationDate=1409213051000&api=v2

2.2 Bitmap

Tip:捕获异常

image2014-8-28%2016%3A12%3A12.png?version=1&modificationDate=1409213291000&api=v2

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

OutOfMemoryError是一种Error,而不是Exception。

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

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

此方法无效!因为:

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

在Resource.java类中有LongSparseArray > mDrawableCache

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

Tip:压缩图片

BitmapFactory.Options options = 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。通过这两个值,就可以知道图片是否过大了。

image2014-8-28%2016%3A14%3A34.png?version=1&modificationDate=1409213434000&api=v2

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

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

image2014-8-28%2016%3A9%3A58.png?version=1&modificationDate=1409213157000&api=v2

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

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

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

作用:内存重用

定义和存储:

Set>

mReusableBitmaps;//声明

private LruCache

BitmapDrawable> 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

BitmapDrawable>(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中删除的对象

}

}

}

....

}

限制:

beforeAndroid 4.4 (API level 19):宽度及高度必须完全相等

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

2.3 ListView

Tip:从ContentView获取缓存的view

image2014-8-28%2016%3A17%3A26.png?version=1&modificationDate=1409213606000&api=v2

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

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

image2014-8-28%2016%3A35%3A48.png?version=1&modificationDate=1409214709000&api=v2

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

2.4 SoftReference & WeakReference

软引用

只有当内存空间不足了,才会回收这些对象的内存。

软引用可用来实现内存敏感的高速缓存。

Map> imageCache = new HashMap>();

弱引用

被垃圾回收器扫描到后即被回收。

Map> cacheMap = new HashMap>();

只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有 弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对 象。

案例:异步加载网络图片

image2014-8-28%2016%3A44%3A20.png?version=1&modificationDate=1409215222000&api=v2

2.5 UI

Tip 1:利用系统资源

说明:

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

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

Tip 2:通用模块抽离

背景

头部标题栏

底部导航栏

ListView

Tip 3:ViewStub

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

定义:

image2014-8-28%2016%3A47%3A30.png?version=1&modificationDate=1409215412000&api=v2

使用:

image2014-8-28%2016%3A47%3A39.png?version=1&modificationDate=1409215421000&api=v2

三、Android 内存监测方法

3.1 adb

adb root

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

或者

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

我们比较关注的几项:

Pss列的TOTAL是APP使用的总内存,也就是在设置中正在运行里面看到的进程使用内存。

Pss列的Dalvik表示APP使用的堆内存。

Pss列的Other dev表示其它设备(如显卡)使用的内存,4.2系统上,开启硬件加速后,这个值会变得很大。

3.2 DDMS

image2014-8-28%2016%3A48%3A43.png?version=1&modificationDate=1409215486000&api=v2

3.3 Memory Analyzer(MAT)

image2014-8-28%2016%3A49%3A5.png?version=1&modificationDate=1409215507000&api=v2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值