现在网络上关于Android下载的代码很多,但是光看还是无法完全理解和掌握,所以决定自己手写一个,以加深学习。
在写下载代码之前,有几点知识必须要掌握:
1.Http请求报文格式及响应报文,可以通过使用wireshark来对报文进行抓取,查看http协议的报文
2.HttpUrlConnection的使用
3.RandomAccessFile类的使用(可断点下载)
这里写的下载是关于断点下载的。
断点下载的原理就是在请求报文中加入Range:bytes=x-y 其中x表示开始下载的位置,y表示下载到的位置,y可省略,表示到文件结尾。例如:0- 表示从文件的开头一直读到结尾。
多线程下载页是基于这个原理。例如有三个线程。文件大小为600;则:
线程1:Range:bytes=0-199;
线程2:Range:bytes=200-399;
线程3:Range:bytes=400-
建立起连接之后,获取输入流:
in=con.getInputStream();然后利用RandomAccessFile类就行写出。这个类可以随机写入文件,通过seek方法定位到要写的位置,然后通过write方法进行写出。代码如下
public class DownLoadUtil {
private static DownLoadUtil downLoadUtil;
private int readTimeOut = 6 * 1000;// 读取超时时间
private int connectTimeOut = 6 * 1000;// 连接超时时间
private OnDownLoadListener listener;// 下载监听器
private Handler handler;// 监听结果交由主线程处理
private boolean isDownloading;// 下载状态
private int downloadLength = 0;// 记录下载长度(应用中应保存在数据库中)
private String requestUrl;
private String path;
// private Handler handerl;
private DownLoadUtil() {
// 获取主线程handler,可以让回调函数在主线程运行(volley框架就是通过这种方式让处理结果在主线程运行的)
handler = new Handler(Looper.getMainLooper());
}
public static DownLoadUtil getInstance() {
if (downLoadUtil == null) {
downLoadUtil = new DownLoadUtil();
}
return downLoadUtil;
}
public void downLoad(String requestUrl, String path, int downloadLength) {
this.isDownloading = true;
this.requestUrl = requestUrl;
this.path = path;
new DownLoadThread(requestUrl, path, downloadLength).start();
}
// 下载线程
public class DownLoadThread extends Thread {
private String downloadUrl;// 下载地址
private String path;// 存储路径
private int downloadLength;// 已下载长度
private int progressNotifyCount;// 每下载100字节更新一次
public DownLoadThread(String downloadUrl, String path, int downloadLength) {
this.downloadLength = downloadLength;
this.path = path;
this.downloadUrl = downloadUrl;
}
@Override
public void run() {
// TODO Auto-generated method stub
// super.run();
InputStream is = null;
RandomAccessFile downFile = null;
HttpURLConnection con = null;
try {
URL url = new URL(downloadUrl);
con = (HttpURLConnection) url.openConnection();
con.setReadTimeout(readTimeOut);// 设置主机读取超时时间
con.setConnectTimeout(connectTimeOut);// 设置连接超时时间
// 断点下载所需 Range:bytes=x-y;
con.setRequestProperty("Range", "bytes=" + String.valueOf(downloadLength) + "-");
is = con.getInputStream();
int fileLength = con.getContentLength();// 获取文件长度
Log.v("fileLength", String.valueOf(fileLength));
exeDownLoadInit(downloadLength, fileLength);// 下载初始化回调,返回已下载大小和文件总大小
File file = new File(path);
if (!file.exists()) {
file.mkdir();
}
String[] str = downloadUrl.split("/");
String fileName = str[str.length - 1];
File file1 = new File(path + fileName);
if (!file1.exists()) {
file1.createNewFile();
} else {
}
downFile = new RandomAccessFile(file1, "rw");
downFile.seek(downloadLength);// 定位文件已经写入的位置,断点下载使用
byte[] data = new byte[1024];
int len;
while ((len = is.read(data)) != -1) {
// 文件写出,使用write(data,0,len)这个方法,当读取的长度小于1024,则只写入读取的长度
// 如果使用write(data)方法,则当长度不够1024,就会在后面自动补0
downFile.write(data, 0, len);
downloadLength += len;
downLoadUtil.downloadLength += len;
progressNotifyCount++;
// if(progressNotifyCount%100==0){
// 这个运算就相当于progressNotifyCount%100==0
// 计算机进行二进制运算速度更快,可做android优化使用
// 每下载100字节,就回调一次进度,供更新进度条使用
if ((progressNotifyCount & (100 - 1)) == 0) {
exeDownLoadProgress(downloadLength);
}
// 暂停函数
if (!isDownloading) {
return;
}
}
//下载完成回调
if (downloadLength == fileLength) {
exeDownloadComplete(fileLength);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
downFile.close();
is.close();
con.disconnect();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// 交由主线程处理监听结果
private void exeDownLoadProgress(final int downloadLength) {
handler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
listener.downloadProgress(downloadLength);
}
});
}
private void exeDownLoadInit(final int downloadLength, final int fileLength) {
handler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
listener.downloadInit(downloadLength, fileLength);
}
});
}
private void exeDownloadComplete(final int fileLength) {
handler.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
listener.complete(fileLength);
}
});
}
/**
* 下载监听器
*
* @author Administrator
*
*/
public static interface OnDownLoadListener {
/**
* 请求完成回调
*
* @param fileLength
*/
void complete(int fileLength);
/**
* 进度更新
*
* @param downloadLength
*/
void downloadProgress(int downloadLength);
/**
* 下载初始化
*
* @param fileLength
*/
void downloadInit(int downloadLength, int fileLength);
}
public void setListener(OnDownLoadListener listener) {
this.listener = listener;
}
public void stop() {
this.isDownloading = false;
// downloadThread.getId();
}
public void restart() {
isDownloading = true;
downLoad(requestUrl, path, downloadLength);
}
}