Volley 解析

Volley

Volley 原理
Request流程

Request处理流程

RequestQueue类中有三个主要的队列。调用RequestQueue.add(request)加入的请求会先加入mCacheQueue(优先级阻塞队列)由CacheDispatcher( 循环读取队列中的请求,当没有请求处理时线程阻塞)线程处理。如果该请求之前已经被缓存,读取缓存返回给主线程结果。否则将请求加入mNetworkQueue由NetworkDispatcher线程处理。由于处理网络请求比较耗时,NetworkDispatcher线程默认开启四个。每个线程都循环从mNetworkQueue读取请求,执行HTTP请求并解析服务器返回结果,将结果存入缓存,同时向通过handler回调到主线程,向用户返回结果。
如果该请求不是第一次处理,在进入mCacheQueue之前,可能回被加入mWaitingRequests(如果有相同url的请求正在处理)。作用是避免重复的请求多次执行,提高请求速度。当请求处理完之后,会检查mWaitingRequests是否有等待的请求,并全部加入缓存队列。

mCacheQueue :PriorityBlockingQueue<Request<?>>
mNetworkQueue:PriorityBlockingQueue<Request<?>>
mWaitingRequests: Map<String,Queue<Request<?>>

如何判断缓存是否过期

Expires首部和Cache-Control:max-age首部都是来告诉缓存文档有没有过期。Volley提供的解析类HttpHeaderParser在解析HTTP返回结果时,会从返回头中获取Expires和Cache-Control:max-age相关信息并存储在缓存数据中。如果没有过期还要判断是否需要刷新。一般如果数据没有过期是不需要刷新的。但是如果返回头中包含stale-while-revalidate会将数据提交给用户后请求刷新数据。在实现Request类的抽象方法parseNetworkResponse时,用户必须调用HttpHeaderParser.parseCacheHeaders解析返回头,否则无法判断缓存是否过期。

执行网络请求

如果sdk版本大于9使用HttpURLConnection执行网络请求,否则使用HttpClient。由BasicNetwork的
performRequest方法执行网络请求。在performRequest会调用Request类的getHeaders获取请求头。
如果服务器返回的响应头中包含Last-Modified或ETag,在执行下次请求时会在请求头中加入If-None-Match或If-Modified-Since。
如果服务器上的内容没有改变,会返回304状态,不会返回内容。内容从缓存获取。

自定义Request

这里写图片描述

Request类是一个泛型类,其中T用户期望带到的数据类型。Volley已经提供了StringRequest、ImageRequest、JosnObjectRequest等可以将服务器返回的数据解析为String、Image JSONObject。但是这些Request有时候不能满足用户的需求。比如如果服务器返回
Xml格式的数据,服务器返回的Josn用户不想解析为JosnObject。可以非常方便的对Volley的Request进行扩展。主要是继承Request,重写parseNetworkResponse和deliverResponse方法在parseNetworkResponse中将返回二进制数据转化为需要的数据格式。同时调用HttpHeaderParser.parseCacheHeaders得到缓存数据。在deliverResponse中调用Listenner返回结果。
如果要更改Http的请求头或者在post方法中需要提供数据需要重写getHeaders和getParams方法。

Request实现了Comparable,存储Request使用的是优先级队列。可以对Reqest设定优先级。有LOW,NORMAL,HIGH,IMMEDIATE四中优先级。默认使用NORMAL,ImageRequest使用的是LOW。

public class GsonRequest<T> extends Request<T> {  
private final Listener<T> mListener;  
private Gson mGson;  
private Class<T> mClass;  
public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,  
        ErrorListener errorListener) {  
    super(method, url, errorListener);  
    mGson = new Gson();  
    mClass = clazz;  
    mListener = listener;  
}  
public GsonRequest(String url, Class<T> clazz, Listener<T> listener,  
        ErrorListener errorListener) {  
    this(Method.GET, url, clazz, listener, errorListener);  
}  
@Override  
protected Response<T> parseNetworkResponse(NetworkResponse response) {  
    try {  
        String jsonString = new String(response.data,  
                HttpHeaderParser.parseCharset(response.headers));  
        return Response.success(mGson.fromJson(jsonString, mClass),  
                HttpHeaderParser.parseCacheHeaders(response));  
    } catch (UnsupportedEncodingException e) {  
        return Response.error(new ParseError(e));  
    }  
}  
@Override  
protected void deliverResponse(T response) {  
    mListener.onResponse(response);  
}  
}  

