自定义图片加载器

来源:互联网 发布:python 双引号 变量 编辑:程序博客网 时间:2024/05/18 21:46

自定义图片加载器


前言

图片加载器是一个非常常用的功能模块,但是一般我们不会去从零开始自己写一个,因为有Glide、Picasso、Fresco等一些优秀的开源库,或者公司自己维护了一套。再者完整实现这一整套功能是很耗费精力的。而这篇文章仅仅是为了学习程序的设计模式和LruCache、Bitmap优化显示等,去实现这样的一个库。

先说下面向对象六大原则及在此项目里的体现:

1. 单一原则

简单来说,一个类中,应该是一组相关性很高的函数、数据的封装。具体根据类的职责。

2. 开闭原则

提取共同的函数为一个接口,通过设置接口对象或者实现该接口的对象,实现不同的缓存策略。

3. 里氏替换原则

在这里体现的就是只要实现IImageCache接口的都可以设置到缓存去。

4. 依赖倒置原则

模块间依赖通过抽象发生,实现类不直接发生依赖关系,其依赖是通过抽象类和接口。通过这样减少耦合。

5. 接口隔离原则

fileOutputStream等其他流的关闭,这里只要是实现Closeable接口的都可以关闭close();只要知道类实现了closeable,就可关闭,其他的一概不关心,这就是接口隔离。

6. 迪米特原则

ImageCache里面使用了DiskLruCache,但是用户不需要知道实现细节,只需要和ImageCache打交道。即使里面的DiskLruCache替换为其他的缓存实现,用户也不会感知到。


下面开始分析

ImageLoader:负责下载图片和加载的类.

ImageCache:含有LruCache和DiskLruCache.

ImageResizer:获取特定采样的bitmap.

IImageCacahe:抽象ImageCache的接口,之前说的六大原则,很重要的一点是强调抽象.

ImageCache里面使用了LruCache和DiskLruCache:

 lruCache = new LruCache<String, Bitmap>(cacheSize) {            @Override            protected int sizeOf(String key, Bitmap bitmap) {                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;            }        };               try {            diskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);        } catch (IOException e) {            e.printStackTrace();        }

这里缓存大小cacheSize设置为当前进程可用内存的0.25倍.sizeOf完成对Bitmap对象大小的计算。
lruCache缓存和读取:
lruCache.put(key, bitmap), Bitmap bitmap = lruCache.get(key);
DiskLruCache缓存和读取:

 public void addToDiskCache(Bitmap bitmap, String key) throws IOException {        DiskLruCache.Editor editor = diskLruCache.edit(key);        if (editor != null) {            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);            if (addToDisk(bitmap, outputStream)) {                editor.commit();            } else {                editor.abort();            }            diskLruCache.flush();            outputStream.close();        }    }
try {                DiskLruCache.Snapshot snapShot = diskLruCache.get(key);                if (snapShot != null) {                    FileInputStream fileInputStream = (FileInputStream) snapShot.getInputStream(DISK_CACHE_INDEX);                    FileDescriptor fileDescriptor = fileInputStream.getFD();                    bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, width, height);                    if (bitmap != null)                        addToCache(bitmap, url);                    return bitmap;                }            } catch (IOException e) {                e.printStackTrace();                Log.e(TAG, "getDiskLruCache error");            }

BitmapFactory.decodeStream(in),不能decode两次。所以用ImageResizer.decodeSampledBitmapFromFileDescriptor根据ImageView大小获取对应采样率的Bitmap(因为ImageView小,而图片的分辨率大时,加载原图是很浪费内存的)。主要是计算options.inSampleSize:

public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {        final BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        BitmapFactory.decodeFileDescriptor(fd, null, options);        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);        options.inJustDecodeBounds = false;        return BitmapFactory.decodeFileDescriptor(fd, null, options);    }

ImageLoader里面先从ImageCache中读取,若是没有,者从网络下载,但是第一次下载获取的Bitmap不能直接设置到ImageView中去。

private IImageCacahe cache;private Bitmap downloadBitmapFromUrl(String urlString, int width, int height) {        Bitmap bitmap = null;        HttpURLConnection urlConnection = null;        BufferedInputStream in = null;        try {            final URL url = new URL(urlString);            urlConnection = (HttpURLConnection) url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);            bitmap = BitmapFactory.decodeStream(in);            if (cache instanceof ImageCache)                ((ImageCache) cache).addToDiskCache(bitmap, Utils.keyFormUrl(urlString));            bitmap.recycle();            bitmap = cache.getFromCache(urlString, width, height);// TODO: 2017/4/4  这里因为第一次下载的原图需要重新采样获取需求大小的bitmap,而BitmapFactory.decodeStream(in),不能decode两次。        } catch (final IOException e) {            Log.e(TAG, "Error in downloadBitmap: " + e);        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            Utils.close(in);        }        return bitmap;    }

根据imageview的大小,通过BitmapFactory.Options options,,options.inSampleSize,计算需要的inSampleSize压缩得到bitmap再加载到imageview上去。
若未采样优化,加载一屏6张图片原图,总20张,上下滑动时,内存占用情况如下:
这里写图片描述
优化后内存占用情况如下 :这里只是指定了下需要的bitmap显示的大小(单位为像素,不同的设备上相同的dp显示的像素不一致,所以大小也会不同)。根据需要的大小及其FileDescriptor,从其DiskLruCache获取bitmap,再显示到ImageVIew中去。
这里写图片描述
对比内存占用区别相当大,但是显示的效果一致并没有打折扣。

还有需要解决的一个问题是ImageView的复用,例如在Listview中加载图片,如果需要加载的图片用户已经划过去了,那么应该忽略这张图片,具体的就是在 target.setTag(TAG_KEY_URI, url),线程池的任务中 handler.obtainMessage(SUCCESS_COMPLETE, new LoaderResult(target, url, bitmap)).sendToTarget();然后在handler的handleMessage中
imageView.getTag(TAG_KEY_URI);对比url是否一致,不一致则忽略该图片。

最后顺便接入了leakcanary,发现内存泄漏:华为mate8上Android6.0的HwPhoneWindow的mContext持有MainActivity对象,导致泄漏;红米note2上Android5.0:未发现有内存泄漏。这里是华为的系统问题,不知有没有大神能指点这该如何解决,感激不尽。
项目源码地址:https://github.com/Ulez/UImageLoader
参考资料:
1.Android开发艺术探索
2.Android源码设计模式解析与实战

0 0
原创粉丝点击