Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android Volley源码分析(1)

$
0
0

之前的博客中已经记录过Volley的基本使用方法了,
从本篇博客开始,我会比较仔细地分析整个Volley框架的源码。

对于一个APK开发者而言,细致地了解整个Volley源码可能用处不大,
但对于一个Framework开发者而言,阅读和分析源码的能力还是时不时地锻炼一下为好,
况且如此广泛被使用的通信框架,它的源码应该是营养丰富的,仔细看看一定会有所收获的。


1. Volley.newRequestQueue

我们从Volley中RequestQueue的初始化入手,开始进行分析。

//应用利用Volley.java的静态方法,获取RequestQueue,开启使用Volley框架的大门
public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}

//最终调用到该函数
//HttpStack是个接口,定义了performRequest函数
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    //创建一个File文件,指向应用默认的Cache路径,子路径名称为“volley”
    //例如之前博客的demo,最终会建立一个/data/data/stark.a.is.zhang.volleytest/cache/volley的cache文件
    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名称,这个将用在Http通信中
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    //创建通信使用的协议栈
    //在Volley框架中,协议栈应该是进行通信的实体对象
    //之后分析Volley处理Request的流程时,应该还会遇到
    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 network = new BasicNetwork(stack);

    //创建DiskBasedCache对象,指向之前的cacheDir,默认缓存最大值为5MB
    //注意到构造RequestQueue时,同时传入了缓存对象和网络对象,因此缓存和下载就是RequestQueue的核心功能
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

    //创建完RequestQueue后,调用其start方法启动
    queue.start();

    return queue;
}

从上述代码来看,即使我们在同一个应用中多次调用newRequestQueue,
得到的多个RequestQueue也将共用同一个缓存文件和userAgent,
即对于网络服务端,一个应用所有的RequestQueue都只是一个客户。

从这里可以看出,同一个应用使用一个RequestQueue就够了。
后文可以看到,RequestQueue底层已经支持并发特性,
多个RequestQueue进一步加大并发力度,对于移动终端而言,用处不大。

在这一部分的最后,我们看看RequestQueue的构造函数:

public RequestQueue(Cache cache, Network network) {
    //DEFAULT_NETWORK_THREAD_POOL_SIZE的值为4
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            //ExecutorDelivery用于向主线程传递下载结果
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

public RequestQueue(Cache cache, Network network, int threadPoolSize,
        ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    //创建NetworkDispatcher对应的数组
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

接下来,我们看看RequestQueue的start方法。


2. RequestQueue的start方法

    .................
    public void start() {
        //如果已经存在处于运行态的CacheDispatcher和NetworkDispatcher,则进行停止操作
        //相当于先复位现有状态
        stop();  // Make sure any currently running dispatchers are stopped.

        // Create the cache dispatcher and start it.
        // 创建新的CacheDispatcher,并启动
        // CacheDispatcher具有cache和network对应的BlockingQueue
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        // 默认创建4个NetworkDispatcher
        //这个四个NetworkDispatcher与上文的CacheDispatcher共用network对应的BlockingQueue
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }
    .................

CacheDispatcher与NetworkDispatcher均继承自Thread,我们看看这些线程启动后的情况。

2.1 CacheDispatcher的run方法

@Override
public void run() {
    ..............
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // Make a blocking call to initialize the cache.
    // 初始化缓存区,即调用DiskBasedCache的initialize方法
    mCache.initialize();
    ..............

在分析后续流程前,先看看DiskBasedCache在initialize方法中的工作。

2.1.1 DiskBasedCache的initialize方法

    @Override
    public synchronized void initialize() {
        //如果应用对应的cache/volly不存在,就创建对应的目录
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }

        //得到目录下所有的文件
        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }

        //如果目录下已经存在文件,就对文件进行解析
        for (File file : files) {
            BufferedInputStream fis = null;
            try {
                fis = new BufferedInputStream(new FileInputStream(file));

                //从所有的文件中解析出对应的CacheHeader对象
                //readHeader方法就是从InputStream中,读取CacheHeader所需的字段
                CacheHeader entry = CacheHeader.readHeader(fis);
                entry.size = file.length();

                //文件被放置到LinkedHaspMap中
                //LinkedHaspMap的初始化形式为:new LinkedHashMap<String, CacheHeader>(16, .75f, true);
                //最后的参数表示accessOrder,当为true时,以LRU算法保存对象
                //初始向LinkedHaspMap加入数据时,按加入顺序依次保存
                //一旦访问LinkedHashMap时,被访问的对象将被移动到尾部
                //当LinkedHaspMap满了,需要释放空间时,将从前面开始移除最老的对象
                putEntry(entry.key, entry);
            } catch (IOException e) {
                if (file != null) {
                   file.delete();
                }
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException ignored) { }
            }
        }
    }

至此,DiskBasedCache的initialize方法分析完毕。
容易看出,在该方法中将创建出物理的文件存储目录;
如果该目录已经存在,则形成并保存当前Cache文件的CacheHeader。
DiskBasedCache将以LinkedHashMap来保存CacheHeader,以呈现出LRU算法的特性。

2.1.2 CacheDispatcher的服务端循环

