在我们的开发中,碰到文件下载的情况已经是很多见了吧?平时在开发大项目时,我们可能会用一些开源的库,比如afinal、Xutils等等,不过对于一些菜鸟的学习来说,自己去写一个下载的工具类,这对于自身的知识巩固那还是有很多好处的。好了废话不多说,现在我们直接来写一个文件下载工具类(不支持断点续传)。
下面我们来分析一下怎么去写。
首先,我们得去发送http请求去加载我们的inputStream吧,其次我们还得去利用BufferedInputStream、BufferedOutputStream等去写文件吧,还有md5校验吧。好了这里边还有很多细节问题,我们来一一查看,直接上代码。
// 获取文件的inputStream
private InputStream getInputStream(String path)throws Exception {
listener.statusListener(0,this.getFileName());
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10 * 1000);
conn.setRequestMethod("GET");
if(length <= 0){
try {
Map<String, List<String>> map = conn.getHeaderFields();
for(int i=0;i<map.size();i++){
if(map.containsKey("Content-Length")){
String temp = map.get("Content-Length").toString().replaceAll("\\]", "").replaceAll("\\[", "");
length = Long.parseLong(temp);
break;
}
}
} catch (Exception e) {
length = 0;
}
}
if(length <= 0){
listener.statusListener(-1,this.getFileName());
return null;
}
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
return conn.getInputStream();
}
return null;
}
根据上面的代码块我们来分析,HttpURLConnection去拿我们想要的数据,包括http头和inputStream,在http头里边有我们下载文件的长度,这个长度可以用来检测下载成功与否和下载进度的显示。这个方法我们返回的是一个inputStream。
在线程run方法里边有一个while循环,在循环里去获取inputStream,因为有可能我们获取到的inputStream为null,所以我们循环获取,最多获取三次。
while (isRunning && errorCount < 3) {
threadStartRun = true;
if(errorCount >= 2){
listener.statusListener( -1,this.getFileName());
break;
}
try {
inputStream = getInputStream(src);
if(inputStream == null){
errorCount++;
Thread.sleep(3000);
continue;
}
} catch (Exception e) {
e.printStackTrace();
errorCount++;
continue;
}
if(inputStream !=null){
break;
}
}
拿到inputStream之后当然是去读写文件到本地.
/**
* 将InputStream保存为本地文件
* */
private String saveFile(InputStream is) throws IOException {
threadStartWrite = true;
File myCaptureFile = new File(this.getFileName());
if (myCaptureFile.exists()) {
myCaptureFile.delete();
}
myCaptureFile.createNewFile();
FileOutputStream out = null;
try {
out = new FileOutputStream(myCaptureFile);
BufferedInputStream bufis = new BufferedInputStream(is);
BufferedOutputStream bufos = new BufferedOutputStream(out);
byte[] b = new byte[1024*16];
int len;
long count = 0;
float process = 0;
while (isRunning && (len = bufis.read(b)) != -1) {
bufos.write(b, 0, len);
count += len;
process =(float)count/length*100;
if(!isRunning){
break;
}
getListener().statusListener((int)process,myCaptureFile.getAbsolutePath());
downprocess = (int)process;
N = (int) count;
}
out.flush();
bufis.close();
bufos.close();
if(!isRunning){
return null;
}
String tempMd5 = getFileMD5(myCaptureFile);
if(!(getMd5().equals(tempMd5))){
N = -1;
downprocess = -1;
return null;
}
return myCaptureFile.getAbsolutePath();
} catch (Exception e) {
getListener().statusListener(-1,myCaptureFile.getAbsolutePath());
N = -1;
downprocess = -1;
return null;
}
}
从上面代码块我们可以看到,获取到inputStream之后利用BufferedInputStream等去写文件,当然还有一个重要方法就是getListener().statusListener((int)process, myCaptureFile. getAbsolutePath());通知调用者当前下载的进度。
下载完成之后会有一个md5码的校验,直接来代码。若返回为null校验失败,否则下载成功。
private String getFileMD5(File file) {
if (!file.isFile()) {
return null;
}
MessageDigest digest = null;
FileInputStream in = null;
byte buffer[] = new byte[1024];
int len;
try {
digest = MessageDigest.getInstance("MD5");
in = new FileInputStream(file);
while ((len = in.read(buffer, 0, 1024)) != -1) {
digest.update(buffer, 0, len);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
return null;
}
BigInteger bigInt = new BigInteger(1, digest.digest());
String strMd5 = bigInt.toString(16);
if (strMd5.length() < 32) {
for (int i = 0; i < 32 - strMd5.length(); i++) {
strMd5 = "0" + strMd5;
}
}
return strMd5;
}
当然下载完成之后我们还得通知调用者下载完成吧。
getListener().statusListener(100,myCaptureFile.getAbsolutePath());
好了,下载的线程算是完事了,我们的额超时线程还没完呢,等等,别着急马上。它主要是干啥呢,就是检测下载线程的运行状态,来吧那就。
@Override
public void run() {
super.run();
name = downloadTask.getFileName();
try {
while(!downloadTask.isThreadStartRun()&& downloadTask.isRunning()){ //开始启动下载的时候 ,超时线程也跟着启动。
Thread.sleep(500);
}
if(downloadTask.isRunning()){
isWhile = true;
}
Thread.sleep(2000);
while(downloadTask != null && downloadTask.isRunning()) {
isWhile = true;
Thread.sleep(3000);
if(c != downloadTask.getN() && downloadTask.isThreadStartWrite() && c!=downloadTask.getLength()) {
c = downloadTask.getN();
t=0;
process = downloadTask.getDownprocess();
} else {
if(process<100)t++;
}
process = downloadTask.getDownprocess();
if(process > 100){
break;
}
if(t >= TIMEOUT_COUNT) {
downloadTask.setRunning(false);
downloadTask.interrupt();
downloadTask = null;
break;
}
if(process <= -1){
break;
}
}
if(t >= TIMEOUT_COUNT && downloadTask == null && process<100) {
listener.statusTimeoutListener(-1,name);
return;
}
if(!isWhile){
if(downloadTask != null){
downloadTask.setRunning(false);
downloadTask.interrupt();
downloadTask = null;
}
listener.statusTimeoutListener(-1,name);
}
} catch (Exception e) {
if(process <100){
listener.statusTimeoutListener(process,name);
}
}
这是它的一个run方法,可以看到首先,做一个延时,等待下载线程的执行,因为我们的超时线程和下载线程必须同时执行!!随之来的就是检测,检测下载线程的isRunning,检测文件读写是是否卡顿,如果在12s之内下载一直是卡顿的,我们就直接将下载线程停止掉(当然,这个停止掉指的是我们不再使用它,并不能直接杀死他),这个12秒我们也可以自己在超时线程里边控制。如果 listener.statusTimeoutListener(process,name);则表示线程下载超时,停止下载。
使用方法:
DownloadTask downloadTask = new DownloadTask("下载地址", "文件md5", 122222, "文件保存地址", new DownloadTask.DownLoadTaskStatusListener() <span style="white-space:pre"> </span>{
public void statusListener(int process, String fileName) {
// 这里边是下载进度的回调
}
});
downloadTask.start();
DownloadTimeout downloadTimeout = new DownloadTimeout(downloadTask, new DownLoadTaskStatusTimeoutListener() {
public void statusTimeoutListener(int process, String fileName) {
// 这里边是下载失败,超时的回调
}
});
downloadTimeout.start();
当然咯,下面就是下载链接了 点击打开链接 下载。