Volley图片加载一些不一样的使用姿势

这一篇也是关于Volley的,主要是在个人使用的时候总结出来的一些不同于大多数教程的使用经验,我会说明我使用这些使用方法的原因,但是并不一定它们一定是最好的,如果看到这篇文章,请自行斟酌。

第一点:使用全局请求队列

大家都知道,Volley无论使用什么样的Request,最终都要把它添加到一个RequestQueue中,这样Volley会在后台创建一个线程池,并发的发出请求。创建一个RequestQueue的方法是这样的。
RequestQueue queue = Volley.newRequestQueue(context);
这里补充一点,静态方法newRequestQueue()方法有一个拥有两个参数的重载,第二个参数你可以传入HttpStack的实例或者HurlStack的实例(这两个类在Volley内部已经提供了),分别代表Volley使用HttpClient或HttpURLConnection的方式发起网络请求。当然,大家都知道Square有一个重新封装了http协议的库okhttp,它的速度非常快,我们也可以自己写一个OkHttpStack,创建一个实例在这里传入,让Volley用OkHttp发起网络请求。至于怎么写,大家可以参考GitHub上Android大神Jack Wharton的方法:https://gist.github.com/JakeWharton/5616899
扯远了,回归主题。把一个请求添加到RequestQueue中的方法是这样的:queue.add(request);相信大家也都知道。
现在我们的问题是,我们到底需要多少个RequestQueue,为每个Request都创建一个RequestQueue显然是不对的,这样请求就不并发了,Volley引以为傲的设计就没什么卵用了。而且每个RequestQueue对象都会开启一个线程池,创建太多的RequestQueue显然是浪费资源的。郭霖大神的文章中说为每个Activity创建一个RequestQueue。如果你的Activity中的Request足够的多,貌似也是可以的,但是我相信大多数人的Activity中的不同的网络请求不超过四个,而且最极端的情况是某个Activity中只有一个request,这时候你专门为了它创建一个请求队列显然是浪费,而且只要你的Activity不被销毁,请求队列中的线程池就会一直存在,如果你的返回栈中的Activity数量较多的话,这样也是比较占资源的。说了这么多,我推荐另外一些教程中写的方法,就是利用Application类创建一个全局的RequestQueue。写法如下:
public class MyApplication extends Application {

    private static RequestQueue queues;

    @Override
    public void onCreate() {
        super.onCreate();
        queues = Volley.newRequestQueue(getApplicationContext(), new OkHttpStack());
    }

    public static RequestQueue getHttpQueues() {
        return queues;
    }

}

这样你需要RequestQueue对象的时候,你就可以这么写:MyApplication.getHttpQueues();
这样,你的应用全局都使用这一个请求队列,可以同时并发的发出四个网络请求,其余的请求则等待,这样既能保证Request的并发执行,又能保证子线程数量不会过于庞大,导致有些子线程没有工作从而造成资源浪费。

第二点:编写一个合适的图片缓存

大家都知道,在使用ImageLoader的时候,我们要传入一个ImageCache参数,ImageCache是一个接口,它没有具体实现,因此我们需要手动编写一个类实现这个接口,从而真正实现图片缓存功能。关于这个类的编写也不复杂,主要就是使用了LruCache,代码如下:
public class BitmapCache implements ImageLoader.ImageCache {

    private LruCache<String, Bitmap> cache;

    public BitmapCache() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        cache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }

    @Override
    public Bitmap getBitmap(String url) {
        return cache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        cache.put(url, bitmap);
    }  ......
LruCache的使用我也不多嘴了,《Android开发艺术探索》一书中有详解,Android官方开发文档中也有详细的教程,网上相关的博客也是数不胜数,大家可以自己去搜一搜。
这个类的大体思想就是把系统划分给一个APP最大使用的内存的1/8用来缓存图片,而且重写了接口的getBItmap和putBitmap方法,这两个方法会在ImageLoader内部被自行调用,大多数时候我们不需要手动去调用,它们的作用分别是通过键,找到缓存的BItmap以及将Bitmap和对应的键存入LruCache。
我们之前看到的ImageLoader使用的教程也许会教大家这样写:ImageLoader imageLoader = new ImageLoader(MyApplication.getHttpQueues, new BItmapCache());
这样写确实是可以的,但是,同样造成了很大的浪费。比如说,同一个缓存图片可能在两个地方被用到,但由于创建了两个BitmapCache实例,这两个地方仍然发起了两次网络请求,并且缓存了两张一模一样的图片。LruCache的内部实现是LinkedHashMap,你可以想象,每次创建BitmapCache的实例的时候都会创建一个LinkedHashMap,且每个对象中只有一个键值对,这显然不是我们想要的,这也没有让LruCache内部的最近最少使用算法发挥作用。
由于我们有了之前的经验,我们一下子就能想到,我们也把BitmapCache写到Application中去,得到一个全局的BitmapCache。方法和之前的RequestQueue如出一辙,这里我就不重复写了。
虽然我们把功能大体实现了,但是有些东西我们还是云里雾里,没有搞明白,ImageLoader每次向BitmapCache中加入缓存的时候的键究竟是什么,只是一个url吗,如果只是一个url,可以想象这样一种场景,用户要加载头像,打开主页的时候加载了一次,Bitmap被缓存了,当用户查看app中内容的时候,就比如说文章吧,假如说文章中有评论,评论是该用户发送的,那么加载评论人头像的时候,由于键值是url,所以会从缓存区中直接加载,但是主页的头像和评论区的头像尺寸是不同的,把一张大图加载到小控件上是极其不合理的,也许这样还能勉强显示,但是可以想象另外一种场景,我们先有了一张图片小尺寸版的缓存,这时候又遇到了要加载它大尺寸版的地方,由于键值都是url,是相同的,所以会直接调用缓存,把小图片加载到大控件上效果可能会更差。所以键值一定不是单纯的url。我们这时候要去查看ImageLoader的源码,从哪里开始看效率高呢?当然是看ImageLoader内部调用ImageCache的getBitmap和putBitmap方法的地方使用的键是什么。
翻阅源码可知,生成键用了一个静态方法,getCacheKey(),把ImageLoader的源码翻阅到最下面,我们找到了getCacheKey的具体实现。
private static String getCacheKey(String url, int maxWidth, int maxHeight, ScaleType scaleType) {
        return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
                .append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
                .toString();
    }
