okhttp断点下载

  创建一个DownloadUtils类继承AsyncTask,并实现doInBackground()、onProgressUpdate()、onPostExecute()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DownloadUtils extends AsyncTask<String, Integer, Integer> {
@Override
protected Integer doInBackground(String... strings) {
}

@Override
protected void onProgressUpdate(Integer... values) {
}

@Override
protected void onPostExecute(Integer integer) {
}
}

  添加一个接口类,用于下载回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DownloadUtils extends AsyncTask<String, Integer, Integer> {
public interface DownloadListener {
// 下载进度
void onProgress(int progress);

// 下载成功
void onSuccess(File downloadCacheFile);

// 下载失败
void onFailed();

// 下载暂停
void onPaused();

// 下载取消
void onCanceled();
}
}

  使用该类创建对象时,要求传入下载回调接口

1
2
3
4
5
6
7
public class DownloadUtils extends AsyncTask<String, Integer, Integer> {
private final DownloadListener downloadListener; // 下载监听

public DownloadUtils(DownloadListener downloadListener) {
this.downloadListener = downloadListener;
}
}

  注意执行下载任务时一定要在传入的数组中添加三个顺序固定的参数,方法中发起请求时,在请求头中添加了RANGE参数,该参数用作指定下载区间,重启断点续传;使用RandomAccessFile类写入数据,可以指定从目标文件的哪个位置开始插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public class DownloadUtils extends AsyncTask<String, Integer, Integer> {
/**
* 通过execute执行时,需要在传入的数组中添加三个参数,且顺序固定
* 1. 文件下载链接
* 2. 存放下载文件的目录的路径
* 3. 文件名
*/
@Override
protected Integer doInBackground(String... strings) {
// 下载链接
String downloadUrl = strings[0];
// 文件所在目录的路径
String folderPath = strings[1];
// 文件名
String fileName = strings[2];

// 如果下载链接为空,直接return出去
if (downloadUrl == null || TextUtils.isEmpty(downloadUrl))
return null;
// 如果所在目录的路径不正确
if (folderPath == null || TextUtils.isEmpty(folderPath))
folderPath = Environment.getExternalStorageDirectory().getAbsolutePath();
// 如果文件名不正确
if (fileName == null || TextUtils.isEmpty(fileName))
fileName = getDownloadFileName(downloadUrl);

// 下载到本地的文件路径
String downloadFilePath = folderPath + "/" + fileName;
// 下载到本地的文件
downloadFile = new File(downloadFilePath);

// 下载到本地的缓存文件路径
String downloadCacheFilePath = folderPath + "/" + fileName + "_cache";
// 下载到本地的缓存文件
downloadCacheFile = new File(downloadCacheFilePath);

// 已经下载的缓存文件大小
long downloadCacheFileLength = 0;
// 获取已经下载的缓存文件大小
if (downloadCacheFile.exists() && downloadCacheFile.isFile()) {
downloadCacheFileLength = downloadCacheFile.length();
}

// 要下载的文件的大小
long downloadFileLength = getDownloadFileLength(downloadUrl);
if (downloadFileLength == 0) {
return TYPE_FAILED; // 下载失败
} else if (downloadFileLength == downloadCacheFileLength) {
return TYPE_SUCCESS; // 下载成功
}

OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.addHeader("RANGE", "bytes=" + downloadCacheFileLength + "-") // 指定下载区间
.url(downloadUrl)
.build();

try {
Response execute = okHttpClient.newCall(request).execute();
ResponseBody body = execute.body();
if (execute.isSuccessful() && body != null) {
InputStream inputStream = body.byteStream();
RandomAccessFile randomAccessFile = new RandomAccessFile(downloadCacheFile, "rw");
randomAccessFile.seek(downloadCacheFileLength); // 跳过已经下载的字节
byte[] bytes = new byte[1024];
int total = 0;
int len;
while ((len = inputStream.read(bytes)) != -1) {
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPaused) {
return TYPE_PAUSED;
} else {
total += len;
randomAccessFile.write(bytes, 0, len);
int progressValue = (int) ((total + downloadCacheFileLength) * 100 / downloadFileLength);
publishProgress(progressValue);
}
}
body.close();
return TYPE_SUCCESS;
}
} catch (IOException e) {
e.printStackTrace();
}
return TYPE_FAILED;
}

