1. 程式人生 > >Android之對Volley網路框架的一些理解

Android之對Volley網路框架的一些理解

##前言
Volley這個網路框架大家並不陌生,它的優點網上一大堆, 適合網路通訊頻繁操作,並能同時實現多個網路通訊、擴充套件性強、通過介面配置之類的優點。在寫這篇文章之前,特意去了解並使用Volley這個網路框架,文章是對Volley的一點點理解,如有寫得不到位的地方,歡迎大家指出。

##用法
使用Volley的通用步驟就是通過Volley暴露的newRequestQueue方法,建立的我們的RequestQueue,接著往我們的RequestQueue中新增Request( StringRequest、JsonRequest、ImageRequest,以及你自己定義的Request)。

private void initNetWork() {
        RequestQueue queue = Volley.newRequestQueue(this);
        String url = "";
        StringRequest stringRequest = new StringRequest(Request.Method.GET,
                url, new Response.Listener<String>() {

                    @Override
                    public void onResponse(String response) {

                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                });
        queue.add(stringRequest);
        queue.start();
 }

步驟很簡單,總結就三步:

  1. 建立一個RequestQueue物件。
  2. 建立一個StringRequest物件。
  3. 將StringRequest物件新增到RequestQueue裡面。

上面的例項通過get方式請求資料的,post的方式也是很簡單,在建立Request的時候,第一個引數改為Request.Methode.POST:

private void initNetWork_1() {
        RequestQueue queue = Volley.newRequestQueue(this);
        String url = "";
        StringRequest stringRequest = new StringRequest(Request.Method.POST,
                url, new Response.Listener<String>() {

                    @Override
                    public void onResponse(String response) {

                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                }) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                // TODO Auto-generated method stub
                return super.getParams();
            }
        };
        queue.add(stringRequest);
        queue.start();
}

仔細檢視上面POST和GET的不同方式的請求程式碼,可以看出POST的時候多了一個getParams方法,返回的Map型別,getParams獲取的資料是用於向伺服器提交的引數。
在這裡我們看到,如果每次都去定義Map,然後往裡面put鍵值對,這是一件很沮喪的事情,這裡給出一個工具類,可以通過解析物件轉換成我們需要的Map,程式碼如下:

package com.example.volleyproject;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class MapUtil {

    public static <T> Map<String, String> changeTtoMap(T m) {
        HashMap<String, String> map = new HashMap<String, String>();
        // 獲取實體類的所有屬性
        Field[] field = m.getClass().getDeclaredFields();
        // 遍歷所有屬性
        for (int j = 0; j < field.length; j++) {
            // 獲取屬性的名字
            String name = field[j].getName();

            String value = (String) getFieldValueObj(m, name);
            if (value != null && !value.equals("")) {
                map.put(name, value);
            }
        }
        return map;
    }

    /**
     * 獲取對應的屬性值
     *
     * @param target
     *            物件
     * @param fname
     *            Filed
     * @return
     */
    public static Object getFieldValueObj(Object target, String fname) { // 獲取欄位值
        // 如:username 欄位,getUsername()
        if (target == null || fname == null || "".equals(fname)) {// 如果型別不匹配,直接退出
            return "";
        }
        Class clazz = target.getClass();
        try { // 先通過getXxx()方法設定類屬性值
            String methodname = "get" + Character.toUpperCase(fname.charAt(0))
                    + fname.substring(1);
            Method method = clazz.getDeclaredMethod(methodname); // 獲取定義的方法
            if (!Modifier.isPublic(method.getModifiers())) { // 設定非共有方法許可權
                method.setAccessible(true);
            }
            return (Object) method.invoke(target); // 執行方法回撥
        } catch (Exception me) {// 如果get方法不存在,則直接設定類屬性值
            try {
                Field field = clazz.getDeclaredField(fname); // 獲取定義的類屬性
                if (!Modifier.isPublic(field.getModifiers())) { // 設定非共有類屬性許可權
                    field.setAccessible(true);
                }
                return (Object) field.get(target); // 獲取類屬性值
            } catch (Exception fe) {
            }
        }
        return "";
    }
}

最後在getParams 呼叫這個工具類中的changeTtoMap方法即可:

private void initNetWork_1() {
        RequestQueue queue = Volley.newRequestQueue(this);
        String url = "";
        final RequestParams params = new RequestParams();
        params.id = "1";
        params.name = "bill";
        StringRequest stringRequest = new StringRequest(Request.Method.POST,
                url, new Response.Listener<String>() {

                    @Override
                    public void onResponse(String response) {

                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                }) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                return MapUtil.changeTtoMap(params);
            }
        };
        queue.add(stringRequest);
        queue.start();
}

##Volley的淺談

所有操作都是基於上面講的三個步驟,第一步建立一個RequestQueue物件。

RequestQueue queue = Volley.newRequestQueue(this);

請求佇列(RequestQueue)不需要出現多個,因此我們可以在Application中進行初始化。

	 /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
 }

