Android客户端中Bitmap的下载过程和缓存机制

加载流程:
if(内存命中){
     从内存中读取
}else{
     create AsyncTasks,task中的多个Runnable是通过堆栈先进后出的方式来调度,而非队列式的先进先出,目的是最先加载用户最近划到或打开的图片。    
}
 
AsyncTask:
//do in background——该后台进程在用户scroll列表的时候会暂停,从而减小了列表划动时cpu的overhead,此方法也被ImageLoader和facebook的官方app所使用。
if(磁盘缓存命中){
     从缓存中读取    
}else{
     从网络下载
  成功后,存入磁盘缓存并且存入内存
}
 
内存图片管理
LruCache,容量为1/10运行时内存。以nexus 4为例,内存2G,jvm为app分配的可用内存为512MB,那么内存中的图片LruCache大小为51.2MB。
当达到容量后,不要直接recycle bitmap,而是将bitmap加到一个WeakReference的Map中;否则在小内存手机上,很可能会recycle掉一些还被引用着的bitmap。
 
缓存图片管理
ChocolateCache,策略类似于LruCache,但是对IO读写速度和命中率都做了优化(鸣谢 伯奎)
 
下载和解析图片的注意事项
下载过程优化:
之前采用ImageLoader时,发现它先将图片下载到文件中,然后再从文件中decode图片并显示,增加了一步IO操作,所以现在直接从网络字节流decode图片并返回。
 
黑图问题:
直接从网络的InputStream解析Bitmap时,会出现黑图(图片中有横向的黑色矩形),所以不直接decode从网络得到的InputStream,而是把stream转换为一个固定长度的byte array,再进行解析,黑图问题解决。
                            // 如果不根据这个固定的length来生成byte[],而是直接decode
                            // inputstream ,会造成黑图
                            imgData = new byte[length];
 
                            byte[] temp = new byte[512];
                            int readLen = 0;
                            int destPos = 0;
 
                            while ((readLen = mInputStream.read(temp)) > 0) {
                                System.arraycopy(temp, 0, imgData, destPos, readLen);
                                destPos += readLen;
                           }
 
机型和网络兼容性问题:
三星note3在CDMA网络下得到的是GZIPInputStream(length为-1,无法采用以上方法生成byte array),其他机型和场景都是FixedLengthInputStream(length为正常大小)。所以当length=-1时,采用最基本的InputStream->OutputStream->ByteArray的方式生成图像的字节数组
 
分辨率适配问题
解析图片应采用BitmapFactory.decodeStream而不是BitmapFactory.decodeByteArray,因为后者在从source density到target density转换时,不会自动缩放。
以表情为例,某250x250的表情图片,默认的density是320,但是LG的G2手机density为480,所以此图片应按照480/320=1.5的比例放大并显示到G2手机上。但是decodeByteArray之后得到的图片长宽仍然是250x250,而如果使用decodeStream则会得到长宽均为250*1.5=375的图片,从而实现了最佳的显示效果。根本原因可以从BitmapFactory.java的源码中得到解释(第一个方法带有缩放功能,第二个方法则没有):
 
    public static Bitmap decodeStream (InputStream is, Rect outPadding, Options opts) {
       ... ...
            if (opts == null || (opts. inScaled && opts. inBitmap == null)) {
                float scale = 1.0f;
                int targetDensity = 0;
                if (opts != null) {
                    final int density = opts. inDensity;
                    targetDensity = opts. inTargetDensity;
                    if (density != 0 && targetDensity != 0) {
                        scale = targetDensity / ( float) density;//请注意这里有计算缩放比例,而decodeByteArray未进行此项操作
                    }
                }
 
                bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
                if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
 
                finish = false;
            } else {
                bm = nativeDecodeAsset(asset, outPadding, opts);
            }
        }
   ... ...
 
    public static Bitmap decodeByteArray (byte [] data, int offset, int length, Options opts) {
        if ((offset | length) < 0 || data. length < offset + length) {
            throw new ArrayIndexOutOfBoundsException();
        }
        Bitmap bm = nativeDecodeByteArray(data, offset, length, opts);
 
        if (bm == null && opts != null && opts. inBitmap != null) {
            throw new IllegalArgumentException( "Problem decoding into existing bitmap");
        }
        return bm;
    }
 
 
场景策略
网络获取的图片都是服务端处理过的图片,按原尺寸处理没有任何问题,但是本地图片或拍照图片,经常是高清大图,极易Out of memory,所以需要根据具体情况设置采样率,减小decode bitmap和write to cache时的内存占用(鸣谢 风念)
 
待优化点
关于BitmapFactory.Options.inBitmap的trade-off:
google推荐使用BitmapFactory.Options.inBitmap属性,将新的图片decode并保存到旧图片的内存(reuse the memory of old bitmaps),从而“removing both memory allocation and de-allocation”,减少GC操作,提升性能。这样做也有一个缺点,如此一来需要把从LruCache中evict出的图片放入软引用(方便用来reuse内存),而非很快就会被回收掉的弱引用,结果会造成不再使用的旧图片大量留驻内存,直到内存紧张时才被回收,虽然会提升一定性能,但是应用会“看上去”比较耗内存。
 
上一篇:073 HBASE的读写以及client API


下一篇:[Unity3D]自己动手重制坦克舰队ArmadaTank