Android 内存优化

前言

Random-access memory (RAM) is a valuable resource in any software development environment, but it's even more valuable on a mobile operating system where physical memory is often constrained. Although both the Android Runtime (ART) and Dalvik virtual machine perform routine garbage collection, this does not mean you can ignore when and where your app allocates and releases memory. You still need to avoid introducing memory leaks, usually caused by holding onto object references in static member variables, and release any Reference objects at the appropriate time as defined by lifecycle callbacks.

摘取自developer.android.google.cn

Andorid中的内存使用问题以及解决方案

在Android开发中内存优化是开发者需要永不停息去做的事情,随着Android版本的变化和硬件的升级,系统对内存的大小限制也都有了变化,内存方面的问题主要有两大问题:内存溢出,内存泄露。内存溢出是内存问题的最终因果。

内存溢出

内存泄露可以引发很多的问题:
  1. 程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)
  2. 莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)
  3. 直接崩溃(OutOfMemoryError)
发生内存溢出的条件:
  • Android 2.x系统中:GC LOG中的dalvik allocated + external allocated + 新分配的大小>= getMemoryClass()值的时候就会发生OOM。 例如,假设有这么一段Dalvik输出的GC LOG:GC_FOR_MALLOC free 2K, 13% free 32586K/37455K, external 8989K/10356K, paused 20ms,那么32586+8989+(新分配23975)=65550>64M时,就会发生OOM。
  • Android 4.0以上的系统中: Android 4.x的系统废除了external的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= getMemoryClass()的时候就会发生OOM。

如何优化内存避免内存溢出?

1.申请大内存。
     android:largeHeap="true"

上面这句话可以写在Android Manifest.xml中 请求扩大内存限制。这项选择是提供给那种对内存特别需要的APP的,比如一些需要展示或编辑大量图片视频的应用。但是需要谨慎添加这句话,第一因为这种办法本来就是治标不治本的,第二,如前言中Google官方文档所说,移动端因设备尺寸的限制本身内存的容量就是非常有限的,占用了一些就会少一些。第三,如果你应用占用内存过大,估计当你的应用失去焦点时,java虚拟机或者尤其国内横行的第三方加速软件也是容不下你。

2.合理的使用、压缩、缓存与复用图片。

在Android 系统中图片是使用内存的大户,建议使用较为成熟的第三方图片加载框架,比如Glide、Freso,Picasso(依次为推荐顺序,下面也是以Glide为例),同时我们使用图片时需要注意以下几点:

  • 启用RBG_565色彩模式。色彩模式Android中有四种,分别是:

ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存
RGB_565:每个像素占用2byte内存

Android默认的色彩模式为ARGB_8888,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。通常我们只需要使用RGB_565格式就好(Glide默认模式)。

  • 根据已知尺寸加载图片缩略图

      1 BitmapFactory.Options options = new BitmapFactory.Options();
      2 options.inSampleSize = 2;
      3 Bitmap img = BitmapFactory.decodeFile("/sdcard/1.png", options);
    

该段代码便是读取本地sd卡根目录1.png的缩略图,长度、宽度都只有原图片的1/2。图片大小削减,占用的内存自然也变小了。当然代价就是图片质量变差了。加载网络图片时Glide的逻辑是可以指定图片尺寸按需使用,缓存的时候默认也是按需缓存的,所以有些时候你会发现本来这张图片已经在其他地方加载过但是换了个位置需要重新下载,这个时候可以针对性的设置缓存策略diskCacheStrategy(DiskCacheStrategy.ALL)。

  • 及时的回收内存,使用Glide时正确的赋予应有的生命周期。

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

  • 当使用Glide加载一张图片时可以给指定特定的生命周期.

      Glide.with(NewsDetailActivity.this).load(url)into(ImageView);
    

Glide 的 with() 方法不光接受 Context,还接受 Activity 和 Fragment。此外,with() 方法还能自动地从你放入的各种东西里面提取出 Context,供它自己使用。将Activity/Fragment作为with()参数的好处是:图片加载会和Activity/Fragment的生命周期保持一致,比如 Paused状态在暂停加载,在Resumed的时候又自动重新加载。所以我建议传参的时候传递Activity 和 Fragment给Glide,而不是Context。

  • 内存重用

充分使用BitmapFactory.Option inBitmap属性,利用这种特性即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大 小。在3.0-4.4系统中inBitmap要求新申请的Bitmap必须和可复用的Bitmap对象的大小完全相同,而在4.4系统以后要求有所放 松,只要新申请Bitmap对象小于或等于可复用的Bitmap对象即可复用

关于图片内存优化的内容 Google 官方文档上也有类似讲解。Managing Bitmap Memory

