Android Volley解析(一)之GET、POST请求篇

来源:互联网 发布:免费点播软件 编辑:程序博客网 时间:2024/04/30 08:58

一、 Volley 的地位

自2013年Google I/O 大会上,Google 推出 Volley 之后,一直到至今,由于其使用简单、代码轻量、通信速度快、并发量大等特点,倍受开发者们的青睐。
先看两张图,让图片告诉我们 Volley 的用处;
第一张 Volley 的经典图
技术分享
通过上图,我们可以发现 Volley适合网络通信频繁操作,并能同时实现多个网络通信。
第二张图
技术分享
我们在以前在 ListView 的 item 中如果有网络请求,一般都是通过Task 异步任务来完成,并在完成之后通知 Adapter 更新数据。而Volley 不需要这么麻烦,因为里面已经为我们封装好了处理的线程,网络请求,缓存的获取,数据的回掉都是对应不同的线程。

二、Volley使用步骤及基本分析

volley 的使用遵循以下四步:
1、获取请求队里RequestQueue
RequestQueue mRequestQueue = Vollay.newRequestQueue(Context context) ;
2、启动请求队列
mRequestQueue.start();
以上这两步通常也归为一步
3、获取请求Request 
Request mRequest = new ObjectRequest(…) ;
ObjectRequest需要根据自己请求返回的数据来定制,继承之抽象类Request,Vollay 已经为我们实现了 StringRequest、JsonArrayRequest、JsonObjectRequest、ImageRequest请求;
4、把请求添加到请求队列中
mRequestQueue.add(mRequest);
说明:在一个项目中,请求队列不需要出现多个,一般整个项目中共用同一个mRequestQueue,因为请求队列启动的时候会做以下事情

    /**     * 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.        //启动网络请求处理线程,默认为5个,可以自己设定 size        for (int i = 0; i < mDispatchers.length; i++) {            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,                    mCache, mDelivery);            //保存网络请求线程            mDispatchers[i] = networkDispatcher;            //启动网络请求处理线程            networkDispatcher.start();        }    }

启动一个缓存mCacheDispatcher线程,用来读取缓存数据,启动若干个网络请求mDispatchers线程,用来实现网络通信。
mCacheDispatcher线程的 run 方法

    @Override    public void run() {        if (DEBUG) VolleyLog.v("start new dispatcher");        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);        // Make a blocking call to initialize the cache.        //初始化缓存        mCache.initialize();        //循环获取缓存请求        while (true) {            try {                // Get a request from the cache triage queue, blocking until                // at least one is available.                //从缓存队列中获取缓存请求,如果没有缓存请求,这个方法会阻塞在这里                final Request request = mCacheQueue.take();                //打印 log 信息                request.addMarker("cache-queue-take");                // If the request has been canceled, don‘t bother dispatching it.                //如果请求终止了,结束本次循环                if (request.isCanceled()) {                    request.finish("cache-discard-canceled");                    continue;                }                // Attempt to retrieve this item from cache.               //获取缓存数据,如果没有,把请求加入到网络请求的队列中                Cache.Entry entry = mCache.get(request.getCacheKey());                if (entry == null) {                    request.addMarker("cache-miss");                    Log.i("CacheDispatcher", "没有缓存数据:" + request.getUrl());                    mNetworkQueue.put(request);                    continue;                }                // If it is completely expired, just send it to the network.                //判断缓存是否已经过期,如果过期,把请求加入到网络请求的队列中,直接请求网络获取数据                if (entry.isExpired()) {                    request.addMarker("cache-hit-expired");                    request.setCacheEntry(entry);                    Log.i("CacheDispatcher", "缓存数据过期:" + request.getUrl());                    mNetworkQueue.put(request);                    continue;                }                // We have a cache hit; parse its data for delivery back to the request.                // 已经获取到了有效的缓存数据,回调给 request 的parseNetworkResponse,需要自己根据需求来解析数据                request.addMarker("cache-hit");                Response<?> response = request.parseNetworkResponse(                        new NetworkResponse(entry.data, entry.responseHeaders));                request.addMarker("cache-hit-parsed");                //判断缓存是否需要刷新                if (!entry.refreshNeeded()) {                    // Completely unexpired cache hit. Just deliver the response.                    Log.i("CacheDispatcher", "获取缓存数据:" + request.getUrl());                    mDelivery.postResponse(request, response);                } else {                    // Soft-expired cache hit. We can deliver the cached response,                    // but we need to also send the request to the network for                    // refreshing.                    request.addMarker("cache-hit-refresh-needed");                    request.setCacheEntry(entry);                    // Mark the response as intermediate.                    response.intermediate = true;                    // Post the intermediate response back to the user and have                    // the delivery then forward the request along to the network.                    mDelivery.postResponse(request, response, new Runnable() {                        @Override                        public void run() {                            try {                                mNetworkQueue.put(request);                            } catch (InterruptedException e) {                                // Not much we can do about this.                            }                        }                    });                }            } catch (InterruptedException e) {                // We may have been interrupted because it was time to quit.                if (mQuit) {                    return;                }                continue;            }        }    }

mDispatchers线程的 run 方法

    @Override    public void run() {        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);        Request request;        while (true) {            try {                // Take a request from the queue.                //获取网络请求,当队列中为空的时候,阻塞                request = mQueue.take();            } catch (InterruptedException e) {                // We may have been interrupted because it was time to quit.                if (mQuit) {                    return;                }                continue;            }            try {                request.addMarker("network-queue-take");                // If the request was cancelled already, do not perform the                // network request.                if (request.isCanceled()) {                    request.finish("network-discard-cancelled");                    continue;                }                // Tag the request (if API >= 14)                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {                    TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());                }                // Perform the network request.                //网络请求的基本操作(核心操作),从网络中获取数据                NetworkResponse networkResponse = mNetwork.performRequest(request);                request.addMarker("network-http-complete");                // If the server returned 304 AND we delivered a response already,                // we‘re done -- don‘t deliver a second identical response.                if (networkResponse.notModified && request.hasHadResponseDelivered()) {                    request.finish("not-modified");                    continue;                }                // Parse the response here on the worker thread.                Response<?> response = request.parseNetworkResponse(networkResponse);                request.addMarker("network-parse-complete");                // Write to cache if applicable.                // TODO: Only update cache metadata instead of entire record for 304s.                //判断是否需要缓存,如果需要则缓存。                if (request.shouldCache() && response.cacheEntry != null) {                    mCache.put(request.getCacheKey(), response.cacheEntry);                    request.addMarker("network-cache-written");                }                // Post the response back.                request.markDelivered();                mDelivery.postResponse(request, response);            } catch (VolleyError volleyError) {                parseAndDeliverNetworkError(request, volleyError);            } catch (Exception e) {                VolleyLog.e(e, "Unhandled exception %s", e.toString());                mDelivery.postError(request, new VolleyError(e));            }        }    }

这两个线程处理类型基本相同,都是采用循环的方法,在队列中获取请求,有请求则执行相应的请求,没有则阻塞在下面两行代码中

//阻塞线程的执行//缓存线程阻塞的地方final Request request = mCacheQueue.take();//网络请求阻塞的地方request = mQueue.take();

所以我们一般只需要根据不同的接口,实例化不同的请求 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.take()会得到执行,当不符合要求的时候,请求会添加到真正的网络请求队列中,以下是不符合要求的代码

                //没有缓存                if (entry == null) {                    request.addMarker("cache-miss");                    Log.i("CacheDispatcher", "没有缓存数据:" + request.getUrl());                    mNetworkQueue.put(request);                    continue;                }                // If it is completely expired, just send it to the network.                //缓存已过期                if (entry.isExpired()) {                    request.addMarker("cache-hit-expired");                    request.setCacheEntry(entry);                    Log.i("CacheDispatcher", "缓存数据过期:" + request.getUrl());                    mNetworkQueue.put(request);                    continue;                }

如果缓存不符合要求,网络线程终止阻塞得到执行;
我们一般习惯用法是在 Application 中全局初始化RequestQueue mRequestQueue,并启动它,让整个应用都能获取到。具体运用将会在下面用到。

三、Volley 实战 GET 请求和 POST 请求

先来来看下测试的接口http://www.minongbang.com/test.php?test=minongbang
返回数据:
技术分享
这里 get 请求和 post 请求都是用同一个接口来测试,所以先把返回的基本数据类型定义出来

/** * Created by gyzhong on 15/3/3. */public class TestBean {    @Expose    private int id ;    @Expose    private String name ;    @Expose    private int download ;    @Expose    private int version ;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getDownload() {        return download;    }    public void setDownload(int download) {        this.download = download;    }    public int getVersion() {        return version;    }    public void setVersion(int version) {        this.version = version;    }}

