Andorid内存优化(一)

前言

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)所导致的内存问题会更加隐晦复杂,具体会在下一节详细阐述。

您的支持是对我最大的鼓励!