面试记录第十五节——(bitmap释放、lru、三级缓存、图片压缩)

一、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方法来释放那一部分内存。

  • 源码
 /**
     * Free the native object associated with this bitmap, and clear the
     * reference to the pixel data. This will not free the pixel data synchronously;
     * it simply allows it to be garbage collected if there are no other references.
     * The bitmap is marked as "dead", meaning it will throw an exception if
     * getPixels() or setPixels() is called, and will draw nothing. This operation
     * cannot be reversed, so it should only be called if you are sure there are no
     * further uses for the bitmap. This is an advanced call, and normally need
     * not be called, since the normal GC process will free up this memory when
     * there are no more references to this bitmap.
     */
  • 翻译:

释放bitmap内存的时候,它会释放和这个bitmap有关的native内存,同时它会清理有关数据对象的引用,但是这里处理数据对象的引用,并不是立即清理数据(他并不是调用玩recycle()方法,就直接清理这个内存,他只是给垃圾回收机制发送一个指令,让它在bitmap没有对象引用的时候,来进行垃圾回收)。当调用recycle()方法之后,这个bitmap就会被表明为“死亡状态”。这个时候你在调用bitmap其他相关的方法,例如果get像素()或set像素()就会抛出一个异常。同时这个操作是不可逆的,所以一定百分之百确定这个bitmap在以后的场景下,不会被你的程序在使用到,再去调用recycle()方法。所以谷歌源码中建议我们,可以不用去主动调用recycle()方法,因为在没有引用的情况下,我们的垃圾回收机制会主动的清理内存。

    public void recycle() {
        if (!mRecycled && mNativePtr != 0) {
            if (nativeRecycle(mNativePtr)) {
                // return value indicates whether native pixel object was actually recycled.
                // false indicates that it is still in use at the native level and these
                // objects should not be collected now. They will be collected later when the
                // Bitmap itself is collected.
                mBuffer = null;
                mNinePatchChunk = null;
            }
            mRecycled = true;
        }
    }

通过看源码,我们会发现,这个方法首先将这个Bitmap的引用置为null,然后调用了nativeRecycle(mNativeBitMap)方法,这个方法很明显是个JNI调用,会调用底层的c或者c++代码就可以做到对该内存的立即回收,而不需要等待那不确定啥时候会执行的GC来回收了。

推荐博客:http://blog.csdn.net/xiaanming/article/details/41084843


二、lru是个什么玩意

答:LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法。它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除。其在API12被引进,低版本可以用support包中的类。

  • 源码:如图01,其实很简单,LinkedHashMap就是一个map。
    面试记录第十五节——(bitmap释放、lru、三级缓存、图片压缩)_第1张图片

1、其中用到的数据对象是LinkedHashMap,所以不要把这个类想的多么深不可测,还是数据结构 + 算法。既然用到了这个map,自然就要有添加修改和删除操作了,用到了最近最少使用算法,自然就要用到优先级了。

2、作为缓存,肯定有一个缓存的大小,这个大小是可以设定的(自定义sizeOf())。当你访问了一个item(需要缓存的对象),这个item应该被加入到内存中,然后移动到一个队列的顶部,如此循环后这个队列的顶部应该是最近访问的item了,而队尾部就是很久没有访问的item,这样我们就应该对队尾部的item优先进行回收操作。

3、因为用到了HashMap,那么就有这个数据存储对象的特点(KEY-VALUE),放入这个map的item应该会被强引用,要回收这个对象的时候是让这个key为空,这样就让有向图找不到对应的value,最终被GC。

4、缓存的最大特点是不做重复的劳动,如果你之前已经缓存过这个item了,当你再次想要缓存这个item时,应该会先判断是否已经缓存好了,如果已经缓存,那么就不执行添加的操作。

5、我们应该能通过某个方法来清空缓存,这个缓存在app被退出后就自动清理,不会常驻内存。

6、sizeof()方法。这个方法默认返回的是你缓存的item数目,如果你想要自定义size的大小,直接重写这个方法,返回自定义的值即可。