/**
* 获取下载的文件的大小
* @param url 下载链接
* @return 文件大小
*/
private long getDownloadFileLength(String url){
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
long length = 0;
try {
Response execute = okHttpClient.newCall(request).execute();
ResponseBody body = execute.body();
if (execute.isSuccessful() && body != null){
length = body.contentLength();
body.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return length;
}

/**
* 获取下载链接中的文件名
* @param url 下载链接
* @return 文件名
*/
private String getDownloadFileName(String url){
int lastIndexOf = url.lastIndexOf("/");
return url.substring(lastIndexOf);
}
}

  更新下载进度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 当在后台任务中调用了publishProgress(Progress...)方法之后,onProgressUpdate()方法
* 就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以
* 对界面进行相应的更新。
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
int progress = values[0];
if (progress > lastProgress) {
downloadListener.onProgress(progress);
lastProgress = progress;
}
}

  回调传入的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 当后台任务执行完毕并通过Return语句进行返回时,这个方法就很快被调用。返回的数据会作为参数
* 传递到此方法中,可以利用返回的数据来进行一些UI操作。
*/
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
switch (integer) {
case TYPE_SUCCESS:
removeFile();
downloadListener.onSuccess(downloadFile);
break;
case TYPE_FAILED:
downloadListener.onFailed();
break;
case TYPE_PAUSED:
downloadListener.onPaused();
break;
case TYPE_CANCELED:
deleteCache();
downloadListener.onCanceled();
break;
default:
break;
}
}

/**
* 删除下载缓存
*/
private void deleteCache() {
downloadCacheFile.delete();
}

/**
* 将缓存文件重命名为目标文件
*/
private void removeFile() {
if (downloadFile.exists() && downloadFile.isFile()) {
downloadFile.delete();
}
downloadCacheFile.renameTo(downloadFile);
}

  调用时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