分析完初始化缓存目录后,我们回到CacheDispatcher的run方法,看看后续的代码:

...................
//无限循环
//CacheDispatcher线程类似于一个处理Request的服务端
while (true) {
    try {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.
        // mCacheQueue是一个BlockingQueue,意味着无数据时,将一直阻塞在这里
        final Request<?> request = mCacheQueue.take();
        request.addMarker("cache-queue-take");

        // If the request has been canceled, don't bother dispatching it.
        // 当取出Request后,首先判断是否已经取消
        if (request.isCanceled()) {

            //若Request已取消,则调用其finish接口
            request.finish("cache-discard-canceled");
            continue;
        }

        // Attempt to retrieve this item from cache.
        //根据Request的CacheKey,判断DiskBaseCache中是否已经进行过存储
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.

            // 没有存储的话,就将该Request加入到NetworkQueue中
            // 注意到NetworkDispatcher保存着NetworkQueue的引用,将负责处理
            mNetworkQueue.put(request);
            continue;
        }

        // If it is completely expired, just send it to the network.
        // 如果Request有对应的Cache,但Cache too old
        // 那么也要将Request重新加入到NetworkQueue中
        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");
        // 如果有可用的Cache,那么从Cache中解析出NetworkResponse
        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.
            //利用ExecutorDelivery将结果返回给UI线程
            mDelivery.postResponse(request, response);
        } else {
            // 缓存可用,但需要refresh时
            // 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.
            // 将结果递交给界面,同时提交Request给NetworkQueue
            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.
        //异常时,除非调用过quit接口,否则继续循环
        if (mQuit) {
            return;
        }
        continue;
    }
}
.......................

至此,CacheDispatcher的服务端介绍完毕。

上面代码的处理逻辑还是清晰易懂的:
当CacheDispatcher得到一个Request时,试着从Cache中获取对应的Response信息。
如果获取失败或着Cache超时,那么将Request加入到NetworkQueue中,等待下载。
如果成功获取到Cache,那么解析出对应的Response,发送给UI线程;
如果Cache还要求refresh,那么将Response递交给UI线程的同时,仍将Request递交给NetworkQueue处理。

2.2 NetworkDispatcher的run方法

现在我们再来看看NetworkDispatcher的run方法:

@Override
public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    while (true) {
        //要是我写这个框架,我就将这行代码移到mQueue.take下面
        //感觉这样更准确些吧
        long startTimeMs = SystemClock.elapsedRealtime();

        Request<?> request;

        try {
            // Take a request from the queue.
            // 从NetworkQueue中取出Request
            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;
            }

            .............

            // Perform the network request.
            // 交给BasicNetwork的performRequest处理
            // 将与网络端通信,直到获得返回结果
            // 下一篇博客再分析这些细节
            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.
            // 当向服务端发送了同样的NetworkRequest,服务端回应not modified
            // 并且这个NetworkRequest已经向UI线程发送过Response时,不再进行后续处理
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // Parse the response here on the worker thread.
            // 由Request的子类处理,解析返回结果
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            //如果需要,则将Response中的信息加入到缓存中
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // Post the response back.
            request.markDelivered();

            //向UI线程返回结果
            mDelivery.postResponse(request, response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            //如果出现的是VolleyError,则根据Request的类型解析VolleyError,再递交给UI线程
            parseAndDeliverNetworkError(request, volleyError);
        } catch (Exception e) {
            //出异常时,直接封装异常并递交给UI线程
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
        }
    }
}

至此,NetworkDispatcher的run方法分析完毕。

作为一个服务端,NetworkDispatcher将具体的下载工作递交给BasicNetwork处理;
下载完毕后的结果递交给具体的Request子类分析,并将处理结果通过ExecutorDelivery发送给UI线程。


3. 总结

了解Volley的Cache和Network服务端处理流程后,
我们基本上已经大致知道了Volley处理网络请求的脉络了。

结合上图,我们再来整体回顾一下。
从整体架构来看,Volley就是一个供APK调用的工具类。

当Volley的接口被调用,用于创建RequetQueue时,
Volley会负责创建下载需要的HttpStack,并指定存储缓存的Cache区域。
然后,Volley将管理HttpStack的BasicNetwork对象,及管理Cache的DiskBasedCache对象
一并递交给RequestQueue的构造函数。

RequestQueue会负责启动检索缓存的CacheDispatcher对象,
执行网络下载工作的NetworkDispatcher对象,
及为UI线程传递Response信息的ExecutorDelivery对象。

CacheDispatcher启动后,将负责创建DiskBasedCache对应的文件存储目录;
收到Request后,根据对应目录下的缓存信息,决定是否进行实际的网络下载工作。

NetworkDispatcher启动,收到Request后,将利用BasicNetwork进行实际的下载工作。

CacheDispatcher和NetworkDispatcher均利用ExecutorDelivery向UI线程返回Response信息。


在下一篇博客中,我们再从向RequestQueue加入Request的流程开始分析,
从客户端流程入手,看看Volley是如何分配Request的,并深入看看BasicNetwork的实际下载流程。

作者:Gaugamela 发表于2017/3/24 14:58:17 原文链接
阅读:44 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>