Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Volley 解析

Volley 解析

編輯:關於Android編程

Volley 原理

Request流程 <喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="request處理流程">Request處理流程

RequestQueue類中有三個主要的隊列。調用RequestQueue.add(request)加入的請求會先加入mCacheQueue(優先級阻塞隊列)由CacheDispatcher( 循環讀取隊列中的請求,當沒有請求處理時線程阻塞)線程處理。如果該請求之前已經被緩存,讀取緩存返回給主線程結果。否則將請求加入mNetworkQueue由NetworkDispatcher線程處理。由於處理網絡請求比較耗時,NetworkDispatcher線程默認開啟四個。每個線程都循環從mNetworkQueue讀取請求,執行HTTP請求並解析服務器返回結果,將結果存入緩存,同時向通過handler回調到主線程,向用戶返回結果。
如果該請求不是第一次處理,在進入mCacheQueue之前,可能回被加入mWaitingRequests(如果有相同url的請求正在處理)。作用是避免重復的請求多次執行,提高請求速度。當請求處理完之後,會檢查mWaitingRequests是否有等待的請求,並全部加入緩存隊列。

mCacheQueue :PriorityBlockingQueue>
mNetworkQueue:PriorityBlockingQueue>
mWaitingRequests: Map>

如何判斷緩存是否過期

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 extends Request {  
private final Listener mListener;  
private Gson mGson;  
private Class mClass;  
public GsonRequest(int method, String url, Class clazz, Listener listener,  
        ErrorListener errorListener) {  
    super(method, url, errorListener);  
    mGson = new Gson();  
    mClass = clazz;  
    mListener = listener;  
}  
public GsonRequest(String url, Class clazz, Listener listener,  
        ErrorListener errorListener) {  
    this(Method.GET, url, clazz, listener, errorListener);  
}  
@Override  
protected Response 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 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加載圖片使用了兩級緩存。

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

加載圖片
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 mEntries =new LinkedHashMap(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> groupList(@Path("id") int groupId, @Query("sort") String sort); 指定請求頭
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call> widgetList(); 指定field
@FormUrlEncoded
@POST("user/edit")
Call updateUser(@Field("first_name") String first, @Field("last_name") String last)

指定Http body
@POST("users/new")
Call createUser(@Body User user);//必須有對應的Converter

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

使用步驟

1.定義接口
public interface Api {
@GET("tnfs/api/list")
Call 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() {
@Override
public void onResponse(Response 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 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() {
    @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 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 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不適合大文件處理。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved