1. 程式人生 > >Android中 網路框架Volley的用法

Android中 網路框架Volley的用法

Volley是在Google I/O 2013上釋出的一框網路通訊http模組,新版本的Android已經廢除了HttpClient的使用,目前主流的android網路通訊庫有:Async-Http、NoHttp、xUtil等。本文就自己學習了Volley一些相關知識,基於Volley的部分原始碼分析,簡單描述一下Volley的用法,主要從以下幾點進行介紹:

  1. Volley原始碼淺析
  2. Volley使用教程
  3. 總結

1、Volley原始碼淺析:
首先,我們先看下Volley.java的整個類程式碼:

package com.android.volley.toolbox;

import
android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.net.http.AndroidHttpClient; import android.os.Build; import com.android.volley.Network; import com.android.volley.RequestQueue; import java.io.File; public
class Volley { /** Default on-disk cache directory. */ private static final String DEFAULT_CACHE_DIR = "volley"; /** * 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; } /** * 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); } }

其實可以發現,整個類並不龐大,不到100行的程式碼量,當我們使用Volley時,我們呼叫了newRequestQueue(Context context)這個方法來初始化一個請求佇列,該方法在原始碼中呼叫的是newRequestQueue(Context context, HttpStack stack)這塊主體的程式碼塊。
該方法的前面聲明瞭快取路徑,接下來初始化了HttpStack:

if (Build.VERSION.SDK_INT >= 9) {
   stack = new HurlStack();
} 

進入HurlStack中我們看下它主要的方法:

@Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        response.setEntity(entityFromConnection(connection));
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

就是涉及跟網路通訊互動的方式,基於了HttpURLConnection 的實現。回到Volley類中,接下來就是這幾句程式碼:

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

我們的請求佇列就是在這裡生成的。這邊就涉及到了RequestQueue這個類,我們還是先看下這個類的構造方法吧,下面方法塊是最終在類內部呼叫的最終構造方法:

/**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     * @param delivery A ResponseDelivery interface for posting responses and errors
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

那我們知道了,初始化queue主要跟以下幾個物件有關:Cache 、Network 、ResponseDelivery 、NetworkDispatcher、CacheDispatcher

之後便是queue.start()方法:

/**
     * Starts the dispatchers in this queue.
     */
    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();
        }
    }

至此,便返回一個請求佇列,完成queue的初始化工作。

最後,我們這個佇列可以新增request,完成訪問網路的工作。我們也還是貼下add方法的原始碼進行看下先:

