07.Android之多媒体问题

1,205

目录介绍

  • 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万字],将会陆续发表到网上,转载请注明出处,谢谢!
  • 链接地址:github.com/yangchong21…
  • 如果觉得好,可以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:github.com/yangchong21…
        • 说出我的结论:上面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方法来释放那一部分内存。
  • 查看源码如下所示
    • image
    • 翻译这段解释:释放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.关于博客汇总链接

02.关于我的博客