参考链接:https://juejin.cn/post/6844903919479422984
在日常开发的APP,大部分时候需要想用户展示图片信息,图片最终对应Android中的Bitmap对象。而对于APP端来说Bitmap又是一个比较麻烦的问题,主要表现在Bitmap是非常占用内存的对象,处理不当将导致APP运行卡顿甚至出现OOM
image.png
Android为每一个进程设置Dalvik Heap Size阈值,这个阈值在不一样的设备上会由于RAM大小不一样而各有差别。若是APP想要分配的内存超过这个阈值,就会发生OOM。
Android 5.x之前,Bitmap分配在Native heap中,而在,5.x-7.1,Bitmap分配在Dalvik或ART的Java heap中,Android 8.x之后,Bitmap分配在Native heap中
系统 native heap的增加并不受dalvik vm heapsize的限制,只要RAM有剩余空间,程序员能够一直在native heap上申请空间,固然若是 RAM快耗尽,memory killer会杀进程释放RAM。使用一些软件时,有时候会闪退,多是软件在native层申请了比较多的内存致使的。好比,UC web在浏览内容比较多的网页时闪退,缘由就是其native heap增加到比较大的值,占用了大量的RAM,被memory killer杀掉了。
Bitmap内存计算
从本地加载或者从网络加载可以用下面的公式计算:
图片的长度 * 图片的宽度 * 一个像素点占用的字节数
从资源文件夹加载:
Bitmap内存占用 ≈ 像素数据总大小 = 图片宽 × 图片高× (当前设备密度dpi/图片所在文件夹对应的密度dpi)^2 × 每个像素的字节大小
1、同一张图片放在不同的资源目录下,其分辨率会有变化。
2、Bitmap的分辨率越高,其解析后的宽高越小,甚至小于原有的图片(及缩放),从而内存也响应的减少。
3、图片不放置任何资源目录时,其使用默认分辨率mdpi:160。
4、资源目录分辨率和屏幕分辨率一致时,图片尺寸不会缩放。
优化思路
编码
android系统提供了四种编码格式
编码格式
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。
ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度。
ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节。
ARGB_8888 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节。
RGB_565 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节。
可以通过改变图片格式,来改变每个像素占用字节数,来改变占用的内存
注意:由于ARGB_4444的画质惨不忍睹,一般假如对图片没有透明度要求的话,可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。
采样
bitmap的占用内存是以bitmap的宽高和每个像素占用的字节数决定的,按公式我们可以缩减bitmap的宽高来达到压缩图片占用内存的目的
BitmapFactory.Options options = new BitmapFactory.Options();
//不获取图片,不加载到内存中,只返回图片属性
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(photoPath, options);
//图片的宽高
int outHeight = options.outHeight;
int outWidth = options.outWidth;
Log.d("mmm", "图片宽=" + outWidth + "图片高=" + outHeight);
//计算采样率
int i = utils.computeSampleSize(options, -1, 1000 * 1000);
//设置采样率,不能小于1 假如是2 则宽为之前的1/2,高为之前的1/2,一共缩小1/4 一次类推
options.inSampleSize = i;
Log.d("mmm", "采样率为=" + i);
//图片格式压缩
//options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);
float bitmapsize = getBitmapsize(bitmap);
Log.d("mmm","压缩后:图片占内存大小" + bitmapsize + "MB / 宽度=" + bitmap.getWidth() + "高度=" + bitmap.getHeight());
缓存
内存缓存(LruCache)、磁盘缓存(DiskLruCache)
当我们首次从网络上或者USB读取图片,会对图片进行相应的压缩处理。在处理过后不加入缓存,下一次请求图片还是直接从网络上或者USB中直接读取,不仅消耗了用户的流量还重复对图片进行压缩处理,占用多余内存的同时加载图片也很缓慢。
目前的策略是内存缓存和存储设备缓存。当加载一张图片时,首先会从内存中去读取,如果没有就接着在存储设备中读,最后才直接从网络或者USB中读取
LruCache
LRU是用于实现内存缓存的一种常见算法,LRU也叫做最近最少使用算法,通俗来讲就是当缓存满了的时候,就会优先的去淘汰最近最少使用的缓存对象
private LruCache<Integer,Bitmap> mCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.初始化LruCache.
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mCache = new LruCache<Integer,Bitmap>(cacheSize){
@Override
protected int sizeOf(Integer key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
//2.从Cache中获取数据
public Bitmap getDataFromCache(int key) {
if (mCache.size() != 0) {
return mCache.get(key);
}
return null;
}
//3.将数据存储到Cache中
public void putDataToCache(int key, Bitmap bitmap) {
if (getDataFromCache(key) == null) {
mCache.put(key,bitmap);
}
}
DiskLruCache
磁盘缓存所使用的算法为DiskLruCache,它的使用比内存缓存要复杂一点,但是还是离不开上面的3步,初始化,查找和添加。同样的,
如何加载高清图
要求我们既不能压缩图片,又不能发生oom怎么办,这种情况我们需要加载图片的一部分区域来显示,通过BitmapRegionDecoder这个类,可以加载图片的一部分区域。
如何绕过dalvikvm heap size的限制 ?
- 建立子进程,,内存分配按进程来。再使用进程通信,就能够把一些对象分配到新进程的heap上了,从而达到一个应用程序使用更多的内存的目的,固然,建立子进程会增长系统开销,并且并非全部应用程序都适合这样作,视需求而定。
建立子进程的方法:使用android:process标签 - 按不一样的系统版本,使用 jni 在native heap上申请空间
加载帧动画怎样避免OOM
原理
帧动画是按照一定的顺序播放一系列图片,从而产生动画。和我们看的动漫原理是一样的,本质上是对图片快速的翻页产生动的效果。系统 从xml中读取到图片id列表后就去硬盘中找这些图片资源,将图片全部读出来后按顺序设置给ImageView,利用视觉暂留效果实现了动画。
优化方案
一次拿出这么多图片,而系统都是以Bitmap位图形式读取的;而动画的播放是按顺序来的,大量Bitmap排好队等待播放然后释放,既然这么多Bitmap,一次却只能展示一个,纯属浪费资源,那么可以采取逐帧解析,及时回收。
参考链接:https://blog.csdn.net/u014702999/article/details/50544455
网友评论