[Android] 内存优化

来源:互联网 发布:西游记 女儿情 知乎 编辑:程序博客网 时间:2024/05/22 17:43

Android 内存管理

Dalvik

Dalvik 虚拟机是 Android 程序的虚拟机,是 Android 中 Java 程序的运行基础。其指令集基于寄存器架构,执行其特有的文件格式——dex 字节码来完成对象生命周期管理、堆栈管理、线程管理、安全异常管理、垃圾回收等重要功能。
Dalvik 虚拟机的内存大体上可以分为:
Java Object Heap:用于分配对象
Bitmap Memory:用来处理图像,≥Android 3.0, 归到 Object Heap 中
Native Heap: malloc 分配,受系统限制

查看最大内存限制

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

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

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

垃圾回收(GC)

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

垃圾回收机制

在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。

OutOfMemory Error

主要原因:
1.内存泄露:程序中存在对无用对象的引用,导致GC无法回收。
2.内存超限:保存了多个耗用内存过大的对象(如Bitmap)。

内存优化

Coding
Bitmap
ListView
SoftReference & WeakReference
UI

Coding

使用优化过的数据容器。

如 SparseArray,SparseBooleanArray,LongSparseArray,代替HashMap
前提:Key 为 Integer 类型
原因:
HashMap 是内存低效的,因为每一个 mapping 都需要单独的 entry。
每个元素多占用 8 byte 内存(多了 next 和 hash 两个成员变量)。AutoBox【int 转 Integer,导致产生另一个对象】也会额外加 4 byte。Entry 对象本身至少 16 byte。
SparseArray 可以避免 AutoBox,查找方法为二分查找,效率比 HashMap 低一些,但百量级以内性能差距不大。

HashMap<Integer, E> hashMap = new HashMap<Integer, E>();

替换为

SparseArray<E> sparseArray = new SparseArray<E>();、

使用 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在执行完后自动退出。

尽量避免使用 Enum。

枚举相对于静态常量来说,需要两倍甚至更多的内存。

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

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

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

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

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

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

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

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

对象不用时最好显式置为 Null 可以减少 GC 开销。
警惕:静态变量引起内存泄露

这段代码中有一个静态的 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。

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

Bitmap

捕获异常

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

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

应用场景:默认头像。有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。
此方法无效!因为:
因为Android自带资源文件缓存机制:
在Resource.java 类中有

LongSparseArray<WeakReference<Drawable.ConstantState>>mDrawableCache

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

压缩图片

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

如果图片像素过大,使用 BitmapFactory 类的方法实例化Bitmap的过程中,就会发生 OutOfMemory 异常。
使用 BitmapFactory.Options 设置 inSampleSize 就可以缩小图片。属性值 inSampleSize 表示缩略图大小为原始图片大小的几分之一。即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4。
如果知道图片的像素过大,就可以对其进行缩小。那么如何才知道图片过大呢?
使用 BitmapFactory.Options 设置 inJustDecodeBound s为true后,再使用 decodeFile() 等方法,并不会真正的分配空间,即解码出来的 Bitmap 为 null,但是可计算出原始图片的宽度和高度,即 options.outWidth 和 options.outHeight。通过这两个值,就可以知道图片是否过大了。

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

及时回收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 方法了的。

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

作用:内存重用
定义和存储:

Set<SoftReference<Bitmap>> mReusableBitmaps;        //声明private LruCache<String, 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<SoftReference<Bitmap>>());      //定义}mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {// Notify the removed entry that is no longer being cached.@Overrideprotected 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<Bitmap>(oldValue.getBitmap()));    //存入从LRUCache中删除的对象        }    }}....}

使用方法详见:https://developer.android.com/training/displaying-bitmaps/manage-memory.html#inBitmap
限制:
before Android 4.4 (API level 19):宽度及高度必须完全相等
after:尺寸小于等于既有内存

ListView

从ContentView获取缓存的view

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

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

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

SoftReference & WeakReference

软引用

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

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

Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

弱引用

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

Map<String,WeakReference<Bitmap>> cacheMap = new HashMap<String, WeakReference<Bitmap>>();

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

案例:异步加载网络图片

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

UI

利用系统资源

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

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

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

  3. 系统的Style:
    如android:textAppearance=”?android:attr/textAppearanceMedium“

  4. 系统的颜色定义:如android:background =”@android:color/transparent”

说明:
Android 系统本身有很多的资源,包括各种各样的字符串、图片、动画、样式和布局等等,这些都可以在应用程序中直接使用。这样做的好处很多,既可以减少内存的使用,又可以减少部分工作量,也可以缩减程序安装包的大小。
Android 中没有公开的资源,在 xml 中直接引用会报错。除了去找到对应资源并拷贝到我们自己的应用目录下使用以外,我们还可以将引用“@android”改成“@*android”解决。

通用模块抽离

<include layout=”@layout/navigator_bar”/>
背景
头部标题栏
底部导航栏
ListView

ViewStub

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

使用:

Android 内存监测方法

adb

adb root
adb shell procrank | grep “com….”【包名】查看概要内存使用情况
或者
adb shell dumpsys meminfo com.*【包名】查看较详细内存使用情况

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

DDMS

Memory Analyzer(MAT)

转载自:http://blog.csdn.net/tiantangrenjian/article/details/39182293
感谢作者。

0 0
原创粉丝点击