1、GET 请求
第一步:在  Application 中初始化RequestQueue,

    //初始化请求队列    private void initRequestQueue(){        //初始化 volley        VolleyUtil.initialize(mContext);    }
/** * Created by gyzhong on 15/3/1. */public class VolleyUtil {    private static RequestQueue mRequestQueue ;    public static void initialize(Context context){        if (mRequestQueue == null){            synchronized (VolleyUtil.class){                if (mRequestQueue == null){                    mRequestQueue = Volley.newRequestQueue(context) ;                }            }        }        mRequestQueue.start();    }    public static RequestQueue getRequestQueue(){        if (mRequestQueue == null)            throw new RuntimeException("请先初始化mRequestQueue") ;        return mRequestQueue ;    }}

第二步:定制 Request
先来分析接口所返回的数据,我们看到是一条 json 数据,虽然 Volley 中已经为我们定制好了JsonObjectRequest请求,但我们知道,在数据具体显示的时候,是需要把 json 数据转化为对象进行处理,所以这里我们可以定制通用的对象请求。如何定制呢?
先看StringRequest的实现代码

//继承Request<String>,String 为请求解析之后的数据public class StringRequest extends Request<String> {    //正确数据回调接口    private final Listener<String> mListener;    public StringRequest(int method, String url, Listener<String> listener,            ErrorListener errorListener) {        super(method, url, errorListener);        mListener = listener;    }    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {        this(Method.GET, url, listener, errorListener);    }    //回调解析之后的数据    @Override    protected void deliverResponse(String response) {        mListener.onResponse(response);    }    //解析数据,把网络请求,或者中缓存中获取的数据,解析成 String    @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继承了 Request 并实现了两个抽象方法parseNetworkResponse()和 deliverResponse(),这两个方法很好理解,parseNetworkResponse()把获取到的数据解析成我们所定义的数据类型;deliverResponse()把所解析的数据通过回调接口回调给展示处。
为了简化回调接口,这里把错误回调Response.ErrorListener 和正确的数据回调Response.Listener合并成一个ResponseListener