NetworkImageView 加载图片

NetworkImageView必须配合ImageLoader使用。ImageLoader可以根据url异步加载图片。ImageLoader内部使用ImageRequest加载图片,同时对会把相同的请求进行合并减少请求次数。多个NetworkImageView对应一个ImageLoader。
如果有多个NetworkImageView同时请求同一个图片,ImageLoader只会执行一次网络请求。
ImageLoader内部定义了一个接口,该接口的实现由用户提供。该接口用来缓存图片,一般的实现都使用了LRU算法。

public class BitmapLruCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache {
public static final int ONE_MB = 1048576;
public BitmapLruCache(Context context) {
    super(1048576 * ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() / 10);
}
protected int sizeOf(String key, Bitmap value) {
    return Build.VERSION.SDK_INT >= 12?value.getByteCount():value.getRowBytes() * value.getHeight();
}
public Bitmap getBitmap(String url) {
    return (Bitmap)this.get(url);
}
public void putBitmap(String url, Bitmap bitmap) {
    this.put(url, bitmap);
}
}

加上Volley提供的磁盘缓存,NetworkImageView加载图片使用了两级缓存。

  1. 创建ImageLoader
    mLoader = new ImageLoader(Volley.newRequestQueue(context), new BitmapLruCache(context));

  2. 加载图片
    imageView.setImageUrl(girl.getImg(), mLoader);

还可以通过imageView.setDefaultImageResId()来设置图片未加载时显示的默认图片,通过imageView.setErrorImageResId()来设置图片加载失败显示的图片。NetworkImageView会在url地址改变之后会及时取消之前的加载。

Volley 缓存

Volley默认会缓存请求结果。缓存存放在应用的volley目录。每个请求结果对应一个文件。文件名由url的hash码得到。默认缓存大小为5M。没有提供更改缓存大小的接口。如果更改大小必须更改代码。缓存采用LUR算法。内部主要由LinkedHashMap实现。LinkedHashMap内部有一个双向链表和一个HashMap,链表用来保存元素的存储顺序,MAP用来快速存取元素。
private final Map<String, CacheHeader> mEntries =new LinkedHashMap<String, CacheHeader>(16, .75f, true)
必须将LinkedHashMap的第三个参数设为true。LinkedHashMap才会按访问顺序排序(最少被访问的entry靠前,最近访问的entry靠后)。
每次在加入元素时,都会判断缓存大小是否超过5M,如果超过调用pruneIfNeeded将最老的元素移除。缓存文件时,会缓存服务器返回的http头和body。

Retrofit

Retrofit注解

Retrofit的Annotation包含请求方法相关的@GET、@POST、@HEAD、@PUT、@DELETA、@PATCH,和参数相关的@Path、@Field、@Multipart等。

  • 通过注解指定请求url,方法和参数
    @GET("group/{id}/users")
    Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);
  • 指定请求头
    @Headers("Cache-Control: max-age=640000")
    @GET("widget/list")
    Call<List<Widget>> widgetList();
  • 指定field
    @FormUrlEncoded
    @POST("user/edit")
    Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last)
  • 指定Http body
    @POST("users/new")
    Call<User> createUser(@Body User user);//必须有对应的Converter

    通用的header可以在okhttp的Interceptors中设定。

使用步骤

1.定义接口
public interface Api {
@GET("tnfs/api/list")
Call<Gallery> getList(@Query(ID) int id, @Query(PAGE) int page, @Query(ROWS) int rows);
}

2.生成对象

public static  Api getApi(){    
Retrofit retrofit=new Retrofit.Builder().baseUrl(BASEURL)   //设置域名 
            .addConverterFactory(GsonConverterFactory.create())//增加Converter
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//与RxJava整合时需要添加
            .build();    
    return  retrofit.create(Api.class);    
}

Converter用于将请求结果转化成需要的数据,如GsonConverter将JSON请求结果用Gson解析成Java对象,Retrofit提供的Converter

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

3.调用接口
异步调用

