美文网首页
Volley源码分析

Volley源码分析

作者: Yasin27878 | 来源:发表于2018-12-14 14:19 被阅读6次

    一.Volley的基本使用

    1.volley简单使用

    • 1.创建一个RequestQueue

    RequestQueue requestQueue = Volley.newReequestQueue(context);
    
    • 2.创建一个StringRequest对象

     StringRequest sq = new StringRequest("www.baidu.com",new Request.Listener<String>(){
         @overide
         public void onRespose(String res){
             Log.d("TAG",res)
         }
     },new Respose.ErrorListener(){
         @Override
         public void onErrorRespose(VolleyError error){
             log.e("TAG",error.getMessage(),error)
         }
     });
    
    • 3.把StringRequest对象添加到RequestQueue

    requestQueue.add(sq);
    

    2.其他

    StringRequest的请求方式 ---四参构造方法

    StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {  
        @Override  
        protected Map<String, String> getParams() throws AuthFailureError {  
            Map<String, String> map = new HashMap<String, String>();  
            map.put("params1", "value1");  
            map.put("params2", "value2");  
            return map;  
        }  
    };  
    

    JsonRequest的用法

    StringRequest,JsonRequest也是继承自Request类的不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest

    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html",null,new Respose.Listener<JSONObject>(){
        @Override
        public void onRespose(JSONObject respose){
            log.d("TAG",response.toString());
        }
    },new Respose.ErrorListener(){
        @Override
        public void onErrorListener(VolleyError error){
            Log.e("TAG",error.getMessage(),error);
        }
    });
    //最后把它添加到RequestQueue就可以了
    mQueue.add(jsonObjectRequest); 
    

    2.使用Volley加载网络图片

    1.ImageRequest的用法

    1.创建RequestQueue对象

    RequestQueue rq = Volley.newRequestQueue(context);
    

    2.创建ImageRequest对像

    ImageRequest ir = new ImageRequest("image_url",new Respose>listener<Bitmap>(){
        @Override
        public void onRespose(Bitmap res){
            imageview.setImageBitmap(res);
        }
    },0,0,Config.RGB_565,new Response.ErrorListener(){
        @Override
        public void onErrorRespose(VolleyReeor error){
            imageView.setImageResource(R.drawable.default_image);
        }
    })
    

    可以看到,ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址,这个没什么需要解释的。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。

    3.最后将ImageRequest对象添加到RequestQueue中

    mQueue.add(imageRequest);
    

    2.ImageLoader的用法

    由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步:

    1. 创建一个RequestQueue对象。

    RequestQueue rq = Volley.newRequestQueue(context);
    

    2. 创建一个ImageLoader对象。

    ImageLoader imageLoader = new ImageLoader(rq,new ImageCache(){
        @Override
        public void putBitmap(String url,Bitmap bitmap){
        }
        @Override
        public Bitmap getBitmap(String url){
            return null
        }
    })
    
    

    3. 获取一个ImageListener对象。

    ImageListener listener = ImageLoader.getImagerListener(imageview,R.drawable.default,R.drawable.failed_image);
    

    我们通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对象,getImageListener()方法接收三个参数,第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片。

    4. 调用ImageLoader的get()方法加载网络上的图片。

    imageLoader.get("image_url",listener);
    

    get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:

    imageLoader.get("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",  listener, 200, 200);  
    

    三 定制自己的Request

    主要参考StringRequest 继承Request<T>
    首先是StringRequest的源码

    /**
     * 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的源码很简练,根本就没几行代码,我们一起来分析下。首先StringRequest是继承自Request类的,Request可以指定一个泛型类,这里指定的当然就是String了,接下来StringRequest中提供了两个有参的构造函数,参数包括请求类型,请求地址,以及响应回调等,由于我们已经很熟悉StringRequest的用法了,相信这几个参数的作用都不用再解释了吧。但需要注意的是,在构造函数中一定要调用super()方法将这几个参数传给父类,因为HTTP的请求和响应都是在父类中自动处理的。

    另外,由于Request类中的deliverResponse()和parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入即可,这样就可以将服务器响应的数据进行回调了。parseNetworkResponse()方法中则应该对服务器响应的数据进行解析,其中数据是以字节的形式存放在NetworkResponse的data变量中的,这里将数据取出然后组装成一个String,并传入Response的success()方法中即可。

    下面我们就可以动手来尝试实现一下XMLRequest了,代码如下所示:

    public class XMLRequest extends Request<XmlPullParser> {
    
        private final Listener<XmlPullParser> mListener;
    
        public XMLRequest(int method, String url, Listener<XmlPullParser> listener,
                ErrorListener errorListener) {
            super(method, url, errorListener);
            mListener = listener;
        }
    
        public XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {
            this(Method.GET, url, listener, errorListener);
        }
    
        @Override
        protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {
            try {
                String xmlString = new String(response.data,
                        HttpHeaderParser.parseCharset(response.headers));
                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                XmlPullParser xmlPullParser = factory.newPullParser();
                xmlPullParser.setInput(new StringReader(xmlString));
                return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));
            } catch (UnsupportedEncodingException e) {
                return Response.error(new ParseError(e));
            } catch (XmlPullParserException e) {
                return Response.error(new ParseError(e));
            }
        }
    
        @Override
        protected void deliverResponse(XmlPullParser response) {
            mListener.onResponse(response);
        }
    
    }
    

    自定义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);
        }
    
    }
    

    带你从源码的角度理解Volley

    核心思路:我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。

    开始

    说起分析源码,那么应该从哪儿开始看起呢?这就要回顾一下Volley的用法了,还记得吗,使用Volley的第一步,首先要调用Volley.newRequestQueue(context)方法来获取一个RequestQueue对象,那么我们自然要从这个方法开始看起了,代码如下所示:

    public static RequestQueue newRequestQueue(Context context) {  
        return newRequestQueue(context, null);  
    }  
    

    这个方法仅仅只有一行代码,只是调用了newRequestQueue()的方法重载,并给第二个参数传入null。那我们看下带有两个参数的newRequestQueue()方法中的代码,如下所示:

    public static RequestQueue newRequestQueue(Context content,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 {  
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));  
            }  
        }  
        Network network = new BasicNetwork(stack);  
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);  
        queue.start();  
        return queue;     
    }
    
    

    可以看到,这里在第10行判断如果stack是等于null的,则去创建一个HttpStack对象,这里会判断如果手机系统版本号是大于9的,则创建一个HurlStack的实例,否则就创建一个HttpClientStack的实例。实际上HurlStack的内部就是使用HttpURLConnection进行网络通讯的,而HttpClientStack的内部则是使用HttpClient进行网络通讯的.

    创建好了HttpStack之后,接下来又创建了一个Network对象,它是用于根据传入的HttpStack对象来处理网络请求的,紧接着new出一个RequestQueue对象,并调用它的start()方法进行启动,然后将RequestQueue返回,这样newRequestQueue()的方法就执行结束了。

    那么RequestQueue的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();  
        }  
    }
    

    这里先是创建了一个CacheDispatcher的实例,然后调用了它的start()方法,接着在一个for循环里去创建NetworkDispatcher的实例,并分别调用它们的start()方法。这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的,而默认情况下for循环会执行四次,也就是说当调用了Volley.newRequestQueue(context)之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,其中CacheDispatcher是缓存线程,NetworkDispatcher是网络请求线程。

    addRequest

    得到了RequestQueue之后,我们只需要构建出相应的Request,然后调用RequestQueue的add()方法将Request传入就可以完成网络请求操作了,那么不用说,add()方法的内部肯定有着非常复杂的逻辑,我们来一起看一下:

    public <T> Request<T> add(Request<T> 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;  
        }  
    }  
    

    可以看到,在第11行的时候会判断当前的请求是否可以缓存,如果不能缓存则在第12行直接将这条请求加入网络请求队列,可以缓存的话则在第33行将这条请求加入缓存队列。在默认情况下,每条请求都是可以缓存的,当然我们也可以调用Request的setShouldCache(false)方法来改变这一默认行为。

    OK,那么既然默认每条请求都是可以缓存的,自然就被添加到了缓存队列中,于是一直在后台等待的缓存线程就要开始运行起来了,我们看下CacheDispatcher中的run()方法,代码如下所示:

    public class CacheDispatcher extends Thread {
    
        ……
    
        @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();
                    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");
                        // Cache miss; send off to the network dispatcher.
                        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);
                        mNetworkQueue.put(request);
                        continue;
                    }
                    // We have a cache hit; parse its data for delivery back to the request.
                    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.
                        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;
                }
            }
        }
    }
    

    代码有点长,我们只挑重点看。首先在11行可以看到一个while(true)循环,说明缓存线程始终是在运行的,接着在第23行会尝试从缓存当中取出响应结果,如何为空的话则把这条请求加入到网络请求队列中,如果不为空的话再判断该缓存是否已过期,如果已经过期了则同样把这条请求加入到网络请求队列中,否则就认为不需要重发网络请求,直接使用缓存中的数据即可。之后会在第39行调用Request的parseNetworkResponse()方法来对数据进行解析,再往后就是将解析出来的数据进行回调了,这部分代码我们先跳过,因为它的逻辑和NetworkDispatcher后半部分的逻辑是基本相同的,那么我们等下合并在一起看就好了,先来看一下NetworkDispatcher中是怎么处理网络请求队列的,代码如下所示:

    public class NetworkDispatcher extends Thread {
        ……
        @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;
                    }
                    addTrafficStatsTag(request);
                    // 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));
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Volley源码分析

          本文链接:https://www.haomeiwen.com/subject/pwhghqtx.html