/** * Created by gyzhong on 15/3/1. * 简化回调接口 */public interface ResponseListener<T> extends Response.ErrorListener,Response.Listener<T> {}

根据 StringRequest,如法炮制

/** * Created by gyzhong on 15/3/1. */public class GetObjectRequest<T> extends Request<T> {    /**     * 正确数据的时候回掉用     */    private ResponseListener mListener ;    /*用来解析 json 用的*/    private Gson mGson ;    /*在用 gson 解析 json 数据的时候,需要用到这个参数*/    private Type mClazz ;    public GetObjectRequest(String url,Type type, ResponseListener listener) {        super(Method.GET, url, listener);        this.mListener = listener ;        mGson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create() ;        mClazz = type ;    }    /**     * 这里开始解析数据     * @param response Response from the network     * @return     */    @Override    protected Response<T> parseNetworkResponse(NetworkResponse response) {        try {            T result ;            String jsonString =                    new String(response.data, HttpHeaderParser.parseCharset(response.headers));            result = mGson.fromJson(jsonString,mClazz) ;            return Response.success(result,                    HttpHeaderParser.parseCacheHeaders(response));        } catch (UnsupportedEncodingException e) {            return Response.error(new ParseError(e));        }    }    /**     * 回调正确的数据     * @param response The parsed response returned by     */    @Override    protected void deliverResponse(T response) {        mListener.onResponse(response);    }}

以上代码中在实例化 Gson 的时候用到的是mGson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation(),主要是用于过滤字段用的.如果有疑问的同学可以参考我前面写的一篇文章Gson 过滤字段的几种方法
第三步:获取 request

Request request = new GetObjectRequest(url,new TypeToken<TestBean>(){}.getType(),listener) ;

