深度揭秘!Android Volley性能瓶颈全解析与源码级剖析(21)

65 阅读9分钟

深度揭秘!Android Volley性能瓶颈全解析与源码级剖析

一、引言

在Android移动开发领域,Android Volley曾是开发者处理网络请求的热门选择。它以轻量级、易用性和内置缓存等特性,帮助开发者快速实现网络通信功能。然而,随着应用规模的扩大和用户对性能要求的不断提高,Volley在实际使用中逐渐暴露出一些性能瓶颈。这些瓶颈不仅影响应用的响应速度,还可能导致用户体验下降、资源浪费等问题。深入分析Volley的性能瓶颈,从源码层面探究其产生的原因,对于优化应用性能、提升用户体验具有重要意义。本文将围绕Android Volley的性能瓶颈展开全面、深入的源码级分析,揭开其性能问题背后的真相。

二、Volley架构基础回顾

在分析性能瓶颈之前,有必要先对Volley的基础架构进行回顾,这有助于我们更好地理解后续性能问题产生的根源。

2.1 核心组件

Volley主要由以下几个核心组件构成:

  1. RequestQueue(请求队列):请求队列是Volley的核心调度中心,负责管理和调度所有的网络请求。它包含两个子队列,分别是缓存队列(mCacheQueue)和网络队列(mNetworkQueue)。
public class RequestQueue {
    // 缓存队列,存储可缓存的网络请求
    private final BlockingQueue<Request<?>> mCacheQueue = new LinkedBlockingQueue<>();
    // 网络队列,存储需要进行网络请求的任务
    private final BlockingQueue<Request<?>> mNetworkQueue = new LinkedBlockingQueue<>();
    // 缓存对象,用于读写缓存数据
    private final Cache mCache;
    // 网络对象,负责实际的网络请求操作
    private final Network mNetwork;
    // 响应分发器,将处理后的响应数据分发到主线程
    private final ResponseDelivery mDelivery;
    // 缓存调度器线程
    private CacheDispatcher mCacheDispatcher;
    // 网络调度器线程数组
    private NetworkDispatcher[] mNetworkDispatchers;

    // 构造函数,初始化请求队列相关组件
    public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDelivery = delivery;
        // 创建缓存调度器线程
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        // 创建并启动多个网络调度器线程
        mNetworkDispatchers = new NetworkDispatcher[threadPoolSize];
        for (int i = 0; i < threadPoolSize; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
            mNetworkDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    // 将请求添加到请求队列中
    public <T> Request<T> add(Request<T> request) {
        // 设置请求所属的请求队列
        request.setRequestQueue(this);
        // 判断请求是否可缓存,决定放入哪个队列
        if (request.shouldCache()) {
            mCacheQueue.add(request);
        } else {
            mNetworkQueue.add(request);
        }
        return request;
    }

    // 启动请求队列,开始处理请求
    public void start() {
        mCacheDispatcher.start();
        for (NetworkDispatcher networkDispatcher : mNetworkDispatchers) {
            networkDispatcher.start();
        }
    }
}
  1. Cache(缓存):缓存模块负责存储和读取网络请求的响应数据,通过减少重复的网络请求来提高应用性能。Volley的缓存接口为Cache,默认实现类是DiskBasedCache,基于文件系统实现缓存功能。
public class DiskBasedCache implements Cache {
    // 缓存目录,用于存储缓存文件
    private final File mRootDirectory;
    // 缓存的最大容量(字节)
    private final int mMaxCacheSizeInBytes;
    // 当前缓存已使用的大小(字节)
    private long mTotalSize = 0;
    // 缓存条目映射表,通过缓存键快速查找缓存条目
    private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, 0.75f, true);