推荐博客:http://www.cnblogs.com/tianzhijiexian/p/4248677.html


三、问:如果缓存满了的话,什么方法来管理我们队列的移除最进最少使用的item和添加新的item,

答:trimToSize()方法,

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

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

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

//如果你想在在我们的缓存中实现二级缓存,可以实现此方法,源码中是空方法。
            entryRemoved(true, key, value, null);
        }
    }
  • 计算inSampleSize
    面试记录第十五节——(bitmap释放、lru、三级缓存、图片压缩)_第2张图片

四、缩略图问题

答:缩略图是跟我们的inSampleSize是分不开的,它主要是根据我们inSampleSize算出的值,来响应的保存bitmap到内存当中。
面试记录第十五节——(bitmap释放、lru、三级缓存、图片压缩)_第3张图片

  • 问:这里有一个重要的知识点:inJustDecodeBounds,他是什么原理呢?

    答:如果该 值设为true那么将不返回实际的bitmap,也不给其分配内存空间,这样就避免内存溢出了。但是允许我们查询图片的信息这其中就包括图片大小信息

    options.outHeight (图片原始高度)和option.outWidth(图片原始宽度))。Options中有个属性inSampleSize。我们可以充分利用它,实现缩放。

  • 注意

    例如,inSampleSize = = 2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。对于任何值< = 1的同样处置为1。那么相应的方法也就出来了,通过设置 inJustDecodeBounds为true,获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度),然后计算一个inSampleSize(缩放值),然后就可以取图片了,这里要注意的是,inSampleSize 可能小于0,必须做判断。

  • 具体步骤

    第一步:BitmapFactory.Option
    设置 inJustDecodeBounds为true
    第二步:BitmapFactory.decodeFile(path,option)方法
    解码图片路径为一个位图。如果指定的文件名是空的,或者不能解码到一个位图,函数将返回null[空值]。
    获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度)
    第三步:计算缩放比例,也可以不计算,直接给它设定一个值。
    options.inSampleSize = “你的缩放倍数”;
    如果是2就是高度和宽度都是原始的一半。
    第四步:设置options.inJustDecodeBounds = false;
    重新读出图片
    bitmap = BitmapFactory.decodeFile(path, options);

public Bitmap decodeBitmap()  
    {  
        BitmapFactory.Options options = new BitmapFactory.Options();  
        options.inJustDecodeBounds = true;  
        // 通过这个bitmap获取图片的宽和高         
        Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/MTXX/3.jpg", options);  
        if (bitmap == null)  
        {  
            System.out.println("bitmap为空");  
        }  
        float realWidth = options.outWidth;  
        float realHeight = options.outHeight;  
        System.out.println("真实图片高度:" + realHeight + "宽度:" + realWidth);  
        // 计算缩放比         
        int scale = (int) ((realHeight > realWidth ? realHeight : realWidth) / 100);  
        if (scale <= 0)  
        {  
            scale = 1;  
        }  
        options.inSampleSize = scale;  
        options.inJustDecodeBounds = false;  
        // 注意这次要把options.inJustDecodeBounds 设为 false,这次图片是要读取出来的。        
        bitmap = BitmapFactory.decodeFile("/sdcard/MTXX/3.jpg", options);  
        int w = bitmap.getWidth();  
        int h = bitmap.getHeight();  
        System.out.println("缩略图高度:" + h + "宽度:" + w);  
        return bitmap;  
    }  
}  

推荐博客:http://blog.csdn.net/sjf0115/article/details/7366746


五、三级缓存概念

答:网络、本地、内存三个层次。

例如你首次打开加载一张图片,首次肯定只能从网络获取,当请求成功,我们会吧图片保存在本地、内存各一份。之后你再次请求同一个url,我们就不用从网络获取了,我们只需要从本地或者内存获取即可。

网络请求:速度最慢。

内存请求:速度最快,也是首先会选择加载的。

你可能感兴趣的:(android)