本帖最后由 boredream 于 2014-5-27 09:07 编辑
ImageLoader和Volley图片部分还包括其他大部分图片框架,基本上图片处理都差不多,区别仅在于部分优化了,而优化方面UIL即Universal-Image-Loader框架做的最好,所以这部分章节算是温习一下图片处理以及寻找下其他框架里面一些不一样的图片处理方式(只关注图片方面) 首先是ImageLoader 单张图片的缩放问题 int calculateScale(final int requiredSize, int widthTmp, int heightTmp) { int scale = 1; while (true) { if ((widthTmp / 2) < requiredSize || (heightTmp / 2) < requiredSize) { break; } widthTmp /= 2; heightTmp /= 2; scale *= 2; } return scale; } 也是写法换了一下,其实意义等同于UIL框架中的CROP类型时的缩放,也等同于官方推荐的基本处理(参见教程一)如下 while (srcWidth / 2 >= targetWidth && srcHeight / 2 >= targetHeight) { // && srcWidth /= 2; srcHeight /= 2; scale *= 2; } 一模一样~!~!~!~! 两个条件完全反过来,而一个是while继续的条件,一个是break中断的条件,所以综合起来实际意义是完全一样滴 ImageLoader框架相当于我们教程一里面的,只有对一种情况的解析,没有UIL那种对不同缩放方式的区别处理 此外是色彩样式修改 框架源代码全局搜索了下关键字,也找了主页文档介绍,貌似没有发现ImageLoader对色样有设置,需要修改色样的话需要自己实现了 缓存池部分 ImageLoader框架分三种缓存池 LruBitmapCache LRU算法的强引用缓存 NoCache 无缓存情况(其实就不能算缓存池了,没啥意义一般不会使用这个类) SoftMapCache 软引用缓存 支持的类型比较少,缓存池只能算有两种,单独使用强引用和单独使用软引用这两种,没有二级缓存的处理 同样也是提供三个类型,由使用者自行设定缓存类型,设置方法为 SettingsBuilder.withCacheManager(...) LruBitmapCache强引用缓存的分析 public final V put(K key, V value) { if (key == null || value == null) { throw new NullPointerException( "key == null || value == null" ); } V previous; synchronized ( this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved( false, key, previous, value); } trimToSize(maxSize); return previous; } 再简单的唠叨下吧 putCount记录添加数量的,这里暂时用不上不介绍 synchronized是为了保证多线程访问时size计算包括putCount的计算不要出现混乱 首先size += 将put的对象的大小加至当前缓存池大小size值上 map.put的返回值是已有对于key时返回的被替换value值,如果非空则代表上一个被移除了,自然要-=减去其size值 然后就是关键的entryRemoved方法了,方法内部是无内容的,只是相当于将移除的bitmap对象作为参数传入方法中 最后调用trimToSize方法检测当前缓存大小是否大于阀值,做对应处理 private void trimToSize(int maxSize) { while (true) { K key = null; V value = null; synchronized ( this) { if ( size < 0 || ( map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!" ); } if ( size <= maxSize) { break; } // Change if ( map.entrySet().iterator().hasNext()) { Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } } entryRemoved( true, key, value, null); } } 和UIL框架也差不多(其实都是跟官方LruCache类里对于方法改的,所以名字逻辑几乎都没区别) 检测size是否超过maxSize,是的话移除之~ 然后对于size处理下 entryRemoved方法 SoftMapCache软引用缓存 总的来说,最基本的都有,但是不够完善,需要自行实现诸如二级缓存等一些加强功能 --------------------------------------------------------------------------- Volley框架图片加载部分 首先还是单张图片的处理 /** * The real guts of parseNetworkResponse. Broken out for readability. */ private Response<Bitmap> doParse(NetworkResponse response) { byte[] data = response. data; BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions. inPreferredConfig = mDecodeConfig; bitmap = BitmapFactory. decodeByteArray(data, 0, data.length, decodeOptions); } else { // If we have to resize this image, first get the natural bounds. decodeOptions. inJustDecodeBounds = true; BitmapFactory. decodeByteArray(data, 0, data.length, decodeOptions); int actualWidth = decodeOptions. outWidth; int actualHeight = decodeOptions. outHeight; // Then compute the dimensions we would ideally like to decode to. int desiredWidth = getResizedDimension(mMaxWidth , mMaxHeight , actualWidth, actualHeight); int desiredHeight = getResizedDimension(mMaxHeight , mMaxWidth , actualHeight, actualWidth); // Decode to the nearest power of two scaling factor. decodeOptions. inJustDecodeBounds = false; // TODO (ficus): Do we need this or is it okay since API 8 doesn‘t support it? // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; decodeOptions. inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); Bitmap tempBitmap = BitmapFactory. decodeByteArray(data, 0, data.length, decodeOptions); // If necessary, scale down to the maximal acceptable size. if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) { bitmap = Bitmap. createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true); tempBitmap.recycle(); } else { bitmap = tempBitmap; } } if (bitmap == null) { return Response.error(new ParseError(response)); } else { return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); } } 两个地方是关键,一个是getResizedDimension一个是findBestSampleSize,分别介绍下 先看后一个findBestSampleSize,我们比较熟的逻辑方法如下 static int findBestSampleSize ( int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { double wr = (double) actualWidth / desiredWidth; double hr = (double) actualHeight / desiredHeight; double ratio = Math.min(wr, hr); float n = 1.0f; while ((n * 2) <= ratio) { n *= 2; } return (int) n; } 又一种算法~但实际意义还是和教程一里的一样~数学逻辑好的应该一下就能看出来,看不出来的...做个demo对比下几个方法对不同值的计算结果,懒得测试的,那就信我的话吧,基本是木有区别的 具体计算效率的区别我水平有限就不研究了,不过这种写法看着貌似显得流弊点?自己需要写图片压缩值计算的时候可以用用~ 然后是另一个方法的介绍getResizedDimension,以前没见过,有点参考价值~ ----------------------------------------------------------------------------------- 本来想偷懒网上搜下这方法的说明,可惜最多查到一句"按一定规则计算出...", 算了 我还是自己跑项目边研究边死扣吧~其实这段方法学习参考价值更大一点,实际上设置所需压缩宽高值以后,图片就会按照之前那种规则去获取一个适当的值了,即不采用本方法压缩效果也是可以实现的 /** * Scales one side of a rectangle to fit aspect ratio. * * @param maxPrimary Maximum size of the primary dimension (i.e. width for * max width), or zero to maintain aspect ratio with secondary * dimension * @param maxSecondary Maximum size of the secondary dimension, or zero to * maintain aspect ratio with primary dimension * @param actualPrimary Actual size of the primary dimension * @param actualSecondary Actual size of the secondary dimension */ private static int getResizedDimension (int maxPrimary, int maxSecondary, intactualPrimary, int actualSecondary) { // If no dominant value at all, just return the actual. if (maxPrimary == 0 && maxSecondary == 0) { return actualPrimary; } // If primary is unspecified, scale primary to match secondary‘s scaling ratio. if (maxPrimary == 0) { double ratio = ( double) maxSecondary / ( double) actualSecondary; return ( int) (actualPrimary * ratio); } if (maxSecondary == 0) { return maxPrimary; } double ratio = ( double) actualSecondary / ( double) actualPrimary; int resized = maxPrimary; if (resized * ratio > maxSecondary) { resized = ( int) (maxSecondary / ratio); } return resized; } 方法传入4个数据,即两组 使用方法是调用两次,主为高次为宽时,计算的结果是调整后所需高度值,也就是计算为主的那个所需值 举个实际例子吧 计算结果 desiredWidth = 180; desiredHeight = 154; 直观上理解就是,将所需宽高值调整成了与原图宽高一样的比例 (所需宽高调整后比例180/154≈1.17 原图比例720/617≈1.17) 再次测试,同一张图片,maxWidth和maxHeight都设成200,计算后结果是 desiredWidth = 200; desiredHeight = 171; 宽高比也和原图比例相同(200/171≈1.17) 即,在限定宽高都设定过的情况下(都不为0),计算原图比例然后将限定宽高大小调整为与原图一致的比例,且调整后的宽高值都小于等于原限定值宽高 任一方为0或者都为0的情况这里就不分析了,不是太重要,是对限定值为0即未设定数值时进行的一个对应处理 之前UIL框架中针对两种不同缩放类型,将压缩比例值计算区分成了两种,一个是||连接条件,一个是&&连接条件 ||的处理结果是保证压缩后图片任意一条边大于限定值对应边即可 &&的处理结果是保证压缩后图片宽高两条边都要大于限定值对应边 &&的处理与官方提供的例子一致,也是教程一中介绍的计算方法 那volley的这种算法,简单实验了几个数据,我发现结果和UIL中FIT_INSIDE情况即||连接条件计算的结果差不多,我也撸了简单demo对比了更多组不同数据下两者的结果,也都是一致的~ 逻辑上理解,如果限定值和原图比例不同,那么处理后的限定值其中一个边会减少~那以这个调整后的限定值压缩图片的话,最终结果就有可能出现: 最后获取的压缩图片样式一条边小于调整前我们设置的限定值对应边 最终造成了只有一条边满足大于等于限定值的情况,即与UIL中FIT_INSIDE情况相同 总结,官方的方法是保证无论什么缩放显示类型,都能保证压缩后图片宽高值大于等于限定值 UIL比较好,不同情况都有考虑,比例值计算的比较精确 Volley框架则是另一种了,即默认情况下能够保证图片清晰度(像素密度能达到目标),但是CROP等缩放类型下尤其是长宽比较大的情况时,则压缩后图片无法保证清晰度(由于此情况较少,所以大部分情况下的压缩质量还是有保证的) 注:可能有不太准确的地方,我正在整理 1.教程一官方方法 2.UIL框架 3.Volley框架图片处理 三者的具体数据对比,因为数据量比较大且杂,所以之后如~果~能整理顺利且有时间的话,就单开一章对框架做个简单比较~ --------------------------------------------------------------------------- 色彩样式部分 多张图片缓存池 一个木有- - 只提供了一个缓存池类型接口ImageCache,需要自定义类实现它 网上主流的做法,包括demo都是自定义一个类继承LruCache然后实现Volley框架中对应接口 类写法也很简单,教程二熟悉的话估计都会写,类代码如下 public class BitmapLruCache extends LruCache<String, Bitmap> implements ImageCache { public BitmapLruCache( int maxSize) { super(maxSize); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } } 同样,你也可以利用LinkedHashMap实现,还可以添加上二级缓存的结构都可以 --------------------------------------------------------------------------- 简单过了一遍ImageLoader框架和Volley框架图片部分,再次从侧面验证了我们教程中方法滴靠谱性~ 这也是我专门拿Volley出来介绍的原因,之后一章会对几个框架的不同图片处理效果做个简单对比,再之后一章就是Volley框架图片缓存的二次开发了,我也是第一次弄,肯定有不足的地方,到时候大家一起探讨~ |
Android Bitmap 开源图片框架分析(精华五)