小心以下的坑:
1、网络是不可靠的,凡是连接网络的部分,都有可能失效;
2、过于频繁的请求连接会出问题;
3、okhttp依赖于okio,从某个版本的okio开始,需要kotlin核心库;
进入正题。
基本思路:利用http head头中的rang可以声明请求的范围。那么,真正下载时可以请求部分资源。
当使用单线程进行断点续传时,第一次下载请求的是全部资源(完整长度)。被中断后,再次下载,此时请求的是上次已经下载成功的部分到资源的末尾,如此一直下去直到下载完成或达到其它结束条件(超时、重试次数等)。
如果进行多线程断点续传,那么先将待下资源划分成多个小块,然后交给多个线程分别下载。
下载的内容写入文件时,一般用RandomAccess方式。对于单线程的情况,实际上退化成了Append方式。
下载类的代码如下:
public class SingleThreadDownader
{
public static final int TYPE_SUCCESS = 0;
public static final int TYPE_FAILED = 1;
public static final int MAX_COUNT = 10;// 重试的最大次数
private DownloadReceiverInterface receiver;
private String downloadUrl;
private Context context;
private String downloadedFileName;
long contentLength;
public SingleThreadDownader(Context _context, String _downloadUrl, String _downloadedFileName, DownloadReceiverInterface listener)
{
this.context = _context;
this.downloadUrl = _downloadUrl;
this.downloadedFileName = _downloadedFileName;
this.receiver = listener;
}
public void work()
{
File f = new File(this.context.getFilesDir() + "/" + this.downloadedFileName);
contentLength = getContentLength(downloadUrl);
if (contentLength == 0)
{
this.receiver.downloadFailed();
return;
}
for (int i = 0; i < MAX_COUNT; i++)
{
int x = download();
switch (x)
{
case TYPE_SUCCESS:
receiver.downloadFinish();
return;
case TYPE_FAILED:
//SLEEP 15S
try
{
Thread.sleep(15*1000);
} catch (InterruptedException e)
{
}
continue;
}
}
receiver.downloadFailed();
}
protected Integer download()
{
File f = new File(this.context.getFilesDir() + "/" + this.downloadedFileName);
InputStream is = null;
RandomAccessFile savedFile = null;
long downloadLength = 0;
if (f.exists())
{
downloadLength = f.length();
}else
{
try
{
f.createNewFile();
} catch (IOException e)
{
return TYPE_FAILED;
}
}
if (contentLength == downloadLength)
{
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(900, TimeUnit.SECONDS).readTimeout(1800, TimeUnit.SECONDS).writeTimeout(900, TimeUnit.SECONDS).build();
Request request = new Request.Builder().addHeader("RANGE", "bytes=" + downloadLength + "-"+contentLength).url(downloadUrl).build();
try
{
Response response = client.newCall(request).execute();
if (response != null)
{
is = response.body().byteStream();
savedFile = new RandomAccessFile(f, "rw");
savedFile.seek(downloadLength);
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1)
{
total += len;
savedFile.write(b, 0, len);
try
{
Thread.sleep(2);
} catch (InterruptedException e)
{
}
}
response.body().close();
return TYPE_SUCCESS;
} else
{
return TYPE_FAILED;
}
} catch (IOException e)
{
return TYPE_FAILED;
} finally
{
try
{
if (is != null)
{
is.close();
}
if (savedFile != null)
{
savedFile.close();
}
} catch (Exception e)
{
e.printStackTrace();
}
}
}
private long getContentLength(String downloadUrl)
{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(downloadUrl).build();
try
{
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful())
{
long contentLength = response.body().contentLength();
response.body().close();
return contentLength;
}
} catch (IOException e)
{
}
return 0;
}
}