您的位置:首页 > 移动开发 > Android开发

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

2014-07-07 20:16 330 查看
加载流程:

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,再进行解析,黑图问题解决。
[align=left] // 如果不根据这个固定的length来生成byte[],而是直接decode[/align]
[align=left] // inputstream ,会造成黑图[/align]
[align=left] imgData = new byte[length];[/align]
[align=left] [/align]
[align=left] byte[] temp = new byte[512];[/align]
[align=left] int readLen = 0;[/align]
[align=left] int destPos = 0;[/align]
[align=left] [/align]
[align=left] while ((readLen = mInputStream.read(temp)) > 0) {[/align]
[align=left] System.arraycopy(temp, 0, imgData, destPos, readLen);[/align]
[align=left] destPos += readLen;[/align]
[align=left] }[/align]

机型和网络兼容性问题:
[align=left]三星note3在CDMA网络下得到的是GZIPInputStream(length为-1,无法采用以上方法生成byte array),其他机型和场景都是FixedLengthInputStream(length为正常大小)。所以当length=-1时,采用最基本的InputStream->OutputStream->ByteArray的方式生成图像的字节数组[/align]
[align=left] [/align]
分辨率适配问题
解析图片应采用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的源码中得到解释(第一个方法带有缩放功能,第二个方法则没有):

[align=left] public static Bitmap decodeStream (InputStream is, Rect outPadding, Options opts) {[/align]
[align=left] ... ...[/align]
[align=left] if (opts == null || (opts. inScaled && opts. inBitmap == null)) {[/align]
[align=left] float scale = 1.0f;[/align]
[align=left] int targetDensity = 0;[/align]
[align=left] if (opts != null) {[/align]
[align=left] final int density = opts. inDensity;[/align]
[align=left] targetDensity = opts. inTargetDensity;[/align]
[align=left] if (density != 0 && targetDensity != 0) {[/align]
[align=left] scale = targetDensity / ( float) density;//请注意这里有计算缩放比例,而decodeByteArray未进行此项操作
[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);[/align]
[align=left] if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);[/align]
[align=left] [/align]
[align=left] finish = false;[/align]
[align=left] } else {[/align]
[align=left] bm = nativeDecodeAsset(asset, outPadding, opts);[/align]
[align=left] }[/align]
[align=left] }[/align]
  ... ...

[align=left] public static Bitmap decodeByteArray (byte [] data, int offset, int length, Options opts) {[/align]
[align=left] if ((offset | length) < 0 || data. length < offset + length) {[/align]
[align=left] throw new ArrayIndexOutOfBoundsException();[/align]
[align=left] }[/align]
[align=left] Bitmap bm = nativeDecodeByteArray(data, offset, length, opts);[/align]
[align=left] [/align]
[align=left] if (bm == null && opts != null && opts. inBitmap != null) {[/align]
[align=left] throw new IllegalArgumentException( "Problem decoding into existing bitmap");[/align]
[align=left] }[/align]
[align=left] return bm;[/align]
[align=left] }[/align]

场景策略
网络获取的图片都是服务端处理过的图片,按原尺寸处理没有任何问题,但是本地图片或拍照图片,经常是高清大图,极易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内存),而非很快就会被回收掉的弱引用,结果会造成不再使用的旧图片大量留驻内存,直到内存紧张时才被回收,虽然会提升一定性能,但是应用会“看上去”比较耗内存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: