乐趣区

07.Android之多媒体问题

目录介绍

7.0.0.1 加载 bitmap 图片的时候需要注意什么?为何 bitmap 容易造成 OOM?如何计算 Bitmap 占用内存?
7.0.0.2 如何理解 recycle 释放内存问题?图片加载到内存其实有两部分数据,这是为何?
7.0.0.3 如何在不压缩图片的情况下加载高清大图?加载图的机制是什么,为何不会内存泄漏?
7.0.0.7 LRU 算法的原理?核心思想是什么?如果缓存满了的话,什么方法来管理移除最近最少使用的 item 和添加新的 item?

好消息

博客笔记大汇总【15 年 10 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 500 篇[近 100 万字],将会陆续发表到网上,转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有的笔记将会更新到 GitHub 上,同时保持更新,欢迎同行提出或者 push 不同的看法或者笔记!

7.0.0.1 加载 bitmap 图片的时候需要注意什么?为何 bitmap 容易造成 OOM?如何计算 Bitmap 占用内存?

注意问题

直接加载大容量的高清 Bitmap 很容易出现显示不完整、内存溢出 OOM 的问题,所以最好按一定的采样率将图片缩小后再加载进来
为减少流量消耗,可对图片采用内存缓存策略,又为了避免图片占用过多内存导致内存溢出,最好以软引用方式持有图片
如果还需要网上下载图片,注意要开子线程去做下载的耗时操作技术博客大总结

为何 bitmap 容易造成 OOM

如何计算 Bitmap 占用内存

1.1 如何计算占用内存

如果图片要显示下 Android 设备上,ImageView 最终是要加载 Bitmap 对象的,就要考虑单个 Bitmap 对象的内存占用了,如何计算一张图片的加载到内存的占用呢?其实就是所有像素的内存占用总和:
bitmap 内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
起决定因素就是最后那个参数了,Bitmap’ 常见有 2 种编码方式:ARGB_8888 和 RGB_565,ARGB_8888 每个像素点 4 个 byte,RGB_565 是 2 个 byte,一般都采用 ARGB_8888 这种。那么常见的 1080*1920 的图片内存占用就是:1920 x 1080 x 4 = 7.9M

1.2 上面方法计算内存对吗

我看到好多博客都是这样计算的,但是这样算对吗?有没有哥们试验过这种方法正确性?我觉得看博客要对博主表示怀疑,论证别人写的是否正确。更多详细可以看我的 GitHub:https://github.com/yangchong211

说出我的结论:上面 1.1 这种说法也对,但是不全对,没有说明场景,同时也忽略了一个影响项:Density。接下来看看源代码。
inDensity 默认为图片所在文件夹对应的密度;inTargetDensity 为当前系统密度。
加载一张本地资源图片,那么它占用的内存 = width height nTargetDensity/inDensity nTargetDensity/inDensity 一个像素所占的内存。

@Nullable
public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}

if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}

if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}

return decodeStream(is, pad, opts);
}

正确说法,这个注意呢?计算公式如下所示

对资源文件:width height nTargetDensity/inDensity nTargetDensity/inDensity 一个像素所占的内存;
别的:width height 一个像素所占的内存;

1.3 一个像素占用多大内存技术博客大总结

Bitmap.Config 用来描述图片的像素是怎么被存储的?

ARGB_8888: 每个像素 4 字节. 共 32 位,默认设置。
Alpha_8: 只保存透明度,共 8 位,1 字节。
ARGB_4444: 共 16 位,2 字节。
RGB_565: 共 16 位,2 字节,只存储 RGB 值。

7.0.0.2 如何理解 recycle 释放内存问题?图片加载到内存其实有两部分数据,这是为何?

如何理解 recycle 释放内存问题?
在 Android2.3.3(API 10)及之前的版本中,Bitmap 对象与其像素数据是分开存储的,Bitmap 对象存储在 Dalvik heap 中,而 Bitmap 对象的像素数据则存储在 Native Memory(本地内存)中或者说 Derict Memory(直接内存)中,这使得存储在 Native Memory 中的像素数据的释放是不可预知的,我们可以调用 recycle()方法来对 Native Memory 中的像素数据进行释放,前提是你可以清楚的确定 Bitmap 已不再使用了,如果你调用了 Bitmap 对象 recycle()之后再将 Bitmap 绘制出来,就会出现”Canvas: trying to use a recycled bitmap”错误,而在 Android3.0(API 11)之后,Bitmap 的像素数据和 Bitmap 对象一起存储在 Dalvik heap 中。