    // 构造函数,初始化缓存相关参数
    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        mRootDirectory = rootDirectory;
        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    // 从缓存中获取指定键的数据条目
    @Override
    public Entry get(String key) {
        // 根据缓存键生成对应的缓存文件
        File file = getFileForKey(key);
        // 如果文件不存在,说明缓存中没有该数据
        if (!file.exists()) {
            return null;
        }
        BufferedInputStream fis = null;
        try {
            // 打开文件输入流
            fis = new BufferedInputStream(new FileInputStream(file));
            // 读取缓存头部信息,获取元数据
            CacheHeader header = CacheHeader.readHeader(fis);
            // 如果头部信息读取失败,删除文件并返回null
            if (header == null) {
                VolleyLog.d("Cache header corruption for %s", file.getAbsolutePath());
                file.delete();
                return null;
            }
            // 读取缓存数据部分
            byte[] data = streamToBytes(fis, (int) (file.length() - fis.available()));
            // 创建缓存条目对象并填充数据
            Entry entry = new Entry();
            entry.data = data;
            entry.etag = header.etag;
            entry.softTtl = header.softTtl;
            entry.ttl = header.ttl;
            entry.serverDate = header.serverDate;
            entry.responseHeaders = header.responseHeaders;
            return entry;
        } catch (IOException e) {
            // 发生异常时,记录日志并删除文件
            VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
            remove(key);
            return null;
        } finally {
            // 关闭输入流
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    // 将数据存入缓存
    @Override
    public void put(String key, Entry entry) {
        // 检查缓存空间是否足够,不足则清理部分缓存
        pruneIfNeeded(entry.data.length);
        // 根据缓存键生成对应的缓存文件
        File file = getFileForKey(key);
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        try {
            // 创建临时文件用于写入
            File tmpFile = getTempFileForKey(key);
            // 打开临时文件输出流
            fos = new FileOutputStream(tmpFile);
            bos = new BufferedOutputStream(fos);
            // 写入缓存头部信息
            CacheHeader header = new CacheHeader(key, entry);
            header.writeHeader(bos);
            // 写入缓存数据
            bos.write(entry.data);
            // 刷新输出流
            bos.flush();
            // 将临时文件重命名为正式的缓存文件
            if (!tmpFile.renameTo(file)) {
                VolleyLog.e("ERROR: rename failed, tmpFile=%s, file=%s", tmpFile.getAbsolutePath(), file.getAbsolutePath());
                throw new IOException("Rename failed!");
            }
            // 更新缓存条目映射表和总大小
            putEntry(key, header);
        } catch (IOException e) {
            // 发生异常时,删除文件并记录日志
            if (file.exists()) {
                if (!file.delete()) {
                    VolleyLog.e("Could not clean up file %s", file.getAbsolutePath());
                }
            }
            VolleyLog.e("Failed to write cache entry for key=%s, filename=%s: %s", key, file.getAbsolutePath(), e.getMessage());
        } finally {
            // 关闭输出流
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException ignored) {
                }
            } else if (fos != null) {
                try {
                    fos.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    // 如果缓存空间不足,清理部分缓存
    private void pruneIfNeeded(int neededSpace) {
        // 如果当前缓存大小加上需要的空间超过最大缓存容量
        if ((mTotalSize + neededSpace) > mMaxCacheSizeInBytes) {
            // 计算目标缓存大小(最大容量的90%)
            int targetSize = (int) (mMaxCacheSizeInBytes * 0.9f);
            // 遍历缓存条目映射表,删除较旧的条目
            Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
            while (iterator.hasNext() && mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
                Map.Entry<String, CacheHeader> entry = iterator.next();
                CacheHeader e = entry.getValue();
                // 删除对应的缓存文件
                boolean deleted = e.file.delete();
                // 更新已使用的缓存大小
                if (deleted) {
                    mTotalSize -= e.size;
                }
                // 从映射表中移除该条目
                iterator.remove();
            }
            // 如果清理后仍空间不足,记录错误
            if (mTotalSize + neededSpace > mMaxCacheSizeInBytes) {
                VolleyLog.e("Failed to clear space in cache");
            }
        }
    }

    // 其他辅助方法...
}
  1. Network(网络):网络模块负责执行实际的网络请求操作,从服务器获取数据。Volley的网络接口为Network,默认实现类是BasicNetwork,基于HttpURLConnectionHttpClient实现网络请求。
public class BasicNetwork implements Network {
    // HTTP堆栈对象,用于发送HTTP请求
    private final HttpStack mHttpStack;
    // 重试策略,用于处理请求失败时的重试逻辑
    private final RetryPolicy mRetryPolicy;

    // 构造函数,初始化HTTP堆栈和重试策略
    public BasicNetwork(HttpStack httpStack) {
        this(httpStack, new DefaultRetryPolicy());
    }

    public BasicNetwork(HttpStack httpStack, RetryPolicy retryPolicy) {
        mHttpStack = httpStack;
        mRetryPolicy = retryPolicy;
    }

    // 执行网络请求
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        // 记录请求开始时间
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            try {
                // 构建HTTP请求
                HttpStack.HttpRequestStackEntry entry = mHttpStack.performRequest(request, new HashMap<String, String>());
                // 获取HTTP响应
                HttpResponse httpResponse = entry.response;
                // 获取响应状态码
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                // 读取响应内容
                byte[] responseContents = EntityUtils.toByteArray(httpResponse.getEntity());
                // 解析响应头部信息
                Map<String, String> responseHeaders = entry.responseHeaders;
                // 如果响应状态码在200 - 299之间,表示请求成功
                if (statusCode >= 200 && statusCode < 300) {
                    return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
                }
                // 如果响应状态码为304,表示缓存未失效
                if (statusCode == 304) {
                    return new NetworkResponse(statusCode, null, responseHeaders, true);
                }
                // 其他错误状态码,抛出异常
                throw new IOException();
            } catch (IOException e) {
                // 处理请求失败的情况,根据重试策略进行重试
                request.addMarker("network-http-attempt-failed");
                if (!request.shouldRetryNetworkException(new VolleyError(e))) {
                    throw new VolleyError(e);
                }
                if (mRetryPolicy.hasAttemptRemaining()) {
                    try {
                        mRetryPolicy.retry(new VolleyError(e));
                    } catch (VolleyError retryError) {
                        throw retryError;
                    }
                } else {
                    throw new VolleyError(e);
                }
            }
        }
    }
}
  1. ResponseDelivery(响应分发器):响应分发器负责将处理后的响应数据分发到主线程,以便更新UI或进行其他业务逻辑处理。
public class ExecutorDelivery implements ResponseDelivery {
    // 主线程的Handler,用于在主线程执行任务
    private final Handler mMainThreadHandler;
    // 后台线程的Executor,用于执行非UI相关任务
    private final Executor mExecutor;

    // 构造函数,初始化Handler和Executor
    public ExecutorDelivery(Handler mainThreadHandler) {
        mMainThreadHandler = mainThreadHandler;
        mExecutor = new Executor() {
            @Override
            public void execute(Runnable command) {
                command.run();
            }
        };
    }

    // 分发正常响应
    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    // 分发响应,并可指定在响应分发后执行的Runnable任务
    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        // 如果响应是中间响应(例如缓存过期但先返回旧数据)
        if (response.intermediate) {
            // 在后台线程执行响应分发
            mExecutor.execute(new ResponseDeliveryRunnable(request, response, runnable));
        } else {
            // 在主线程执行响应分发
            mMainThreadHandler.post(new ResponseDeliveryRunnable(request, response, runnable));
        }
    }

    // 分发错误响应
    @Override
    public void postError(Request<?> request, VolleyError error) {
        // 创建错误响应对象
        Response<?> response = Response.error(error);
        // 在主线程分发错误响应
        mMainThreadHandler.post(new ResponseDeliveryRunnable(request, response, null));
    }

    // 响应分发的Runnable任务类
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request<?> mRequest;
        private final Response<?> mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request<?> request, Response<?> response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = runnable;
        }

