Android Volley架构分析

Android volley架构分析

 

我们在开发Android应用时,网络技术是非常普遍的,大多数情况下我们都是使用Http协议,Android系统中主要有两种方式来进行Http的通信,HttpURLConnectionHttpClient。我们在实际编程过程中会发现这两个类用起来还是很复杂的,而Volley就是将其进行适当的封装,使开发者用起来就比较简单。

一、Volley例子

1)初始化对象

RequestQueue mQueue = Volley.newRequestQueue(context);

2Get请求

StringRequest stringRequest = new StringRequest("http://www.baidu.com",

new Response.Listener<String>() {

@Override

public void onResponse(String response) {

Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();

}

}, new Response.ErrorListener() {

@Override

public void onErrorResponse(VolleyError error) {

showlog(error.getMessage());

}

})

3)发送请求

mQueue.add(stringRequest);

 

二、Volley总体设计

 

Volley 的调用比较简单,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue后,只需要往这个RequestQueue不断 add Request 即可。 

Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。
Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest都是它的子类,表示某种类型的请求。 
RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。 

CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。 

NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。 

ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。 

HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。 

Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。 

Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

三、Volley类图

 

 

 

上面的类图我们采用从上往下,从左往右的顺利进行:

1、用户通过Volley对象新建一个RequestQueue,然后再创建一个Request的请求方式;(通过Request下面有继承的几种请求方式,我们完全可以通过继承Request定义自己的请求方式,比如XML方式)

2、RequestQueue被初始化后会分别启动NetworkDispatch线程和CacheDispatch线程;

3、NetworkDispatch线程将用户的Request请求通过Network接口去启用具体的Http请求;(BaseNetwork实现Network接口,并且调用HttpStack接口)

4、HttpStack接口下面有HttpURLConnectionHttpClient方法的进一步封装;

5、通过用户设置的HTTP方式最后调用HttpURLConnection或者HttpClient,并得到网络的返回值;

6、返回值最终由NetworkDispatch去进行处理,如果用户没有设置缓存,则直接交给ResponseDelivery接口去处理,最后通过Response里面的接口回调给用户类去处理;如果用户设置了缓存,该请求和回复会被放入缓存队列,此时CacheDispatch线程检测到缓存队列有数据后会进行相应的缓存处理。

7、DiskBasedCacheCache接口实现的一种方式,主要用于文件的缓存(下面我们会进行详细的介绍),当然用户也可以自己定义缓存的方式,则只需要实现Cache接口即可,DiskBasedCache线程处理完返回消息后,还是交给esponseDelivery接口去处理,最后通过Response里面的接口回调给用户类去处理。

四、Volley DiskBasedCache

DiskBasedCache里面主要的两个方法是putget方法。

/**
 * 把缓存数据Entry写进磁盘里
 */
@Override
public synchronized void put(String key, Entry entry) {
    //判断是否有足够的缓存空间来缓存新的数据
    pruneIfNeeded(entry.data.length);

    File file = getFileForKey(key);
    try {
        FileOutputStream fos = new FileOutputStream(file);
        //用enry里面的数据,再封装成一个CacheHeader
        CacheHeader e = new CacheHeader(key, entry);
        //先写头部缓存信息
        boolean success = e.writeHeader(fos);
        if (!success) {
            fos.close();
            VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
            throw new IOException();
        }
        //成功后再写缓存内容
        fos.write(entry.data);
        fos.close();
        //把头部信息先暂时保存在一个容器里
        putEntry(key, e);
        return;
    } catch (IOException e) {
    }
    boolean deleted = file.delete();
    if (!deleted) {
        VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
    }
}


每次调用put方法时,也就是写入缓存时,都先调用pruneIfNeeded()检查对象的大小,当缓冲区空间足够新对象的加入时就直接添加进来,否则会删除部分对象,一直到新对象添加进来后还会有10%的空间剩余时为止,文件引用以LinkHashMap保存。添加时,首先以URLkey,经过个文本转换后,以转换后的文本为名称,获取一个file对象。首先向这个对象写入缓存的头文件,然后是真正有用的网络返回数据。最后是当前内存占有量数值的更新,这里需要注意的是真实数据被写入磁盘文件后,在内存中维护的应用,存的只是数据的相关属性。

