標籤:deb uri connect string 代理服務 tostring rom tar socket
1. 邊播放邊緩衝
視頻播放時邊播放邊緩衝,這樣使用者再次播放時可以節省流量,提高使用者體驗,這是視頻播放很常見的需求。但是,Android的VideoView是沒有提供這樣的功能的。
有個開源庫比較好用,github地址:https://github.com/danikula/AndroidVideoCache
2. 簡述一下AndroidVideoCache的大體實現原理
大家都知道,VideoView.setVideoPath(proxyUrl);走的也是http請求,而http底層是走tcp協議的socket實現的。AndroidVideoCache的實現就是在tcp層架一個SocketServer的Proxy 伺服器,也就是說VideoView.setVideoPath(proxyUrl);的請求是先請求到Proxy 伺服器SocketServer,然後Proxy 伺服器SocketServer再去請求真正的伺服器。這樣,這個Proxy 伺服器SocketServer就可以去請求真正的伺服器去下載視頻(AndroidVideoCache是分段下載,也就是斷點續傳,每次8 * 1024大小),然後將視頻響應給請求。當然Proxy 伺服器SocketServer還會先判斷該視頻是否已經下載快取完,如果已經下載快取完就直接使用本地的緩衝視頻。
3. AndroidVideoCache原始碼簡述
1)App全域架設一個本地SocketProxy 伺服器
InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
this.serverSocket = new ServerSocket(0, 8, inetAddress);
2)getProxyUrl方法,先判斷是否已經緩衝過了,如果是直接使用本機快取。如果否,就判斷Proxy 伺服器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了一下Proxy 伺服器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();
}
}
AndroidVideoCache源碼淺析