AndroidVideoCache源码浅析

1. 边播放边缓存

  视频播放时边播放边缓存,这样用户再次播放时可以节省流量,提高用户体验,这是视频播放很常见的需求。但是,Android的VideoView是没有提供这样的功能的。

有个开源库比较好用,github地址:https://github.com/danikula/AndroidVideoCache

2. 简述一下AndroidVideoCache的大体实现原理

  大家都知道,VideoView.setVideoPath(proxyUrl);走的也是http请求,而http底层是走tcp协议的socket实现的。AndroidVideoCache的实现就是在tcp层架一个SocketServer的代理服务器,也就是说VideoView.setVideoPath(proxyUrl);的请求是先请求到代理服务器SocketServer,然后代理服务器SocketServer再去请求真正的服务器。这样,这个代理服务器SocketServer就可以去请求真正的服务器去下载视频(AndroidVideoCache是分段下载,也就是断点续传,每次8 * 1024大小),然后将视频响应给请求。当然代理服务器SocketServer还会先判断该视频是否已经下载缓存完,如果已经下载缓存完就直接使用本地的缓存视频。

3. AndroidVideoCache源代码简述

  1)App全局架设一个本地Socket代理服务器 

    InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
    this.serverSocket = new ServerSocket(0, 8, inetAddress);

  2)getProxyUrl方法,先判断是否已经缓存过了,如果是直接使用本地缓存。如果否,就判断代理服务器SocketServer是否可用

    public String getProxyUrl(String url, boolean allowCachedFileUri) {
      if (allowCachedFileUri && isCached(url)) {  //缓存ok
        File cacheFile = getCacheFile(url);
        touchFileSafely(cacheFile);  //touch一下文件,让该文件时间最新,用于LRUcache缓存,LRUcache缓存是按照时间排序的。
        return Uri.fromFile(cacheFile).toString();
      }
        return isAlive() ? appendToProxyUrl(url) : url;  //isAlive()方法里就是ping了一下代理服务器SocketServer,看看是否可用?是,包装成proxyURL,否,使用来源的url,不缓存
      }

  3)processSocket处理所有的请求进来的Socket,包括ping的和VideoView.setVideoPath(proxyUrl)的Socket

      private void processSocket(Socket socket) {
        GetRequest request = GetRequest.read(socket.getInputStream());
        LOG.debug("Request to cache proxy:" + request);
        String url = ProxyCacheUtils.decode(request.uri);
        if (pinger.isPingRequest(url)) {  //如果是ping的,返回ping响应
          pinger.responseToPing(socket);
        } else {  //视频的Socket,启动对应的Client去处理
          HttpProxyCacheServerClients clients = getClients(url);
          clients.processRequest(request, socket);
        }
      }

  4)clients.processRequest(request, socket);方法的实现

    public void processRequest(GetRequest request, Socket socket) throws ProxyCacheException, IOException {
      startProcessRequest();  //这个方法其实就是获取一个proxyCache对应
      try {
        clientsCount.incrementAndGet();
        proxyCache.processRequest(request, socket);  //交给proxyCache处理
      } finally {
        finishProcessRequest();
      }
    }

    private synchronized void startProcessRequest() throws ProxyCacheException {
      proxyCache = proxyCache == null ? newHttpProxyCache() : proxyCache;  //newHttpProxyCache() 这个方法准备一下url,缓存file路径,监听器等等。。。
    }

  5)proxyCache.processRequest(request, socket);方法的实现

    public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
      OutputStream out = new BufferedOutputStream(socket.getOutputStream());  //创建一个Socket的响应流
      String responseHeaders = newResponseHeaders(request);
      out.write(responseHeaders.getBytes("UTF-8"));

      long offset = request.rangeOffset;
      if (isUseCache(request)) {  //判断是否使用缓存?是
        responseWithCache(out, offset);  //缓存并响应  
      } else {  //不使用缓存
        responseWithoutCache(out, offset);  //不缓存并响应
      }
    }

  6)responseWithCache(out, offset);方法的实现

    while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {
      readSourceAsync();  //异步请求真正的服务器读取该段数据,读取完会通知
      waitForSourceData();  //加锁等待readSourceAsync()的通知
      checkReadSourceErrorsCount();  //校验错误
    }

  6-1)readSourceAsync();方法最后回调用readSource()方法 

    private void readSource() {
      long sourceAvailable = -1;
      long offset = 0;
      try {
        offset = cache.available();
        source.open(offset);
        sourceAvailable = source.length();
        byte[] buffer = new byte[ProxyCacheUtils.DEFAULT_BUFFER_SIZE];  //每次读取这么多数据ProxyCacheUtils.DEFAULT_BUFFER_SIZE
        int readBytes;
        while ((readBytes = source.read(buffer)) != -1) {
          synchronized (stopLock) {
            if (isStopped()) {
            return;
          }
          cache.append(buffer, readBytes);  //追加到cache
        }
        offset += readBytes;
        notifyNewCacheDataAvailable(offset, sourceAvailable);  //通知有数据可用,也就是唤醒waitForSourceData()方法,让while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped)继续判断执行下去
      }
        tryComplete();  //这个方法判断文件是否已经下载完成了。cache的文件是一个临时的.download为后缀的文件,分段缓存完成整个视频文件后,修改文件名为与请求url关联的文件名
        onSourceRead();
      } catch (Throwable e) {
        readSourceErrorsCount.incrementAndGet();
        onError(e);
      } finally {
        closeSource();
        notifyNewCacheDataAvailable(offset, sourceAvailable);
      }
    }

  7)responseWithoutCache(out, offset);方法的实现

    private void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
      HttpUrlSource newSourceNoCache = new HttpUrlSource(this.source);
      try {
        newSourceNoCache.open((int) offset);  //这个方法里面打开HttpURLConnection
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int readBytes;
        while ((readBytes = newSourceNoCache.read(buffer)) != -1) {  //read读取HttpURLConnection的getInputStream()的数据
        out.write(buffer, 0, readBytes);
        offset += readBytes;
      }
      out.flush();
    } finally {
      newSourceNoCache.close();
    }
  }

  

原文地址:https://www.cnblogs.com/yongfengnice/p/9188700.html