안 드 로 이 드 비디오 리퀘스트 의 실현 코드(방송 하면 서 캐 시)
일부 유명한 동 영상 app 클 라 이언 트(유쿠,아이 치 이)가 동 영상 을 재생 할 때 캐 시 진도(2 급 진도 캐 시)가 있 고 짧 은 동 영상 app 도 있 으 며 모두 방송 하면 서 느 린 처 리 를 한다.그리고 파일 캐 시가 끝나 면 다시 재생 하면 네트워크 에 요청 하지 않 고 로 컬 파일 을 직접 재생 합 니 다.절 차 를 절약 할 뿐만 아니 라 로드 속도 도 높 였 다.
오늘 우 리 는 이 사 이 드 캐 시 를 실현 하 는 프레임 워 크 를 연구 토론 하 러 왔 다.왜냐하면 그것 은 어떠한 업무 논리 와 결합 되 지 않 기 때문이다.
오픈 소스 프로젝트
현재 비교적 좋 은 개원 프로젝트 는:https://github.com/danikula/AndroidVideoCache
코드 의 구조 도 잘 썼 습 니 다.네트워크 용 httpurlconnect,파일 캐 시 처리,파일 최대 전략,감청 처리,정지점 전송,프 록 시 서비스 등 이 있 습 니 다.연구 하고 읽 을 만하 다.
개인 적 으로 프로젝트 중 에 최적화 해 야 할 점 이 몇 가지 있다 고 생각 합 니 다.오늘 은 이 몇 가 지 를 처리 하고 원 리 를 간략하게 분석 하 겠 습 니 다.
최적화 점:
1.파일 의 캐 시가 제한 을 초과 한 후 lru 알고리즘 에 따라 삭제 되 지 않 았 습 니 다.
Files 클래스.
모 바 일 장치 에서 file.setLastModified()방법 은 밀리초 단위 의 시간 처 리 를 지원 하지 않 기 때문에 제한 크기 를 초과 한 후 오래된 것 을 삭제 해 야 하 는데 삭제 하지 않 고 이상 을 던 졌 습 니 다.주동 적 으로 던 진 이상 을 주석 하면 됩 니 다.파일 수정 시간 이 맞 으 니까.
static void setLastModifiedNow(File file) throws IOException {
if (file.exists()) {
long now = System.currentTimeMillis();
boolean modified = file.setLastModified(now/1000*1000); // on some devices (e.g. Nexus 5) doesn't work
if (!modified) {
modify(file);
// if (file.lastModified() < now) {
// VideoCacheLog.debug("LruDiskUsage", "modified not ok ");
// throw new IOException("Error set last modified date to " + file);
// }else{
// VideoCacheLog.debug("LruDiskUsage", "modified ok ");
// }
}
}
}
2.재생 기 에 되 돌아 오 는 http 응답 헤더 메 시 지 를 처리 하고 응답 헤더 메시지 의 가 져 오기 처 리 를 head 요청 으로 변경 합 니 다(서버 지원 이 필요 합 니 다)HttpUrlSource 류.fetch ContentInfo 방법 은 비디오 파일 의 Content-Type,Content-Length 정 보 를 가 져 오 는 것 입 니 다.재생 기 를 재생 할 때 재생 기 에 http 응답 헤드 정 보 를 조립 하기 위해 서 입 니 다.따라서 이 조각 은 데이터베이스 로 저장 해 야 합 니 다.재생 기 는 재생 할 때마다 여기 서 가 져 오지 마 십시오.요청 한 횟수 를 줄 이 고 데 이 터 를 절약 할 수 있 습 니 다.헤드 정보 만 필요 하고 응답 체 가 필요 없 기 때문에 우 리 는 얻 을 때 HEAD 방법 을 직접 사용 할 수 있 습 니 다.그래서 코드 에 openConnection ForHeader 를 추가 하 는 방법 은 다음 과 같 습 니 다.
private void fetchContentInfo() throws ProxyCacheException {
VideoCacheLog.debug(TAG,"Read content info from " + sourceInfo.url);
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection = openConnectionForHeader(20000);
long length = getContentLength(urlConnection);
String mime = urlConnection.getContentType();
inputStream = urlConnection.getInputStream();
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
VideoCacheLog.debug(TAG,"Source info fetched: " + sourceInfo);
} catch (IOException e) {
VideoCacheLog.error(TAG,"Error fetching info from " + sourceInfo.url ,e);
} finally {
ProxyCacheUtils.close(inputStream);
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
// for HEAD
private HttpURLConnection openConnectionForHeader(int timeout) throws IOException, ProxyCacheException {
HttpURLConnection connection;
boolean redirected;
int redirectCount = 0;
String url = this.sourceInfo.url;
do {
VideoCacheLog.debug(TAG, "Open connection for header to " + url);
connection = (HttpURLConnection) new URL(url).openConnection();
if (timeout > 0) {
connection.setConnectTimeout(timeout);
connection.setReadTimeout(timeout);
}
// , BODY,
connection.setRequestMethod("HEAD");
int code = connection.getResponseCode();
redirected = code == HTTP_MOVED_PERM || code == HTTP_MOVED_TEMP || code == HTTP_SEE_OTHER;
if (redirected) {
url = connection.getHeaderField("Location");
VideoCacheLog.debug(TAG,"Redirect to:" + url);
redirectCount++;
connection.disconnect();
VideoCacheLog.debug(TAG,"Redirect closed:" + url);
}
if (redirectCount > MAX_REDIRECTS) {
throw new ProxyCacheException("Too many redirects: " + redirectCount);
}
} while (redirected);
return connection;
}
3.네트워크 라 이브 러 리 를 okhttp 로 교체 합 니 다(대부분의 항목 은 okhttp 를 네트워크 요청 라 이브 러 리 로 하기 때 문 입 니 다)왜 우리 가 바 꿔 야 되 지?!첫째,OKHttp 는 효율 적 인 HTTP 클 라 이언 트 로 같은 주 소 를 연결 하 는 링크 가 같은 socket 을 공유 하 는 것 을 지원 합 니 다.연결 풀 을 통 해 응답 지연 을 줄 이 고 투명 한 GZIP 압축,캐 시 요청 등 장점 도 있 습 니 다.그 핵심 은 주로 경로,연결 프로 토 콜,차단기,대리,안전성 인증,연결 풀 과 네트워크 어댑터 가 있 습 니 다.차단 기 는 주로 추 가 를 말 합 니 다.요청 이나 응답 하 는 머리 정 보 를 제거 하거나 변환 합 니 다.안 드 로 이 드 개발 의 인정 을 받 았 습 니 다.둘째,대부분의 app 은 OKHttp 를 사용 하고 구 글 은 이 를 안 드 로 이 드 소스 코드 에 포함 시 킵 니 다.셋째,이 작성 자 코드 에 사 용 된 httpurlconnet 은 HttpUrlSource 에서 다음 과 같은 부분 이 있 습 니 다.
@Override
public void close() throws ProxyCacheException {
if (connection != null) {
try {
connection.disconnect();
} catch (NullPointerException | IllegalArgumentException e) {
String message = "Wait... but why? WTF!? " +
"Really shouldn't happen any more after fixing https://github.com/danikula/AndroidVideoCache/issues/43. " +
"If you read it on your device log, please, notify me [email protected] or create issue here " +
"https://github.com/danikula/AndroidVideoCache/issues.";
throw new RuntimeException(message, e);
} catch (ArrayIndexOutOfBoundsException e) {
VideoCacheLog.error(TAG,"Error closing connection correctly. Should happen only on Android L. " +
"If anybody know how to fix it, please visit https://github.com/danikula/AndroidVideoCache/issues/88. " +
"Until good solution is not know, just ignore this issue :(", e);
}
}
}
okhttp 와 같은 우수한 네트워크 오픈 소스 프로젝트 가 없 기 전에 안 드 로 이 드 개발 은 httpurlconnet 이나 httpclient 를 사용 합 니 다.일부 휴대 전 화 는 이 문 제 를 만 날 수 있 습 니 다.여기 서 사용 하 는 copile'com.squareup.okhttp:okhttp:2.7.5'버 전 으로 이러한 기능 을 실현 합 니 다.원작 자의 구조 사고방식 에서 우 리 는 Source 인 터 페 이 스 를 실현 하 는 종류 인 OkHttpUrlSource 만 늘 리 면 된다.이 를 통 해 작가 의 코드 구조 가 괜찮다 는 것 을 알 수 있다.물론 우 리 는 앞에서 향상 시 킨 최적화 점 2 중의 문 제 를 처리 해 야 한다.프로젝트 에 HttpUrlSource 를 사용 하 는 모든 곳 을 OkHttpUrlSource 로 바 꾸 면 됩 니 다.
원본 코드 는 다음 과 같 습 니 다.
/**
* ================================================
* :
* :
* :2017/4/13- 12:03
* : Android HttpURLConnection.disconnect() ,
* MediaPlayer , okhttp HttpURLConnection
*/
public class OkHttpUrlSource implements Source {
private static final String TAG = OkHttpUrlSource.class.getSimpleName();
private static final int MAX_REDIRECTS = 5;
private final SourceInfoStorage sourceInfoStorage;
private SourceInfo sourceInfo;
private OkHttpClient okHttpClient = new OkHttpClient();
private Call requestCall = null;
private InputStream inputStream;
public OkHttpUrlSource(String url) {
this(url, SourceInfoStorageFactory.newEmptySourceInfoStorage());
}
public OkHttpUrlSource(String url, SourceInfoStorage sourceInfoStorage) {
this.sourceInfoStorage = checkNotNull(sourceInfoStorage);
SourceInfo sourceInfo = sourceInfoStorage.get(url);
this.sourceInfo = sourceInfo != null ? sourceInfo :
new SourceInfo(url, Integer.MIN_VALUE, ProxyCacheUtils.getSupposablyMime(url));
}
public OkHttpUrlSource(OkHttpUrlSource source) {
this.sourceInfo = source.sourceInfo;
this.sourceInfoStorage = source.sourceInfoStorage;
}
@Override
public synchronized long length() throws ProxyCacheException {
if (sourceInfo.length == Integer.MIN_VALUE) {
fetchContentInfo();
}
return sourceInfo.length;
}
@Override
public void open(long offset) throws ProxyCacheException {
try {
Response response = openConnection(offset, -1);
String mime = response.header("Content-Type");
this.inputStream = new BufferedInputStream(response.body().byteStream(), DEFAULT_BUFFER_SIZE);
long length = readSourceAvailableBytes(response, offset, response.code());
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
} catch (IOException e) {
throw new ProxyCacheException("Error opening okHttpClient for " + sourceInfo.url + " with offset " + offset, e);
}
}
private long readSourceAvailableBytes(Response response, long offset, int responseCode) throws IOException {
long contentLength = getContentLength(response);
return responseCode == HTTP_OK ? contentLength
: responseCode == HTTP_PARTIAL ? contentLength + offset : sourceInfo.length;
}
private long getContentLength(Response response) {
String contentLengthValue = response.header("Content-Length");
return contentLengthValue == null ? -1 : Long.parseLong(contentLengthValue);
}
@Override
public void close() throws ProxyCacheException {
if (okHttpClient != null && inputStream != null && requestCall != null) {
try {
inputStream.close();
requestCall.cancel();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
}
@Override
public int read(byte[] buffer) throws ProxyCacheException {
if (inputStream == null) {
throw new ProxyCacheException("Error reading data from " + sourceInfo.url + ": okHttpClient is absent!");
}
try {
return inputStream.read(buffer, 0, buffer.length);
} catch (InterruptedIOException e) {
throw new InterruptedProxyCacheException("Reading source " + sourceInfo.url + " is interrupted", e);
} catch (IOException e) {
throw new ProxyCacheException("Error reading data from " + sourceInfo.url, e);
}
}
private void fetchContentInfo() throws ProxyCacheException {
VideoCacheLog.debug(TAG, "Read content info from " + sourceInfo.url);
Response response = null;
InputStream inputStream = null;
try {
response = openConnectionForHeader(20000);
if (response == null || !response.isSuccessful()) {
throw new ProxyCacheException("Fail to fetchContentInfo: " + sourceInfo.url);
}
long length = getContentLength(response);
String mime = response.header("Content-Type", "application/mp4");
inputStream = response.body().byteStream();
this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
VideoCacheLog.info(TAG, "Content info for `" + sourceInfo.url + "`: mime: " + mime + ", content-length: " + length);
} catch (IOException e) {
VideoCacheLog.error(TAG, "Error fetching info from " + sourceInfo.url, e);
} finally {
ProxyCacheUtils.close(inputStream);
if (response != null && requestCall != null) {
requestCall.cancel();
}
}
}
// for HEAD
private Response openConnectionForHeader(int timeout) throws IOException, ProxyCacheException {
if (timeout > 0) {
// okHttpClient.setConnectTimeout(timeout, TimeUnit.MILLISECONDS);
// okHttpClient.setReadTimeout(timeout, TimeUnit.MILLISECONDS);
// okHttpClient.setWriteTimeout(timeout, TimeUnit.MILLISECONDS);
}
Response response;
boolean isRedirect = false;
String newUrl = this.sourceInfo.url;
int redirectCount = 0;
do {
// , BODY,
Request request = new Request.Builder()
.head()
.url(newUrl)
.build();
requestCall = okHttpClient.newCall(request);
response = requestCall.execute();
if (response.isRedirect()) {
newUrl = response.header("Location");
VideoCacheLog.debug(TAG, "Redirect to:" + newUrl);
isRedirect = response.isRedirect();
redirectCount++;
requestCall.cancel();
VideoCacheLog.debug(TAG, "Redirect closed:" + newUrl);
}
if (redirectCount > MAX_REDIRECTS) {
throw new ProxyCacheException("Too many redirects: " + redirectCount);
}
} while (isRedirect);
return response;
}
private Response openConnection(long offset, int timeout) throws IOException, ProxyCacheException {
if (timeout > 0) {
// okHttpClient.setConnectTimeout(timeout, TimeUnit.MILLISECONDS);
// okHttpClient.setReadTimeout(timeout, TimeUnit.MILLISECONDS);
// okHttpClient.setWriteTimeout(timeout, TimeUnit.MILLISECONDS);
}
Response response;
boolean isRedirect = false;
String newUrl = this.sourceInfo.url;
int redirectCount = 0;
do {
VideoCacheLog.debug(TAG, "Open connection" + (offset > 0 ? " with offset " + offset : "") + " to " + sourceInfo.url);
Request.Builder requestBuilder = new Request.Builder()
.get()
.url(newUrl);
if (offset > 0) {
requestBuilder.addHeader("Range", "bytes=" + offset + "-");
}
requestCall = okHttpClient.newCall(requestBuilder.build());
response = requestCall.execute();
if (response.isRedirect()) {
newUrl = response.header("Location");
isRedirect = response.isRedirect();
redirectCount++;
}
if (redirectCount > MAX_REDIRECTS) {
throw new ProxyCacheException("Too many redirects: " + redirectCount);
}
} while (isRedirect);
return response;
}
public synchronized String getMime() throws ProxyCacheException {
if (TextUtils.isEmpty(sourceInfo.mime)) {
fetchContentInfo();
}
return sourceInfo.mime;
}
public String getUrl() {
return sourceInfo.url;
}
@Override
public String toString() {
return "OkHttpUrlSource{sourceInfo='" + sourceInfo + "}";
}
}
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.