方法很簡單,建立的我們的請求佇列,這裡通過傳入Context來確定我們快取目錄。

	/**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @param stack An {@link HttpStack} to use for the network, or null for default.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

在Volley中,http的處理請求,預設 Android2.3 及以上基於 HttpURLConnection的 HurlStack,2.3 以下基於 HttpClient 的 HttpClientStack,通過上面的這段程式碼可知:

if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
   }

這裡面的HurlStack和HttpClientStack都實現了HttpStack這個介面,HttpStack是用於http請求,返回請求結果的。

public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
     *
     * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
     * and the Content-Type header is set to request.getPostBodyContentType().</p>
     *
     * @param request the request to perform
     * @param additionalHeaders additional headers to be sent together with
     *         {@link Request#getHeaders()}
     * @return the HTTP response
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}


public class HttpClientStack implements HttpStack {
}

public class HurlStack implements HttpStack{
}

在根據SDK版本確定使用哪個Http請求方式後建立BasicNetwork物件,BasicNetwork實現了Network介面:

/**
 * An interface for performing requests.
 */
public interface Network {
    /**
     * Performs the specified request.
     * @param request Request to process
     * @return A {@link NetworkResponse} with data and caching metadata; will never be null
     * @throws VolleyError on errors
     */
    public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

NetWork用於 呼叫HttpStack處理請求,並將結果轉換為可被ResponseDelivery處理的NetworkResponse。 這裡面的BasicNetwork就是一個具體是實現。

接著執行:

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();

DiskBasedCache是我們快取的目錄:

 public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
 }

 public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        mRootDirectory = rootDirectory;
        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
 }

這裡面定義了快取的地址,預設最大是5MB。繼續檢視RequestQueue的構造器:

public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
  }

  public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
  }

 public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
  }

在建立RequestQueue例項時進行了一系列的初始化(快取、HttpStack的處理請求、執行緒池大小、返回結果的分發)。
NetworkDispatcher是一個執行緒, 用於排程處理網路的請求。啟動後會不斷從網路請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理,並判斷結果是否要進行快取。

public class NetworkDispatcher extends Thread {
}

這裡面的ResponseDelivery用於返回結果分發介面,目前只有基於ExecutorDelivery的 handler 對應執行緒內進行分發。

ExecutorDelivery通過內部的handler對應的執行緒進行返回結果的分發:

public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
}

Executor框架是java 5引入的併發庫,把任務拆分為一些列的小任務,即Runnable,然後在提交給一個Executor執行,Executor.execute(Runnalbe) 。Executor在執行時使用內部的執行緒池完成操作。

這裡面傳遞進來的是new Handler(Looper.getMainLooper()),傳入UI執行緒的Handler的目的是用於UI更新,比如通過通過Volley進行圖片下載時的ImageView更新。

上面也提到過ResponseDelivery用於返回結果分發,目前只基於ExecutorDelivery的handler對應執行緒進行分發,由此我們找到資料分發的方法:

	@Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

在postResponse、postError都呼叫了Executor.execute(Runnalbe)方法進行併發執行,ResponseDeliveryRunnable實現了Runnable介面並重寫run方法:

	public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = runnable;
	}

   @Override
   public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

run方法中的程式碼比較簡單,進行資料的分發,這裡面的mResponse就是我們返回的結果,通過mRequest.deliverResponse(mResponse.result);進行資料分發,這裡面的Request是一個請求的抽象類,進入StringRequest類中,我們發現StringRequest實現了這個抽象類:

public class StringRequest extends Request<String>{
}

在StringRequest 中我們找到這麼一段程式碼:

	@Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

對比之前我們定義的StringRequest的程式碼:

StringRequest stringRequest = new StringRequest(Request.Method.GET,
                url, new Response.Listener<String>() {

                    @Override
                    public void onResponse(String response) {

                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
	  });

是不是很熟悉,最後結果就是通過ExecutorDelivery中handler對應執行緒進行分發處理的。
到這裡請求佇列(RequestQueue)就初始化完畢了。
接著就是定義我們的Request,Volley定義了現成的Request,像 StringRequest、JsonRequest、ImageReques,當然我們可以根據StringRequest一樣定義我們自己的Request,只需實現Request這個抽象類:

package com.android.volley.toolbox;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;

import java.io.UnsupportedEncodingException;

/**
 * A canned request for retrieving the response body at a given URL as a String.
 */
public class StringRequest extends Request<String> {
    private final Listener<String> mListener;

    /**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    /**
     * Creates a new GET request.
     *
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}

檢視StringRequest原始碼,重寫了以下兩個方法:

abstract protected void deliverResponse(T response);
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

NetworkResponse存放的是從伺服器返回並以位元組形式的資料。按照StringRequest我們可以定義GsonRequest:

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);
    }

}

到這裡為止我們的佇列以及請求響應都已定義好,最後通過請求佇列的start方法進行請求:

public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }


 public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

CacheDispatcher和NetworkDispatcher中的quit方法:

public void quit() {
        mQuit = true;
        interrupt();
 }

通過start方法對佇列進行排程,方法中的先通過stop進行快取和網路請求的排程終止,通過它們的quit方法中的interrupt方法進行執行緒中斷,CacheDispatcher是 一個執行緒,用於排程處理走快取的請求。啟動後會不斷從快取請求佇列中取請求處理,佇列為空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理。當結果未快取過、快取失效或快取需要重新整理的情況下,該請求都需要重新進入NetworkDispatcher去排程處理。
由此start方法中的處理邏輯就一目瞭然了,先是經過快取的請求,在未快取過或是快取失效時,再進入網路的請求,並判斷結果是否要進行快取。