NetworkApi.getApi().getList(mId,1,8).enqueue(new Callback<Gallery>() {
@Override
public void onResponse(Response<Gallery> response, Retrofit retrofit) {
        mAdapter.setItemList(response.body().getList());
}
 @Override
 public void onFailure(Throwable t) {
     }
});

同步调用
Callery gallery= NetworkApi.getApi().getList(mId,1,8).excute();

Retrofit + RxJava

方法的返回类型定义为Observable
Observable<Gallery> getList(@Query(ID) int id, @Query(PAGE) int page, @Query(ROWS) int rows);
需要增加addCallAdapterFactory(RxJavaCallAdapterFactory.create()才可以识别Observable接口。

NetworkApi.getApi().getList(mId,1,8).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Gallery>() {
    @Override
    public void onCompleted() {}
    @Override
    public void onError(Throwable e) {}
    @Override
    public void onNext(Gallery gallery) {}
});

实现原理

使用OKHTTP执行网络请求
使用动态代理为接口生成实现类。

代理模式介绍

proxy.png

主要作用
1. 方法增强
你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情。比如,比如可以添加调用日志,做事务控制等。
2. 远程调用
Android中的跨进程通信。
服务端为实现类,客户端为代理类。实现相同的接口但是代理类并不需要实现接口定义的功能。而是使用binder机制向服务端发起请求,服务端返回结果给客户端。服务端和客户端还可以跨网络。

动态代理

为某个类自动产生代理类。该类必须实现一个接口。
原理
在运行时自动产生代码并进行编译,然后用classloader加载字节码生成Class类,用反射调用Class类的构造函数生成对象。

public class CachedProviderHandler implements InvocationHandler {
private Map<String, Object> cached = new HashMap<>();
private Object target;

public CachedProviderHandler(Object target) {
    this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
    Type[] types = method.getParameterTypes();
    if (method.getName().matches("get.+") && (types.length == 1) &&
            (types[0] == String.class)) {
        String key = (String) args[0];
        Object value = cached.get(key);
        if (value == null) {
            value = method.invoke(target, args);
            cached.put(key, value);
        }
        return value;
    }
    return method.invoke(target, args);
}
}

public abstract class ProviderFactory {
public static FontProvider getFontProvider() {
    Class<FontProvider> targetClass = FontProvider.class;
    return (FontProvider) Proxy.newProxyInstance(targetClass.getClassLoader(),
        new Class[] { targetClass },
        new CachedProviderHandler(new FontProviderFromDisk()));
}
}

如何自动产生代码

// 假设需代理接口 Simulator 
public interface Simulator { 
short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;
} 

// 假设代理类为 SimulatorProxy, 其类声明将如下
final public class SimulatorProxy implements Simulator { 

// 调用处理器对象的引用
protected InvocationHandler handler; 

// 以调用处理器为参数的构造函数
public SimulatorProxy(InvocationHandler handler){ 
    this.handler = handler; 
} 

// 实现接口方法 simulate 
public short simulate(int arg1, long arg2, String arg3) 
    throws ExceptionA, ExceptionB {

    // 第一步是获取 simulate 方法的 Method 对象
    java.lang.reflect.Method method = null; 
    try{ 
        method = Simulator.class.getMethod( 
            "simulate", 
            new Class[] {int.class, long.class, String.class} );
    } catch(Exception e) { 
        // 异常处理 1(略)
    } 

    // 第二步是调用 handler 的 invoke 方法分派转发方法调用
    Object r = null; 
    try { 
        r = handler.invoke(this, 
            method, 
            // 对于原始类型参数需要进行装箱操作
            new Object[] {new Integer(arg1), new Long(arg2), arg3});
    }catch(Throwable e) { 
        // 异常处理 2(略)
    } 
    // 第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
    return ((Short)r).shortValue();
} 
}

Volley vs Retrofit

  • Retrofit更好用
  • Volley提供了加载图片的支持
  • Volley有缓存,Retrofit可以借助Okhttp提供缓存
  • Volley由于只有四个线程,缓存默认只有5M不适合大文件处理

参考

http://gank.io/post/560e15be2dca930e00da1083#toc_26
给 Android 开发者的 RxJava 详解
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
Java 动态代理机制分析及扩展
http://square.github.io/retrofit/
Retrofit官网

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值