标签:
在前两天我发布的文章:Volley源码分析一 中我较为详细的分析了Volley,今天继续,这篇文章会讲一些上一篇没有提到的比较细节的点,以及对于Volley源码中一些可以优化的实现的一些思考
byte[] 的回收池,用于 byte[] 的回收再利用,减少了内存的分配和回收。主要通过一个元素长度从小到大排序的ArrayList作为 byte[] 的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素。
public synchronized void returnBuf(byte[] buf)
将用过的 byte[] 回收,根据 byte[] 长度按照从小到大的排序将 byte[] 插入到缓存中合适位置。
public synchronized byte[] getBuf(int len)
获取长度不小于 len 的 byte[],遍历缓存,找出第一个长度大于传入参数len的 byte[],并返回;如果最终没有合适的 byte[],new 一个返回。
private synchronized void trim()
当缓存的 byte 超过预先设置的大小时,按照先进先出的顺序删除最早的 byte[]。
上面的内容摘自:Volley源码解析
自定义请求的话就拿ImageRequest和ClearCacheRequest来讲解好了,ClearCacheRequest是因为比较特别所以单独拿出来讲一讲,而ImageRequest的话其实和StringRequest什么的没有本质上的区别,介绍了这个就知道其他怎么写了,代表了大部分请求的写法,选择它讲是为了引出后面的内容,因为ImageRequest请求图片还不是很好,只有磁盘缓存的话是明显不够的。
用于人为清空 Http 缓存的请求。
添加到 RequestQueue 后能很快执行,因为优先级很高,为Priority.IMMEDIATE。并且清空缓存的方法mCache.clear()写在了isCanceled()方法体中,能最早的得到执行。但是我看的一些文章中都说这个清理缓存的请求的写法不是那么好,大家就稍微注意一些这一条请求就行,至于其中的代码,没什么亮点可介绍。
首先从他的构造函数开始,最完整的构造方法(所有构造方法最后也会走这一个构造方法)是有7个参数,分别是地址、正确响应的回调、最大宽度、最大高度、缩放类型、颜色属性、请求失败的回调,在此构造函数中还设置了它的优先级(优先级设置的很低)和它的重试策略,重试两次,超时时间比默认的小了很多,这些都不是重点,重点在这个parseNetworkResponse方法(基本上所有的请求的重点都是这个方法),看他是如何解析响应数据。首先根据我们传入的最大宽度和最大高度加载合适的图片( 具体如何计算这里,说白了就是小学算数,唯一要注意的是BitmapFactory.Options中的injustDecodeBounds参数,设置成true的时候是先读取这个图片的信息,但不加载到内存,读取到这些信息之后我们就可以去算要怎么缩放,计算好之后在真正去加载bitmap到内存,新手可能会不知道这个,这能很好的优化图片加载,降低oom出现的风险 ),看着问题不大,但是如果非得较真的话其实是写的不够好的,主要的点就在bitmap的内存管理上,Bitmap的频繁创建时很耗内存的(加入我们在一个listview上加载大量图片),在Android 2.3.3 (API level 10) 以及更低版本上,使用recycle()方法可以使得程序更快的释放内存,但是在Android 3.0 (API Level 11)开始,引进了BitmapFactory.Options.inBitmap字段。 如果使用了这个设置字段,decode方法会在加载Bitmap数据的时候去重用已经存在的Bitmap。这意味着Bitmap的内存是被重新利用的,这样可以提升性能,并且减少了内存的分配与回收。具体如何做大家可以参考android官网:管理Bitmap的内存
其中也要结合到软引用的知识,大家可以支持一下我的另一篇文章: java 软引用、弱引用、强引用、虚引用的解析
自定义请求上面讲完了,主要大家要去写的就是如何去解析response,其他也没什么,我只是讲了一些大家可以去关注的点。其实对于怎么写自定义请求我屁都没讲。
看了上面ImageRequest的分析知道了有一个可以优化的点就是bitmap内存的复用,但是对于图片的光就一个磁盘缓存是不够用的,还需要内存缓存,但是单单自己实现一个自定义的request很难把内存缓存的东西加上去,有什么办法呢!看下源码中的ImageLoader的实现,可以看到他已经不是一个请求了。
还是老规矩先看看他如何使用的
第一步我就不说了,从第二步开始
ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
@Override
public Bitmap getBitmap(String url) {
return null;
}
});
第一个参数就是请求队列,第二个参数就是缓存,我们可以实现ImageCache接口实现自己的内存缓存。比如:
public class BitmapCache implements ImageCache {
private LruCache<String, Bitmap> mCache;
public BitmapCache() {
int maxSize = 10 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
代码摘自:Android Volley完全解析(二),使用Volley加载网络图片这里有比较详细的Volley的使用方法的介绍。
将上面实现的BitmapCache 当做ImageLoader构造函数的第二个参数即可。
第三步:获取一个Imagelistener对象,
ImageListener listener = ImageLoader.getImageListener(imageView,
R.drawable.default_image, R.drawable.failed_image);
参数就不用介绍什么意思了吧,大家一看就懂了。ImageListener继承自Response的ErrorListener还自己又加了一个onResponse方法,它的意思就是加载有误的时候调用onErrorResponse显示错误图片,加载成功就显示加载的图片(太简单了我都懒得讲)。
最后调用ImageLoader的get()方法,这才是重点(不像我们大学的时候考试,全时重点)。get方法主要就是五个参数:地址、上面实现的ImageListener 、最大宽度、最大高度、缩放类型(这个一般就使用默认的) 啊,接下来看看其中的实现流程:
1、判断get方法是否执行在UI线程(因为要改变ui)。根据地址,最大宽高、缩放类型得到获取缓存需要的key
2、用上面得到的key去我们自己实现的缓存中去拿对应的图片数据,拿到数据就执行3,没有拿到执行4
3、创建ImageContainer对象(有bitmap对象和地址),调用listener的onResponse方法显示图片,然后返回结束
4、同样创建一个ImageContainer对象,但是和3中的不一样,这个ImageContainer对象没有bitmap,创建完之后也调用listener的onResponse方法,因为没有bitmap所以显示的是默认的图片
5、从hashmap中拿取BatchedImageRequest,能拿到就执行6,拿不到就执行7
6、将ImageContainer对象添加到请求BatchedImageRequest中
7、创建一个ImageRequest,忘记了的看一看前面的内容,在成功得到请求图片的时候讲此bitmap保存到缓存中,并显示图片,否则显示错误的图片。
8、将此ImageRequest添加到请求队列中
上面的就是基本的流程,可能会有问题的就是BatchedImageRequest和ImageContainer,其中ImageContainer里记录了bitmap、listener、key以及url,而BatchedImageRequest其中有一个LinkedList保存的是ImageContainer对象,有什么用呢,这里的用处就是记录相同请求,请求一次然后将请求结果循环读取这个LinkedList进行分发,这么讲是不是清楚些
第一个问题:对于结果的分发,也就是在ImageRequest的onResponse和onErrorResponse中,大家看完我的上一篇文章之后应该能知道,默认的分发实现已经确保了上述两个方法会在ui线程中执行,但是在这里你跟踪代码到最后进行分发的时候,又做了一次确保在ui线程分发的工作(虽然最后又一个不一样的地方:做了延时,但我觉得没什么必要),也有可能有人会觉得这样做没有问题,确保不会有错,但是就我看来这就是重复工作,还是避免这种重复工作。
第二个问题:假设我们有两个相同的请求,第一个请求创建添加进了队列并添加入保存BatchedImageRequest的hashmap中,此时第二个相同的请求立马进来,而第一个请求还没执行到判断是否取消请求的那句,于是第二个请求就加入到了那个LinkedList中返回,这个时候第一个请求还是没有执行到判断是否取消请求的那句,到这里我们在取消第一个请求,会发生什么呢!!!第二个请求永远都执行不到了,因为它没有加入到请求队列中。虽然这种情况是很小几率的,但这依旧是个bug。
上面的而第二个问题其实是我想错了,但是这里还是留着比较好,毕竟也是自己走过的路,为什么说没有问题呢!主要看ImageLoader怎么取消一个请求,就单单按我上面所说肯定是有问题的,但是它不是那么取消的,首先ImageLoader没有继承自Request,如果要取消一个请求,需要使用的是ImageContainer里的cancelRequest方法(get方法返回的就是一个ImageContainer对象,很方便我们取消)。
1、我们可以看到Cache的默认实现DiskBasedCache的实现,在超过限制的大小之后,他就去删除文件,这样写是对的,但是它的删除策略有些随意,很可能出现你刚放进去的,马上就被删除的情况。可以考虑使用lru或者lru结合过期什么的。
2、(这个是我在其他地方看到原话照拿来的:Android的volley框架心得) Volley中DiskBasedCache初始化非常慢的问题。 initialize方法中,遍历所有的缓存文件,读取头信息存入map中,当缓存文件非常多的时候,google提供的初始化实现,耗费了10s以上的时间!!!不知道这是不是个bug。也就是说,每次newRequestQueue,都需要等待十秒的时间才能开始处理request。如果这样的话,这个框架就没法用了。 目前我也没找到好的方法。有两个优化方案。 (1) 有人在stackoverflow中提到过这个问题,然后他给出了解决方案。在initialize中,给FileOutputStream在套一层BufferedOutputStream。本人测试过,速度有十倍的提升。基本能控制初始化的时间在1-3s。 (2) 提前初始化。每一次newRequestQueue都要花费这么长时间初始化,但是实际使用中,我们需要new那么多RequestQueue吗?看源代码可以知道,每个RequestQueue实际启动了5个循环线程来调度各种请求。其中四个用来处理网络请求,一个用来处理cache请求。对于一个app来说,这样已经足够,我们没必要每个页面都启动一个RequestQueue。因此,我把RequestQueue设置为单例,在Application中初始化,这样就可以避免initialize被反复调用,同时也能在界面出来之前,预先准备好数据。
其他的优化还没有发现,大家如果知道的请一定要告诉我!
最后Volley源码分析大致就到这里结束了,之后就不会再讲解Volley的源码了,但还是会拿Volley和其他一些图片请求框架进行比较,敬请期待!
标签:
原文地址:http://blog.csdn.net/u012806692/article/details/51313531