Android 다 중 스 레 드 정지점 전송 다운로드 기능 구현 코드
사실 단점 속전 의 원 리 는 간단 하 다.말 그대로 단점 속전 이란 멈 춘 곳 에서 다시 다운로드 하 는 것 이다.
정지점:스 레 드 가 멈 춘 위치.
속보:정 지 된 위치 에서 다시 다운로드 합 니 다.
코드 로 해석 하면:
정지점:현재 스 레 드 가 다운로드 한 데이터 길이 입 니 다.
전송:서버 에 마지막 스 레 드 정지 위치 후의 데 이 터 를 요청 합 니 다.
원 리 를 알 았 으 니 기능 실현 도 간단 하 다.스 레 드 가 멈 출 때마다 다운로드 한 데이터 길 이 를 기록 파일 에 기록 하고 다시 다운로드 할 때 기록 파일 에서 다운로드 한 길 이 를 읽 습 니 다.이 길이 가 필요 한 단점 입 니 다.
리 셋 의 실현 도 간단 합 니 다.네트워크 요청 파 라 메 터 를 설정 하여 서버 에 지정 한 위치 에서 데 이 터 를 읽 어 달라 고 요청 할 수 있 습 니 다.
이 두 가지 기능 을 실현 하려 면 HttpURLconnection 에 있 는 setRequestProperty 방법 만 사용 하면 됩 니 다.
public void setRequestProperty(String field, String newValue)
다음 과 같이 서버 에 500-1000 사이 의 500 개의 byte 를 요청 합 니 다.
conn.setRequestProperty("Range", "bytes=" + 500 + "-" + 1000);
이상 은 계속 전송 하 는 일부 수요 일 뿐 데 이 터 를 다운로드 할 때 데 이 터 를 파일 에 기록 해 야 합 니 다.일반 파일 대상 은 지정 한 위치 에서 데 이 터 를 기록 하 는 기능 을 제공 하지 않 습 니 다.이 럴 때 RandomAccessFile 을 사용 하여 지정 한 위치 에서 파일 에 데 이 터 를 기록 하 는 기능 을 수행 해 야 합 니 다.
public void seek(long offset)
다음 과 같이 파일 의 100 번 째 byte 이후 데 이 터 를 쓰기 시작 합 니 다.
raFile.seek(100);
데 이 터 를 쓰기 시작 할 때 는 RandomAccessFile 의 또 다른 방법 이 필요 합 니 다.
public void write(byte[] buffer, int byteOffset, int byteCount)
이 방법 은 OutputStream 의 write 사용 과 똑 같 습 니 다...이상 이 바로 단점 속전 의 원리 이다.
다 중 스 레 드 정지점 전송
한편,다 중 스 레 드 정지점 의 속전 은 바로 단일 스 레 드 의 정지점 에서 연장 되 는 것 이다.다 중 스 레 드 정지점 의 속전 은 전체 파일 을 몇 부분 으로 나 누고 모든 부분 은 하나의 스 레 드 에서 다운 로드 를 실행 하 며 모든 다운로드 스 레 드 는 정지점 의 속전 기능 을 실현 해 야 한다.
파일 분할 기능 을 구현 하기 위해 서 는 HttpURLconnection 의 다른 방법 을 사용 해 야 합 니 다.
public int getContentLength()
요청 이 성공 하면 이 방법 을 통 해 파일 의 총 길 이 를 얻 을 수 있 습 니 다.스 레 드 마다 다운로드 크기=fileLength/THREADNUM
다음 그림 에서 보 듯 이 다 중 스 레 드 다운로드 모델 을 설명 합 니 다.
다 중 스 레 드 정지점 에서 다운로드 할 때 주의해 야 할 점 이 있 습 니 다.
파일 은 여러 부분 으로 나 뉘 어 서로 다른 스 레 드 와 동시에 다운로드 되 기 때문에 모든 스 레 드 는 하나의 정지점 기록 과 하나의 스 레 드 완료 상태의 기록 이 필요 합 니 다.
다음 그림 에서 보 듯 이:
모든 스 레 드 의 다운로드 상태 가 완료 되 었 을 때 만 파일 이 다운로드 되 었 음 을 표시 할 수 있 습 니 다.
기록 을 실현 하 는 방법 은 다양 합 니 다.저 는 JDK 가 자체 적 으로 가지 고 있 는 Properties 류 를 사용 하여 다운로드 인 자 를 기록 합 니 다.
정지점 전송 구조
원리 에 대한 이 해 를 통 해 단점 전송 도구 류 의 기본 구조 도 를 신속하게 설계 할 수 있다.
IDownloadListener.java
package com.arialyy.frame.http.inf;
import java.net.HttpURLConnection;
/**
*
*/
public interface IDownloadListener {
/**
*
*/
public void onCancel();
/**
*
*/
public void onFail();
/**
* , HttpURLConnection
*/
public void onPreDownload(HttpURLConnection connection);
/**
*
*/
public void onProgress(long currentLocation);
/**
*
*/
public void onChildComplete(long finishLocation);
/**
*
*/
public void onStart(long startLocation);
/**
*
*/
public void onChildResume(long resumeLocation);
/**
*
*/
public void onResume(long resumeLocation);
/**
*
*/
public void onStop(long stopLocation);
/**
*
*/
public void onComplete();
}
이 종 류 는 다운로드 감청 인터페이스 이다.DownloadListener.java
import java.net.HttpURLConnection;
/**
*
*/
public class DownloadListener implements IDownloadListener {
@Override
public void onResume(long resumeLocation) {
}
@Override
public void onCancel() {
}
@Override
public void onFail() {
}
@Override
public void onPreDownload(HttpURLConnection connection) {
}
@Override
public void onProgress(long currentLocation) {
}
@Override
public void onChildComplete(long finishLocation) {
}
@Override
public void onStart(long startLocation) {
}
@Override
public void onChildResume(long resumeLocation) {
}
@Override
public void onStop(long stopLocation) {
}
@Override
public void onComplete() {
}
}
매개 변수 실체 다운로드
/**
*
*/
private class DownloadEntity {
//
long fileSize;
//
String downloadUrl;
// Id
int threadId;
//
long startLocation;
//
long endLocation;
//
File tempFile;
Context context;
public DownloadEntity(Context context, long fileSize, String downloadUrl, File file, int threadId, long startLocation, long endLocation) {
this.fileSize = fileSize;
this.downloadUrl = downloadUrl;
this.tempFile = file;
this.threadId = threadId;
this.startLocation = startLocation;
this.endLocation = endLocation;
this.context = context;
}
}
이 종 류 는 다운로드 정보 설정 류 로 모든 하위 스 레 드 의 다운로드 에는 다운로드 실 체 를 다운로드 하여 다운로드 정 보 를 설정 해 야 합 니 다.작업 스 레 드 다운로드
/**
*
*/
private class DownLoadTask implements Runnable {
private static final String TAG = "DownLoadTask";
private DownloadEntity dEntity;
private String configFPath;
public DownLoadTask(DownloadEntity downloadInfo) {
this.dEntity = downloadInfo;
configFPath = dEntity.context.getFilesDir().getPath() + "/temp/" + dEntity.tempFile.getName() + ".properties";
}
@Override
public void run() {
try {
L.d(TAG, " _" + dEntity.threadId + "_ 【" + " : " + dEntity.startLocation + ", :" + dEntity.endLocation + "】");
URL url = new URL(dEntity.downloadUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//
conn.setRequestProperty("Range", "bytes=" + dEntity.startLocation + "-" + dEntity.endLocation);
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setConnectTimeout(TIME_OUT);
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setReadTimeout(2000); // ,
InputStream is = conn.getInputStream();
//
RandomAccessFile file = new RandomAccessFile(dEntity.tempFile, "rwd");
//
file.seek(dEntity.startLocation);
byte[] buffer = new byte[1024];
int len;
//
long currentLocation = dEntity.startLocation;
while ((len = is.read(buffer)) != -1) {
if (isCancel) {
L.d(TAG, "++++++++++ thread_" + dEntity.threadId + "_cancel ++++++++++");
break;
}
if (isStop) {
break;
}
//
file.write(buffer, 0, len);
synchronized (DownLoadUtil.this) {
mCurrentLocation += len;
mListener.onProgress(mCurrentLocation);
}
currentLocation += len;
}
file.close();
is.close();
if (isCancel) {
synchronized (DownLoadUtil.this) {
mCancelNum++;
if (mCancelNum == THREAD_NUM) {
File configFile = new File(configFPath);
if (configFile.exists()) {
configFile.delete();
}
if (dEntity.tempFile.exists()) {
dEntity.tempFile.delete();
}
L.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++");
isDownloading = false;
mListener.onCancel();
System.gc();
}
}
return;
}
//
if (isStop) {
synchronized (DownLoadUtil.this) {
mStopNum++;
String location = String.valueOf(currentLocation);
L.i(TAG, "thread_" + dEntity.threadId + "_stop, stop location ==> " + currentLocation);
writeConfig(dEntity.tempFile.getName() + "_record_" + dEntity.threadId, location);
if (mStopNum == THREAD_NUM) {
L.d(TAG, "++++++++++++++++ onStop +++++++++++++++++");
isDownloading = false;
mListener.onStop(mCurrentLocation);
System.gc();
}
}
return;
}
L.i(TAG, " 【" + dEntity.threadId + "】 ");
writeConfig(dEntity.tempFile.getName() + "_state_" + dEntity.threadId, 1 + "");
mListener.onChildComplete(dEntity.endLocation);
mCompleteThreadNum++;
if (mCompleteThreadNum == THREAD_NUM) {
File configFile = new File(configFPath);
if (configFile.exists()) {
configFile.delete();
}
mListener.onComplete();
isDownloading = false;
System.gc();
}
} catch (MalformedURLException e) {
e.printStackTrace();
isDownloading = false;
mListener.onFail();
} catch (IOException e) {
FL.e(this, " 【" + dEntity.downloadUrl + "】" + FL.getPrintException(e));
isDownloading = false;
mListener.onFail();
} catch (Exception e) {
FL.e(this, " " + FL.getPrintException(e));
isDownloading = false;
mListener.onFail();
}
}
이것 은 모든 다운로드 하위 스 레 드 의 다운로드 작업 클래스 입 니 다.하위 스 레 드 는 다운로드 실 체 를 통 해 모든 스 레 드 를 다운로드 설정 합 니 다.다 중 정지점 에서 전송 을 중단 하 는 개념 에서 정지 상 태 를 나타 내 고 회복 은 스 레 드 가 기 록 된 정지점 에서 다시 다운로드 하 는 것 을 나타 내기 때문에 스 레 드 가 정지 상태 에 있 을 때 기록 파일 을 삭제 할 수 없습니다.다운로드 입구
/**
* ,
*
* @param context , context
* @param downloadUrl
* @param filePath
* @param downloadListener {@link DownloadListener}
*/
public void download(final Context context, @NonNull final String downloadUrl, @NonNull final String filePath,
@NonNull final DownloadListener downloadListener) {
isDownloading = true;
mCurrentLocation = 0;
isStop = false;
isCancel = false;
mCancelNum = 0;
mStopNum = 0;
final File dFile = new File(filePath);
//
final File configFile = new File(context.getFilesDir().getPath() + "/temp/" + dFile.getName() + ".properties");
try {
if (!configFile.exists()) { // ,
newTask = true;
FileUtil.createFile(configFile.getPath());
} else {
newTask = false;
}
} catch (Exception e) {
e.printStackTrace();
mListener.onFail();
return;
}
newTask = !dFile.exists();
new Thread(new Runnable() {
@Override
public void run() {
try {
mListener = downloadListener;
URL url = new URL(downloadUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setConnectTimeout(TIME_OUT);
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.connect();
int len = conn.getContentLength();
if (len < 0) { //
mListener.onFail();
return;
}
int code = conn.getResponseCode();
if (code == 200) {
int fileLength = conn.getContentLength();
//
FileUtil.createFile(filePath);
RandomAccessFile file = new RandomAccessFile(filePath, "rwd");
//
file.setLength(fileLength);
mListener.onPreDownload(conn);
//
Properties pro = null;
pro = Util.loadConfig(configFile);
int blockSize = fileLength / THREAD_NUM;
SparseArray<Thread> tasks = new SparseArray<>();
for (int i = 0; i < THREAD_NUM; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
Object state = pro.getProperty(dFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) { //
mCurrentLocation += endL - startL;
L.d(TAG, "++++++++++ _" + i + "_ ++++++++++");
mCompleteThreadNum++;
if (mCompleteThreadNum == THREAD_NUM) {
if (configFile.exists()) {
configFile.delete();
}
mListener.onComplete();
isDownloading = false;
System.gc();
return;
}
continue;
}
//
Object record = pro.getProperty(dFile.getName() + "_record_" + i);
if (!newTask && record != null && Long.parseLong(record + "") > 0) { // ,
Long r = Long.parseLong(record + "");
mCurrentLocation += r - startL;
L.d(TAG, "++++++++++ _" + i + "_ ++++++++++");
mListener.onChildResume(r);
startL = r;
}
if (i == (THREAD_NUM - 1)) {
endL = fileLength;// ,
}
DownloadEntity entity = new DownloadEntity(context, fileLength, downloadUrl, dFile, i, startL, endL);
DownLoadTask task = new DownLoadTask(entity);
tasks.put(i, new Thread(task));
}
if (mCurrentLocation > 0) {
mListener.onResume(mCurrentLocation);
} else {
mListener.onStart(mCurrentLocation);
}
for (int i = 0, count = tasks.size(); i < count; i++) {
Thread task = tasks.get(i);
if (task != null) {
task.start();
}
}
} else {
FL.e(TAG, " , :" + code);
isDownloading = false;
System.gc();
mListener.onFail();
}
} catch (IOException e) {
FL.e(this, " 【downloadUrl:" + downloadUrl + "】
【filePath:" + filePath + "】" + FL.getPrintException(e));
isDownloading = false;
mListener.onFail();
}
}
}).start();
}
사실 할 말 도 없 는데,주석 은 이미 완전 하 니,두 가 지 를 주의해 야 한다.1.다운로드 복원 시:다운로드 한 파일 크기=이 스 레 드 의 마지막 정지점 위치-이 스 레 드 의 시작 다운로드 위치;
2.다운로드 파일 의 완전 성 을 확보 하기 위해 기록 파일 이 존재 하지 않 으 면 다시 다운로드 해 야 합 니 다.
최종 효과:
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 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에 따라 라이센스가 부여됩니다.