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();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库