public synchronized Entry get(String key) {
    CacheHeader entry = mEntries.get(key);
    // 如果entry不为空,就直接返回
    if (entry == null) {
        return null;
    }

    File file = getFileForKey(key);
    CountingInputStream cis = null;
    try {
        cis = new CountingInputStream(new FileInputStream(file));
        CacheHeader.readHeader(cis); // eat header
        byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
        return entry.toCacheEntry(data);
    } catch (IOException e) {
        VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
        remove(key);
        return null;
    } finally {
        if (cis != null) {
            try {
                cis.close();
            } catch (IOException ioe) {
                return null;
            }
        }
    }
}


每次调用get方法时,先从文件里获得字节数输入流,从中减去头部文件的字节数,最后把真正内容的data[]数据拿到再组装成一个Cache.Entry返回。从上面的分析可见,cache在做一些基础判断时都会先用到缓存的头部数据,如果确定头部信息没问题了,再真正读写内容,原因是头部数据比较小,放在内存中也不占地方,但处理速度会快很多。而真正的数据内容,可能会比较大,处理的开销也大,只在真正需要的地方读写。

 

public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        //请求只能在主线程里,不然会报错
        throwIfNotOnMainThread();
        //用url和宽高组成key
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

        //从内存缓存里获取数据
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // 如果内存不为空,直接返回图片信息
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        ...
        // 如果为空,就正常请求网络数据,下面用的是ImageRequest取请求网络数据
        Request<?> newRequest =
            new ImageRequest(requestUrl, new Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap response) {
                   //请求成功后,在这个方法里,把图片放进内存缓存中
                    onGetImageSuccess(cacheKey, response);
                }
            }, maxWidth, maxHeight,
            Config.RGB_565, new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    onGetImageError(cacheKey, error);
                }
            });
     ...
    }

    private void onGetImageSuccess(String cacheKey, Bitmap response) {
        //把图片放进内存里
        mCache.putBitmap(cacheKey, response);
        ...
    }

从上面的代码注释中已经能比较清晰的看出,每次调用ImageLoader.get()方法,会先从内存缓存里先看有没有数据,有就直接返回,没有就走正常的网络流程,先查看磁盘缓存,不存在或过期再去请求网络。图片比普通数据多一层缓存的原因也很简单,因为图片较大,读取和网络成本都大,能用缓存就用缓存,能省一点是一点。


五、HttpURLConnectionHttpClient

HttpClient:

    DefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具体的实现类,它们都拥有众多的API,而且实现比较稳定,bug数量也很少。

但同时也由于HttpClientAPI数量过多,使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以目前Android团队在提升和优化HttpClient方面的工作态度并不积极。

HttpURLConnection:

    HttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。虽然HttpURLConnectionAPI提供的比较简单,但是同时这也使得我们可以更加容易地去使用和扩展它。

HTTP 协议可能是现在 Internet上使用得最多、最重要的协议了,越来越多的 Java应用程序需要直接通过 HTTP协议来访问网络资源。在 JDKjava.net包中已经提供了访问 HTTP协议的基本功能:HttpURLConnection 

 

HttpURLConnectionjava的标准类,HttpURLConnection继承自URLConnection,可用于向指定网站发送GET请求、POST请求。它在URLConnection的基础上提供了如下便捷的方法: 

int getResponseCode():获取服务器的响应代码。

String getResponseMessage():获取服务器的响应消息。

String getResponseMethod():获取发送请求的方法。

void setRequestMethod(String method):设置发送请求的方法。

在一般情况下,如果只是需要Web站点的某个简单页面提交请求并获取服务器响应,HttpURLConnection完全可以胜任。但在绝大部分情况下,Web站点的网页可能没这么简单,这些页面并不是通过一个简单的URL就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况下,就需要涉及SessionCookie的处理了,如果打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,只是处理起来难度就大了。

为了更好地处理向Web站点请求,包括处理SessionCookie等细节问题,Apache开源组织提供了一个HttpClient项目,看它的名称就知道,它是一个简单的HTTP客户端(并不是浏览器),可以用于发送HTTP请求,接收HTTP响应。但不会缓存服务器的响应,不能执行HTML页面中嵌入的Javascript代码;也不会对页面内容进行任何解析、处理。

简单来说,HttpClient就是一个增强版的HttpURLConnectionHttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection没有提供的有些功能,HttpClient也提供了,但它只是关注于如何发送请求、接收响应,以及管理HTTP连接。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值