图片加载到内存其实有两部分数据,这是为何?技术博客大总结
一个图片加载到内存里,其实是有两部分数据组成,一部分是图片的相关描述信息,另一部分就是最重要的像素信息(这部分是有 byte 数组组成的),android 系统为了提高对图片的处理效率,对于图片的处理都是调用了底层的功能(由 C 语言实现的),也就是说一个图片加载到内存里后是使用两部分的内存区域,简单的说:一部分是 java 可用的内存区,一部分是 c 可用的内存区,这两个内存区域是不能相互直接使用的,这个 bitmap 对象是由 java 分配的,当然不用的时候系统会自动回收了,可是那个对应的 C 可用的内存区域 jvm 是不能直接回收的,这个只能调用底层的功能释放。所以你要调用 recycle 方法来释放那一部分内存。

查看源码如下所示

翻译这段解释:释放 bitmap 内存的时候,它会释放和这个 bitmap 有关的 native 内存,同时它会清理有关数据对象的引用,但是这里处理数据对象的引用,并不是立即清理数据(他并不是调用玩 recycle()方法,就直接清理这个内存,他只是给垃圾回收机制发送一个指令,让它在 bitmap 没有对象引用的时候,来进行垃圾回收)。当调用 recycle()方法之后,这个 bitmap 就会被表明为“死亡状态”。这个时候你在调用 bitmap 其他相关的方法,例如果 get 像素 () 或 set 像素 () 就会抛出一个异常。同时这个操作是不可逆的,所以一定百分之百确定这个 bitmap 在以后的场景下,不会被你的程序在使用到,再去调用 recycle()方法。所以谷歌源码中建议我们,可以不用去主动调用 recycle()方法,因为在没有引用的情况下,我们的垃圾回收机制会主动的清理内存。
通过看源码,我们会发现,这个方法首先将这个 Bitmap 的引用置为 null,然后调用了 nativeRecycle(mNativeBitMap)方法,这个方法很明显是个 JNI 调用,会调用底层的 c 或者 c ++ 代码就可以做到对该内存的立即回收,而不需要等待那不确定啥时候会执行的 GC 来回收了。

7.0.0.3 如何在不压缩图片的情况下加载高清大图?加载图的机制是什么,为何不会内存泄漏?

如何在不压缩图片的情况下加载高清大图?

使用 BitmapRegionDecoder,主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。

加载图的机制是什么,为何不会内存泄漏?

自定义可拖动的显示高清大图的 View 技术博客大总结

提供一个设置图片的入口,setInputStream 里面去获得图片的真实的宽度和高度,以及初始化我们的 mDecoder
重写 onTouchEvent,在里面根据用户移动的手势,去更新显示区域的参数。在 onMeasure 里面为我们的显示区域的 rect 赋值,大小为 view 的尺寸
每次更新区域参数后,调用 invalidate,onDraw 里面去 regionDecoder.decodeRegion 拿到 bitmap,然后 draw。

7.0.0.7 LRU 算法的原理?核心思想是什么,谈谈你的思路?

为减少流量消耗,可采用缓存策略。常用的缓存算法是 LRU(Least Recently Used):

核心思想:当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。主要是两种方式:

LruCache(内存缓存):LruCache 类是一个线程安全的泛型类:内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象,并提供 get 和 put 方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
DiskLruCache(磁盘缓存):通过将缓存对象写入文件系统从而实现缓存效果

大概过程如下?技术博客大总结
LruCache 是 android 提供的一个缓存工具类,其算法是最近最少使用算法。它把最近使用的对象用“强引用”存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除。

如果缓存满了的话,什么方法来管理移除最近最少使用的 item 和添加新的 item?

trimToSize()方法,删除最年长的条目,直到剩余条目的总数达到或低于请求的大小
public void trimToSize(int maxSize) {
while(true) {
Object key;
Object value;
synchronized(this) {
if (this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(this.getClass().getName() + “.sizeOf() is reporting inconsistent results!”);
}

if (this.size <= maxSize || this.map.isEmpty()) {
return;
}

Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
this.map.remove(key);
// 计算现在缓存的大小,然后减掉多余的,内部调用的是 sizeOf()方法
this.size -= this.safeSizeOf(key, value);
++this.evictionCount;
}

// 如果你想在在我们的缓存中实现二级缓存,可以实现此方法,源码中是空方法。
this.entryRemoved(true, key, value, (Object)null);
}
}

关于其他内容介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

退出移动版