传入每次使用ImageLoader的ImageView的最大宽度,高度,以及ScaleType属性,以及url,生成一个String对象,就是键。我们知道了,键包含四个信息,url,最大宽度,最大高度,ScaleType。这样就避免了我们上面所说的场景带来的麻烦,想想也是,这种问题Volley的设计者一定早就想到了。

第三点,特殊使用场景中的特殊用法

想象这样一种使用场景,我们在主页加载了用户头像,进入个人信息界面以后也会显示用户头像,但这个头像由于和主页的url,宽高,ScaleType都一模一样,所以不会从网络加载,而是直接从缓存取出使用。现在问题来了,用户是有可能更换头像的,用户从相册选了一张照片,剪裁之后上传给了服务器,这一些列动作完成之后,我们加载头像的url并没有变,但是服务器返回的结果变了。现在我们必须把个人信息界面的用户头像更新,并且我们要保证把APP退回到主界面的时候,主界面的用户头像也能改变。这时候我们在发送一次网络请求?显然这是不合适的,因为刚才用户更换照片的时候,我们已经得到了原图的Bitmap,没必要再发送一次网络连接去消耗资源。
这时候我们要怎么办?首先,我们要把原图的Bitmap压缩成适合个人信息界面头像控件大小的尺寸。
我们可以这样写:bitmap = Bitmap.createScaledBitmap(bitmap, headPortrait.getWidth(), headPortrait.getHeight(), true);其中headPortrait是显示头像的NetworkImageView。
压缩之后我们要把之前缓存中的没有修改头像前的Bitmap换掉。我们可以这样写:cache.putBitmap(key, bitmap);
cache的初始化不用我多数,自然是cache = MyApplication.getBitmapCache();这个在第二点里说过。那key怎么拿到呢,之前我们看了源码知道key是怎么生成的,但是原方法写在Volley这个库中,又是个私有方法,我们还是不要随意去修改权限修饰符的好,于是根据它的格式,我们可以自己写一个getCacheKey()静态方法,放在BitmapCache中。如下所示:
    public static String getCacheKey(String url, ImageView imageView) {
        return "#W" + imageView.getWidth()
                + "#H" + imageView.getHeight()
                + "#S" + imageView.getScaleType().ordinal()
                + url;
    }
由于只是字符串的连接,所以我们没必要用StringBuilder,直接用String就好了。
获取key我们就可以这样写:String key = BitmapCache.getCacheKey(mUrl, headPortrait);
最后我们调用NetworkImageView的setImageUrl()方法就OK了,不过在调用之前,我们还要调用NetworkImageView的updateImage()方法,这个方法是我手写的,如果不知道可以看我之前的文章:  从源码角度看Volley中图片加载ImageLoader的重复URL过滤功能,这是由于NetworkImageView会自动检查url,如果这次请求的url和之前的相同,则它会什么都不做直接return。

第四点,ImageLoader也是可以全局化的

我发现,ImageLoader在初始化的时候只需要传入两个参数,第一个是RequestQueue,第二个是ImageCache。从第一和第二点中,我们都通过Application,做了全局化的RequestQueue和实现了ImageCache的BitmapCache。那我们自然也可以把ImageLoader全局化,这样就不用再处处创建ImageLoader对象从而节约资源。现在整个MyApplication类的代码如下所示:
public class MyApplication extends Application {

    private static RequestQueue queues;
    private static BitmapCache cache;
    private static ImageLoader imageLoader;

    @Override
    public void onCreate() {
        super.onCreate();
        queues = Volley.newRequestQueue(getApplicationContext(), new OkHttpStack());
        cache = new BitmapCache();
        imageLoader = new ImageLoader(queues, cache);
    }

    public static BitmapCache getBitmapCache() {
        return cache;
    }

    public static RequestQueue getHttpQueues() {
        return queues;
    }

    public static ImageLoader getImageLoader() {
        return imageLoader;
    }

}

总结:

以上就是我个人使用Volley时总结的一些不同的使用方式,不过当然,其中有些方法也不一定是最好的,如果我发现了这些方法的不足,我会随时回来打自己脸更新的,当然我如果总结出另外一些好的使用技巧也会随时来更新,欢迎看到这篇文章的人一起讨论。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值