3.响应释放内存的事件

Android App 应该监听系统的广播信号且根据用户不同行为释放不同的资源,系统会在有内存压力的时候,发出广播告诉应用,让它们适当调整内存使用情况。以下引用自Google 官方文档

You can use the ComponentCallbacks2 API to listen for these signals and then adjust your memory usage in response to app lifecycle or device events. The onTrimMemory() method allows your app to listen for memory related events when the app runs in the foreground (is visible) and when it runs in the background.
To listen for these events, implement the onTrimMemory() callback in your Activity classes, as shown in the following code snippet.

import android.content.ComponentCallbacks2;
// Other import statements ...

public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {

// Other activity code ...

/**
 * Release memory when the UI becomes hidden or when system resources become low.
 * @param level the memory-related event that was raised.
 */
public void onTrimMemory(int level) {

    // Determine which lifecycle or system event was raised.
    switch (level) {

        case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

            /*
               Release any UI objects that currently hold memory.

               The user interface has moved to the background.
            */

            break;

        case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
        case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
        case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

            /*
               Release any memory that your app doesn't need to run.

               The device is running low on memory while the app is running.
               The event raised indicates the severity of the memory-related event.
               If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
               begin killing background processes.
            */

            break;

        case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
        case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
        case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

            /*
               Release as much memory as the process can.

               The app is on the LRU list and the system is running low on memory.
               The event raised indicates where the app sits within the LRU list.
               If the event is TRIM_MEMORY_COMPLETE, the process will be one of
               the first to be terminated.
            */

            break;

        default:
            /*
              Release any non-critical data structures.

              The app received an unrecognized memory level value
              from the system. Treat this as a generic low-memory message.
            */
            break;
    }
}
}

The onTrimMemory() callback was added in Android 4.0 (API level 14). For earlier versions, you can use the onLowMemory() callback as a fallback for older versions, which is roughly equivalent to the TRIM_MEMORY_COMPLETE event.

4.混淆你的代码

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

5.内存抖动与避免

有些代码并不造成内存泄露,但是资源没有得到重用,例如for循环分配占内存的对象导致垃圾回收机制频繁运行(短时间内产生大量对象,需要大量内存,而且还是频繁抖动,就可能会需要回收内存以用于产生对象,垃圾回收机制就自然会频繁运行了),频繁的申请内存和销毁内存,消耗CPU资源的同时,也引起内存忽高忽低,这就是内存抖动,反应给使用者的表现就是UI卡顿。

此种问题大多数出现在循环或者重复调用的回调里,所以我们应该避免这些问题,尽量在for循环体外创捷对象或者使用对象池等方法,但也需要注意对象池的内存管理和释放。

6.TinyPNG 智能有损压缩资源文件里的图片(同样适合ios)

TinyPNG采用了智能有损压缩技术,以减少文件大小的PNG文件。通过选择性地降低在图像中的颜色的数量,需要较少字节来存储该数据。其效果是几乎看不见,但它使文件的大小非常大的差别!TinyPNG

7.其他优化点
  • 不使用枚举型数据,Android官方培训课程提到过

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
枚举通常情况下所占用的内存是静态常量的两倍甚至更多,有兴趣的同学可以建一个工程先打一个包看看dex有多大,然后分别加上这两段代码,再对比一下dex的大小。

  • Try catch 某些内存操作情况 OutOfMemoryError 是可以被catch的。
  • 使用软引用,软引用只有当内存空间不足了,才会回收这些对象的内存。弱引用,被垃圾回收器扫描到后即被回收。
  • 使用DDMS(Dalvik Debug Monitor Server)查看堆内存的分配情况,针对优化。
  • 使用square 公司出的内存监测工具 LeakCanary 监听内存泄露。

内存泄露

内存泄露漏 (memory leak)所导致的内存问题会更加隐晦复杂,具体会在下一节详细阐述。

技术博客 Wells'Note

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,121评论 25 707
  • 本文转载来源 http://www.csdn.net/article/2015-09-18/2825737/1 (...
    yoosir阅读 1,077评论 0 5
  • 如何避免OOM 一、减小对象的内存占用 1、使用更加轻量的数据结构 例如,我们可以考虑使用ArrayMap/Spa...
    吕侯爷阅读 731评论 0 5
  • 前言 手机极大的方便了和丰富了我们的生活,随着乔布斯改变世界的iOS操作系统的发展和android系统的扶摇直上,...
    平凡小天地阅读 1,557评论 1 13
  • 感恩早上起床,老公已经刷碗熬粥,因为他知道二宝昨晚没吃好,一直在闹,我也没休息好。 感恩我又感冒了,不停打喷嚏流鼻...
    米朵天天阅读 182评论 0 0