new DownloadUtils(new DownloadUtils.DownloadListener() {
@Override
public void onProgress(int progress) {
// 下载进度
}

@Override
public void onSuccess(File downloadCacheFile) {
// 下载成功后回调
}

@Override
public void onFailed() {
// 下载失败时回调
}

@Override
public void onPaused() {
// 下载暂停
}

@Override
public void onCanceled() {
// 下载取消
}
}).execute(url, folderPath, fileName);

  整体来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/**
* OkHttp断点下载
*/
public class DownloadUtils extends AsyncTask<String, Integer, Integer> {
private static final int TYPE_SUCCESS = 0; // 下载成功
private static final int TYPE_FAILED = 1; // 下载失败
private static final int TYPE_PAUSED = 2; // 下载暂停
private static final int TYPE_CANCELED = 3; // 下载取消

private boolean isPaused = false; // 是否暂停
private boolean isCanceled = false; // 是否取消

private int lastProgress; // 记录进度值

private File downloadCacheFile; // 下载到本地的缓存文件
private File downloadFile; // 下载到本地的文件


private final DownloadListener downloadListener; // 下载监听

public DownloadUtils(DownloadListener downloadListener) {
this.downloadListener = downloadListener;
}

/**
* 通过execute执行时,需要在传入的数组中添加三个参数,且顺序固定
* 1. 文件下载链接
* 2. 存放下载文件的目录的路径
* 3. 文件名
*/
@Override
protected Integer doInBackground(String... strings) {
// 下载链接
String downloadUrl = strings[0];
// 文件所在目录的路径
String folderPath = strings[1];
// 文件名
String fileName = strings[2];

// 如果下载链接为空,直接return出去
if (downloadUrl == null || TextUtils.isEmpty(downloadUrl))
return null;
// 如果所在目录的路径不正确
if (folderPath == null || TextUtils.isEmpty(folderPath))
folderPath = Environment.getExternalStorageDirectory().getAbsolutePath();
// 如果文件名不正确
if (fileName == null || TextUtils.isEmpty(fileName))
fileName = getDownloadFileName(downloadUrl);

// 下载到本地的文件路径
String downloadFilePath = folderPath + "/" + fileName;
// 下载到本地的文件
downloadFile = new File(downloadFilePath);

// 下载到本地的缓存文件路径
String downloadCacheFilePath = folderPath + "/" + fileName + "_cache";
// 下载到本地的缓存文件
downloadCacheFile = new File(downloadCacheFilePath);

// 已经下载的缓存文件大小
long downloadCacheFileLength = 0;
// 获取已经下载的缓存文件大小
if (downloadCacheFile.exists() && downloadCacheFile.isFile()) {
downloadCacheFileLength = downloadCacheFile.length();
}

// 要下载的文件的大小
long downloadFileLength = getDownloadFileLength(downloadUrl);
if (downloadFileLength == 0) {
return TYPE_FAILED; // 下载失败
} else if (downloadFileLength == downloadCacheFileLength) {
return TYPE_SUCCESS; // 下载成功
}

OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.addHeader("RANGE", "bytes=" + downloadCacheFileLength + "-") // 指定下载区间
.url(downloadUrl)
.build();

try {
Response execute = okHttpClient.newCall(request).execute();
ResponseBody body = execute.body();
if (execute.isSuccessful() && body != null) {
InputStream inputStream = body.byteStream();
RandomAccessFile randomAccessFile = new RandomAccessFile(downloadCacheFile, "rw");
randomAccessFile.seek(downloadCacheFileLength); // 跳过已经下载的字节
byte[] bytes = new byte[1024];
int total = 0;
int len;
while ((len = inputStream.read(bytes)) != -1) {
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPaused) {
return TYPE_PAUSED;
} else {
total += len;
randomAccessFile.write(bytes, 0, len);
int progressValue = (int) ((total + downloadCacheFileLength) * 100 / downloadFileLength);
publishProgress(progressValue);
}
}
body.close();
return TYPE_SUCCESS;
}
} catch (IOException e) {
e.printStackTrace();
}
return TYPE_FAILED;
}

/**
* 获取下载的文件的大小
* @param url 下载链接
* @return 文件大小
*/
private long getDownloadFileLength(String url){
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
long length = 0;
try {
Response execute = okHttpClient.newCall(request).execute();
ResponseBody body = execute.body();
if (execute.isSuccessful() && body != null){
length = body.contentLength();
body.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return length;
}

/**
* 获取下载链接中的文件名
* @param url 下载链接
* @return 文件名
*/
private String getDownloadFileName(String url){
int lastIndexOf = url.lastIndexOf("/");
return url.substring(lastIndexOf);
}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
int progress = values[0];
if (progress > lastProgress) {
downloadListener.onProgress(progress);
lastProgress = progress;
}
}

@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
switch (integer) {
case TYPE_SUCCESS:
removeFile();
downloadListener.onSuccess(downloadFile);
break;
case TYPE_FAILED:
downloadListener.onFailed();
break;
case TYPE_PAUSED:
downloadListener.onPaused();
break;
case TYPE_CANCELED:
deleteCache();
downloadListener.onCanceled();
break;
default:
break;
}
}

/**
* 删除下载缓存
*/
private void deleteCache() {
downloadCacheFile.delete();
}

/**
* 将缓存文件重命名为目标文件
*/
private void removeFile() {
if (downloadFile.exists() && downloadFile.isFile()) {
downloadFile.delete();
}
downloadCacheFile.renameTo(downloadFile);
}

/**
* 调用该方法暂停下载
*/
public void pauseDownload() {
this.isPaused = true;
}

/**
* 调用该方法取消下载
*/
public void cancelDownload() {
this.isCanceled = true;
}

public interface DownloadListener {
// 下载进度
void onProgress(int progress);

// 下载成功
void onSuccess(File downloadCacheFile);

// 下载失败
void onFailed();

// 下载暂停
void onPaused();

// 下载取消
void onCanceled();
}
}