/**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public Request add(Request request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        //設定序列號,按順序執行請求
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
         // 表示這個請求可以去先去快取中獲取資料。
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }

當mCacheQueue或者mNetworkQueue的add方法新增請求之後,我們執行的執行緒就會接收到請求,從而去處理相對應的請求,最後將處理的結果由mDelivery來發送到主執行緒進行更新。到這裡,我們的請求就會在快取執行緒或者網路執行緒中去處理了,在以上步驟完成之後,request會呼叫自身的finish()方法,表示結束整個請求:

/**
     * Called from {@link Request#finish(String)}, indicating that processing of the given request
     * has finished.
     *
     * <p>Releases waiting requests for <code>request.getCacheKey()</code> if
     *      <code>request.shouldCache()</code>.</p>
     */
    void finish(Request request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

幹完了原始碼分析的工作,接下來我們就實際操刀一下:

2、使用教程:

1、AndroidManifest中新增訪問網路的許可權,不用說都知道。

2、引入Volley包。這裡大家可以去搜一下。

3、程式碼編寫。

按我自己的程式碼風格,喜歡先封裝一下VolleyUtil的工具類,寫了部分程式碼,不打完善,大家可以自行補充。:VolleyUtil .java



import android.content.Context;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;

/**
 * 作者:viviant on 2016/6/30 14:08
 * 描述:Volley封裝類
 */
public class VolleyUtil {

    private static RequestQueue mQueue; // volley的請求佇列
    private static VolleyUtil instance;
    private static Context context;

    private VolleyUtil(Context context) {
        this.context = context;
    }

    public static VolleyUtil getInstance() {
            synchronized (VolleyUtil.class) {
                if (instance == null) {
                    instance = new VolleyUtil(context);
                }
            }
        return instance;
    }

    /**
     * get方式請求
     */
    public void get(Context context, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
        mQueue = Volley.newRequestQueue(context);
        mQueue.add(new MyStringRequest(Request.Method.GET, url, listener, errorListener));
    }

    /**
     * get方式請求
     */
    public void get(Context context, String url, JSONObject jsonObject, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
        mQueue = Volley.newRequestQueue(context);
        mQueue.add(new JsonObjectRequest(Request.Method.GET, url, jsonObject, listener, errorListener));
    }

    /**
     * post方式請求
     */
    public void post(Context context, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
        mQueue = Volley.newRequestQueue(context);
        mQueue.add(new MyStringRequest(Request.Method.POST, url, listener, errorListener));
    }

    /**
     * post方式請求
     */
    public void put(Context context, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
        mQueue = Volley.newRequestQueue(context);
        mQueue.add(new MyStringRequest(Request.Method.PUT, url, listener, errorListener));
    }

    /**
     * post方式請求
     */
    public void delete(Context context, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
        mQueue = Volley.newRequestQueue(context);
        mQueue.add(new MyStringRequest(Request.Method.DELETE, url, listener, errorListener));
    }


    /**
     * get by tag方式請求
     */
    public void getByTag(Context context, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, Object tag) {
        mQueue = Volley.newRequestQueue(context);
        MyStringRequest request = new MyStringRequest(Request.Method.GET, url, listener, errorListener);
        request.setTag(tag);
        mQueue.add(request);

    }

    /**
     * 根據tag取消請求
     * @param tag
     */
    public void cancleRequests(Object tag) {
        if (mQueue != null) {
            mQueue.cancelAll(tag);
        }
    }

    /**
     * 取消所有的請求
     */
    public void onStop() {
        if (mQueue != null) {
            mQueue.cancelAll(new RequestQueue.RequestFilter() {
                @Override
                public boolean apply(Request<?> request) {
                    // do I have to cancel this?
                    return true; // -> always yes
                }
            });
        }
    }

    public class MyStringRequest extends StringRequest {

        public MyStringRequest(int method, String url, Response.Listener<String> listener,Response.ErrorListener errorListener) {
            super(method, url, listener, errorListener);
        }

        /**
         * 重寫以解決亂碼問題
         */
        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String str = null;
            try {
                str = new String(response.data, "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return Response.success(str,  HttpHeaderParser.parseCacheHeaders(response));
        }
    }
}

以上也包含了一個填坑操作,我們使用StringRequest 時返回來的資料會亂碼,這邊我在網上查了別人的解決方案,重寫了一個類。

下面是我的二次封裝程式碼:VolleyNetManager.java


import android.content.Context;
import android.util.Log;

import com.android.volley.Response;
import com.android.volley.VolleyError;

import org.json.JSONObject;

import viviant.cn.weeklyplan.common.util.VolleyUtil;

/**
 * 作者:viviant on 2016/6/30 14:18
 * 描述:
 */
public class VolleyNetManager {

    /**
     * 根據標籤取消請求
     * @param tag
     */
    public static void cancleRequests (Object tag) {
        VolleyUtil.getInstance().cancleRequests(tag);
    }

    /**
     * 取消所有請求
     */
    public static void cancleAllRequests () {
        VolleyUtil.getInstance().onStop();
    }

    /**
     * Volley Get 方法測試方法
     */
    public static void TestVolleyGet(Context context, String url) {
        VolleyUtil.getInstance().get(context, url,new Response.Listener<String>() {

            @Override
            public void onResponse(String arg0) {
//                Toast.makeText(getActivity(), arg0, Toast.LENGTH_LONG).show();
                Log.d("weiwei", arg0);
            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError arg0) {
//                Toast.makeText(getActivity(), arg0.toString(), Toast.LENGTH_LONG).show();
                Log.d("weiwei", "error : " + arg0.toString());
            }
        });
    }

    /**
     * Volley Get JSONObject 方法測試方法
     */
    public static void TestVolley(Context context, String url) {
        VolleyUtil.getInstance().get(context, url, null, new Response.Listener<JSONObject>() {

            @Override
            public void onResponse(JSONObject jsonObject) {
//                Toast.makeText(getActivity(), arg0, Toast.LENGTH_LONG).show();
                Log.d("weiwei", jsonObject.toString());
            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError arg0) {
//                Toast.makeText(getActivity(), arg0.toString(), Toast.LENGTH_LONG).show();
                Log.d("weiwei", "error : " + arg0.toString());
            }
        });
    }


    /**
     * VolleyPost方法測試方法
     */
    public static void TestVolleyPost(Context context, String url) {

        VolleyUtil.getInstance().post(context, url, new Response.Listener<String>() {

            @Override
            public void onResponse(String arg0) {
//                Toast.makeText(getActivity(), arg0, Toast.LENGTH_LONG).show();
                Log.d("weiwei", arg0);
            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError arg0) {
//                Toast.makeText(getActivity(), arg0.toString(), Toast.LENGTH_LONG).show();
                Log.d("weiwei", "error : " + arg0.toString());
            }
        });
    }
}

我們在activity中可以呼叫:

private static final String URL = "http://www.baidu.com/";
VolleyNetManager.TestVolleyGet(getContext(), URL);

然後就可以在VolleyNetManager 得到我們返回來的資料。

3、總結:

這邊畫了個草圖,梳理下RequestQueue的主要方法:
這裡寫圖片描述

總得來說,volley是一個很優秀的框架,比較簡潔。好歹是谷歌自己出的。
以上是我對自己使用時做的一個小小的總結,有不好的地方歡迎提出意見。