        @Override
        public void run() {
            // 如果请求已被取消,直接结束
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }
            // 调用请求的响应监听器处理响应
            mRequest.deliverResponse(mResponse.result);
            // 如果有指定的Runnable任务,执行该任务
            if (mRunnable != null) {
                mRunnable.run();
            }
            // 标记请求已完成响应分发
            mRequest.finish("done");
        }
    }
}

2.2 工作流程

Volley的工作流程如下:

  1. 开发者创建网络请求对象,并调用RequestQueue.add(request)方法将请求添加到请求队列中。
  2. 请求队列根据请求的shouldCache属性,将请求放入缓存队列(可缓存请求)或网络队列(不可缓存请求)。
  3. 缓存调度器CacheDispatcher从缓存队列中取出请求,检查缓存中是否存在对应数据。若存在且未过期,则直接将数据返回;若不存在或已过期,则将请求转发到网络队列。
  4. 网络调度器NetworkDispatcher从网络队列中取出请求,执行网络请求操作,从服务器获取数据。
  5. 网络请求成功后,将响应数据进行处理,并根据请求是否可缓存,决定是否更新缓存。
  6. 最后,响应分发器ResponseDelivery将处理后的响应数据分发到主线程,完成整个网络请求流程。

了解Volley的架构和工作流程是分析其性能瓶颈的基础,接下来我们将深入探讨Volley在实际使用中存在的性能问题。

三、线程池与请求队列引发的性能瓶颈

3.1 固定线程池大小的局限性

Volley使用固定大小的线程池来处理网络请求,在RequestQueue的构造函数中进行初始化:

public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
    // 省略其他参数初始化...
    // 创建网络调度器线程数组,线程池大小固定
    mNetworkDispatchers = new NetworkDispatcher[threadPoolSize];
    for (int i = 0; i < threadPoolSize; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
        mNetworkDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

这种固定线程池大小的设计在某些场景下会带来性能问题。当应用同时发起大量网络请求时,如果线程池大小设置过小,会导致请求在网络队列中大量堆积,等待时间过长,从而影响应用的响应速度。例如,在一个新闻类应用中,首页可能需要同时加载新闻列表、广告、推荐内容等多个数据,若线程池大小仅设置为默认的4个线程,这些请求可能无法及时得到处理,用户会感觉到页面加载缓慢。

另一方面,如果线程池大小设置过大,虽然可以加快请求处理速度,但会占用过多的系统资源,导致系统性能下降,甚至可能引发OOM(Out Of Memory)问题。特别是在一些配置较低的设备上,过大的线程池会使系统资源更加紧张