1、url -> http://www.minongbang.com/test.php?test=minongbang
2、new TypeToken(){}.getType() ->为 gson 解析 json 数据所要的 type
3、listener -> 为我们定义的ResponseListener回调接口
第四步:添加请求到队列中

VolleyUtil.getRequestQueue().add(request) ;

所以,此接口的代码即为

    /**     * Minong 测试数据get网络请求接口     * @param value 要搜索的关键字     * @param listener 回调接口,包含错误回调和正确的数据回调     */    public static void getObjectMiNongApi(String value,ResponseListener listener){        String url ;        try {            url = Constant.MinongHost +"?test="+ URLEncoder.encode(value, "utf-8") ;        } catch (UnsupportedEncodingException e) {            e.printStackTrace();            url = Constant.MinongHost +"?test="+ URLEncoder.encode(value) ;        }        Request request = new GetObjectRequest(url,new TypeToken<TestBean>(){}.getType(),listener) ;        VolleyUtil.getRequestQueue().add(request) ;    }

第五步:代码测试

public class GetRequestActivity extends ActionBarActivity {    /*数据显示的View*/    private TextView mIdTxt,mNameTxt,mDownloadTxt,mLogoTxt,mVersionTxt ;    /*弹出等待对话框*/    private ProgressDialog mDialog ;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_get);        mIdTxt = (TextView) findViewById(R.id.id_id) ;        mNameTxt = (TextView) findViewById(R.id.id_name) ;        mDownloadTxt = (TextView) findViewById(R.id.id_download) ;        mLogoTxt = (TextView) findViewById(R.id.id_logo) ;        mVersionTxt = (TextView) findViewById(R.id.id_version) ;        mDialog = new ProgressDialog(this) ;        mDialog.setMessage("get请求中...");        mDialog.show();        /*请求网络获取数据*/        MiNongApi.getObjectMiNongApi("minongbang",new ResponseListener<TestBean>() {            @Override            public void onErrorResponse(VolleyError error) {                mDialog.dismiss();            }            @Override            public void onResponse(TestBean response) {                mDialog.dismiss();                /*显示数据*/                mIdTxt.setText(response.getId()+"");                mNameTxt.setText(response.getName());                mDownloadTxt.setText(response.getDownload()+"");                mLogoTxt.setText(response.getLogo());                mVersionTxt.setText(response.getVersion()+"");            }        });    }}

测试效果图如下:
技术分享 技术分享
可以看到和我们在浏览器中请求的数据一模一样!

2、POST请求
因为在讲 get 请求的时候花了很大篇幅讲原理,所以在 post 请求的时候,需要注意的东西相对来说比较少, 不管是 get 请求还是 post 请求,实现步骤是不会变。 这里post 请求,我们也是用http://www.minongbang.com/test.php?test=minongbang这个api 来测试!
在前面我们已经讲到了,在同一个应用中共用同一个 RequestQueue,所以第一步可以省略,因为我们已经实现过了。这里直接到定制Request,我们在学习网络编程的时候就已经知道,用 GET方式请求,请求的数据是直接跟在 URL的后面用”?”去分开了,如果有多个数据则用”&”分开。而 POST则把数据直接封装在HTTP的包体中,两者各有优缺点,自己衡量着用。
因为 api 接口还是同一个,所以返回的数据类型肯定是一样的,在解析数据的时候就可以和 GetObjectRequest 复用,所以 PostObjectRequest 的实现可以通过继承GetObjectRequest的方式,也可以直接拷贝一份出来,为了更好的区分,我这里就直接拷贝一份,然后再稍加修改。

