美文网首页
Android Volley源码阅读——主线流程探索

Android Volley源码阅读——主线流程探索

作者: 家硕先生 | 来源:发表于2018-08-08 23:27 被阅读39次

    在Android开发中,网络请求除了自己封装HttpUtils工具类,就是使用开源的第三方网络请求框架了。出于学习的目的,阅读一下google亲儿子Volley框架的源码。选Volley阅读的主要原因还是Volley代码量小,阅读起来不会那么繁琐。

    1.Volley的基本用法
    在阅读一个框架之前,首先得了解该框架的基本用法,下面简单介绍Volley框架的使用:

    // 1.首先实例化一个请求队列
    RequestQueue mQueue = Volley.newRequestQueue(context);
    // 2.实例化一个Request,此处以StringRequest为例
    StringRequest stringRequest = new StringRequest("http://www.xxx.com",
                            new Response.Listener<String>() {
                                @Override
                                public void onResponse(String response) {
                                    Log.d("TAG", response);
                                }
                            }, new Response.ErrorListener() {
                                @Override
                                public void onErrorResponse(VolleyError error) {
                                    Log.e("TAG", error.getMessage(), error);
                                }
                            });
    // 3.将请求加入请求队列中
    mQueue.add(stringRequest);
    

    2.Volley源码分析
    顺着Volley框架Api调用顺序开始分析源码。
    Step 1:调用Volley.newRequestQueue(context)来实例化一个RequestQueue对象,代码如下:

        public static RequestQueue newRequestQueue(Context context) {
            return newRequestQueue(context, (BaseHttpStack) null);
        }
    
        public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
            BasicNetwork network;
            if (stack == null) {
                if (Build.VERSION.SDK_INT >= 9) {
                    // Android 2.3及以上版本,HurlStack内部http请求实现使用的是HttpURLConnection
                    network = new BasicNetwork(new HurlStack());
                } else {
                    // Android 2.3以下版本,HttpClientStack内部http请求实现使用的是HttpClient
                    String userAgent = "volley/0";
                    try {
                        String packageName = context.getPackageName();
                        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
                        userAgent = packageName + "/" + info.versionCode;
                    } catch (NameNotFoundException e) {
    
                    }
                    network = new BasicNetwork(new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
                }
            } else {
                network = new BasicNetwork(stack);
            }
            return newRequestQueue(context, network);
        }  
    
        private static RequestQueue newRequestQueue(Context context, Network network) {
            File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
            RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
            queue.start();
            return queue;
        }
    

    插个支线话题,因为以前大致瞄过Volley源码,原先实例化RequestQueue的方法应该是newRequestQueue(Context context, HttpStack stack),但是HttpStack已经被废弃,原因是HttpStack依赖了已经被Apache弃用的Apache HTTP库。

    在newRequestQueue()方法执行完后,会默认帮你调用RequestQueue.start()方法,让我们看看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();
            }
        }
    

    可以看到start()方法中创建了一个CacheDispatcher实例和若干个NetworkDispatcher实例(默认是4个),其实这些每个实例都相当于一个线程,其中CacheDispatcher是缓存线程,NetworkDispatcher是网路请求的线程。后台运行这5个线程,等待RequestQueue.add()方法被调用,添加请求到请求队列去给后台线程去执行。

    到此,RequestQueue mQueue = Volley.newRequestQueue(context)的源码分析结束。

    Step 2:接着看StringRequest的内部实现
    emmm...其实StringRequest好像没啥东西可以说的,要看的话可以看看StringRequest的构造方法,不看也不影响主线流程,我们便直接看StringRequest的父类:

    public abstract class Request<T> implements Comparable<Request<T>> {
        ....   
    
        // 表示请求优先级
        public enum Priority {
            LOW,
            NORMAL,
            HIGH,
            IMMEDIATE
        }
    
     /**
         * Our comparator sorts from high to low priority, and secondarily by sequence number to provide
         * FIFO ordering.
         */
        @Override
        public int compareTo(Request<T> other) {
            // 1.先获取请求优先级
            Priority left = this.getPriority();
            Priority right = other.getPriority();
            // 2.两个Request优先级一样时,序号小的放前面; 两个Request优先级不一致时,优先级高的在前面
            return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal();
        }
        ....
    }
    

    因为是个基类貌似代码有点多,Request实现了Comparable接口,为什么?
    原因是RequestQueue存储请求队列的是:

        /** The cache triage queue. */
        private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>();
    
        /** The queue of requests that are actually going out to the network. */
        private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>();
    

    PriorityBlockingQueue 优先级阻塞队列,放入该队列的元素需要实现Comparable接口,用于队列元素排序。

    Step 3:接着是分析RequestQueue.add(stringRequest),先看源码:

        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;
            }
            mCacheQueue.add(request);
            return request;
        }
    

    代码似乎很简单:

    1. 首先把Request添加到所有的请求集合中,为确保线程安全使用了同步代码块;
    2. 给请求设置序号,序号是使用AtomicInteger.incrementAndGet()获取的,确保序号的累加并发不会出现问题;
    3. Reuest若是需要进行缓存,则添加到缓存队列mChcheQueue,若不需要缓存则直接添加到网络请求队列mNetwordQueue。

    将请求添加到队列后,RequestQueue中的NetworkDispatcherCacheDispatcher线程就开始干活了。

    Step 5:接下来看真正干活的NetworkDispatcherCacheDispatcher是如何实现的?
    先看一下NetworkDispatcher的主要代码:

    public class NetworkDispatcher extends Thread {
        @Override
        public void run() {
            // 设置当前线程优先级为后台线程
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            while (true) {
                try {
                    processRequest();
                } catch (InterruptedException e) {
                    // 在线程阻塞状态时,调用interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常
                    if (mQuit) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
            }
        }
    
        // Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
        // This is needed to avoid keeping previous request references alive for an indeterminate amount
        // of time. Update consumer-proguard-rules.pro when modifying this. See also
        // https://github.com/google/volley/issues/114
        private void processRequest() throws InterruptedException {
            // Take a request from the queue.
            Request<?> request = mQueue.take();
            processRequest(request);
        }
    
        void processRequest(Request<?> request) {
            long startTimeMs = SystemClock.elapsedRealtime();
            try {
                // 部分代码省略
                ......
                // 在此处发出请求,调用Network去发送请求处理
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
                ......
                // 如果请求是需要进行缓存的,把请求结果添加进缓存
                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);
                request.notifyListenerResponseReceived(response);
            } 
        }
    }
    

    NetworkDispatcher是负责网络请求队列的执行,主要逻辑已在代码中注释,接下来看CacheDispatcher是如何实现的?

    public class CacheDispatcher extends Thread {
    
       void processRequest(final Request<?> request) throws InterruptedException {
            // 省略部分代码
            ......
    
            // 1. 先尝试从缓存中获取请求结果,不存在则将请求放到网络请求队列中
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // Cache miss; send off to the network dispatcher.
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
    
            // 2. 如果从缓存中获取到请求结果,则判断是否是否超时失效,失效则将请求放到网络请求队列中
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
    
            // 3.从缓存取到请求结果
            .....
        }
    
    }
    

    从processRequest()代码中提取出该方法执行的步骤:

    1. 先尝试从缓存中获取请求结果,不存在则将请求放到网络请求队列中;
    2. 如果从缓存中获取到请求结果,则判断是否是否超时失效,失效则将请求放到网络请求队列中;

    从上面可以看出,CacheDispatcher和NetworkDispatcher是有联系的,若是缓存失效的话,则会将请求重新加入到网络请求队列,让NetworkDispatcher线程去执行。

    到此,Volley源码的主线流程分析完毕

    相关文章

      网友评论

          本文标题:Android Volley源码阅读——主线流程探索

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