/** * Created by gyzhong on 15/3/1. */public class PostObjectRequest<T> extends Request<T> {    /**     * 正确数据的时候回掉用     */    private ResponseListener mListener ;    /*用来解析 json 用的*/    private Gson mGson ;    /*在用 gson 解析 json 数据的时候,需要用到这个参数*/    private Type mClazz ;    /*请求 数据通过参数的形式传入*/    private Map<String,String> mParams;    //需要传入参数,并且请求方式不能再为 get,改为 post    public PostObjectRequest(String url, Map<String,String> params,Type type, ResponseListener listener) {        super(Method.POST, url, listener);        this.mListener = listener ;        mGson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create() ;        mClazz = type ;        setShouldCache(false);        mParams = params ;    }    /**     * 这里开始解析数据     * @param response Response from the network     * @return     */    @Override    protected Response<T> parseNetworkResponse(NetworkResponse response) {        try {            T result ;            String jsonString =                    new String(response.data, HttpHeaderParser.parseCharset(response.headers));            Log.v("zgy", "====jsonString===" + jsonString);            result = mGson.fromJson(jsonString,mClazz) ;            return Response.success(result,                    HttpHeaderParser.parseCacheHeaders(response));        } catch (UnsupportedEncodingException e) {            return Response.error(new ParseError(e));        }    }    /**     * 回调正确的数据     * @param response The parsed response returned by     */    @Override    protected void deliverResponse(T response) {        mListener.onResponse(response);    }//关键代码就在这里,在 Volley 的网络操作中,如果判断请求方式为 Post 则会通过此方法来获取 param,所以在这里返回我们需要的参数,    @Override    protected Map<String, String> getParams() throws AuthFailureError {        return mParams;    }}

再来看看 api 接口怎么实现,

    /*    * *Minong 测试数据post网络请求接口    * @param value 测试数据    * @param listener 回调接口,包含错误回调和正确的数据回调    */    public static void postObjectMinongApi(String value,ResponseListener listener){        Map<String,String> param = new HashMap<String,String>() ;        param.put("test",value) ;        Request request = new PostObjectRequest(Constant.MinongHost,param,new TypeToken<TestBean>(){}.getType(),listener);        VolleyUtil.getRequestQueue().add(request) ;    }

跟 get 请求还是很相似的,只是在实例化 Request 的时候多传入了一个param参数,并且 url 不能再是包含请求数据的 url。
接口 api测试代码

public class PostRequestActivity extends ActionBarActivity {    /*数据显示的View*/    private TextView mIdTxt,mNameTxt,mDownloadTxt,mLogoTxt,mVersionTxt ;    /*弹出等待对话框*/    private ProgressDialog mDialog ;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_get);        mIdTxt = (TextView) findViewById(R.id.id_id) ;        mNameTxt = (TextView) findViewById(R.id.id_name) ;        mDownloadTxt = (TextView) findViewById(R.id.id_download) ;        mLogoTxt = (TextView) findViewById(R.id.id_logo) ;        mVersionTxt = (TextView) findViewById(R.id.id_version) ;        mDialog = new ProgressDialog(this) ;        mDialog.setMessage("post请求中...");        mDialog.show();        /*请求网络获取数据*/        MiNongApi.postObjectMinongApi("minongbang",new ResponseListener<TestBean>() {            @Override            public void onErrorResponse(VolleyError error) {                mDialog.dismiss();            }            @Override            public void onResponse(TestBean response) {                mDialog.dismiss();                /*显示数据*/                mIdTxt.setText(response.getId()+"");                mNameTxt.setText(response.getName());                mDownloadTxt.setText(response.getDownload()+"");                mLogoTxt.setText(response.getLogo());                mVersionTxt.setText(response.getVersion()+"");            }        });    }}

测试数据显示跟 get 请求完全相同;ok,以上就是 Volley GET请求和 POST请求的全部内容!接下来又到了总结的时候

四、总结

1、volley 适用于轻量高并发的网络请求,这里补充一个知识点,因为 Volley 请求网络的数据全部保存在内存中,所以 volley 不适合请求较大的数据,比如下载文件,下载大图片等。

2、volley 的使用遵循四个步骤
a、RequestQueue mRequestQueue = Vollay.newRequestQueue(Context context) ;
    b、mRequestQueue.start()
    c、Request mRequest = new ObjectRequst(…)
    d、mRequestQueue.add(mRequest)

3、同一个程序中最好共用一个 RequestQueue。

4、可以根据接口的放回数据类型定制任意的 Request,volley 已经默认为我们实现了 StringRequest、JsonArrayRequest、JsonObjectRequest、ImageRequest四个请求类型。

最后如果觉得有用请继续关注我的 blog,我将会在下篇 blog 中讲解,Volley 如何通过 post实现表单的提交。

现在源码点这里

Android Volley解析(一)之GET、POST请求篇

 

0 0