Android里面下载一般应该有两种方式,一种是多线程实现,一种是异步。而实现多线程在java里可以有两种方式,一种是多个线程实现同一个Runnable,另外就是将一个文件长度分为n个部分,然后让n个线程去分别下载1部分。
[1]我阅读了网上的一个方法2的实现代码,然后自己实现了一个,基本思路就是实现一个单个线程类,在这个单个线程类就完成3个操作,
1是从URL连接读取流
2是将读取的字节数组写入到本地文件中
3是将当前已经读取的长度返回
然后开启一个线程,这个线程的操作是创建一个线程数组,每个元素是刚刚的那个单个线程类,在这个线程中分配每个线程的下载起点和下载终点,然后根据各个线程的下载进度之和来更新进度条。
demo如下:
package com.study.daniel.downloadtest;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
/**
* Created by daniel on 15-8-1.
*/
public class DownloadSingleThread extends Thread{//单个下载线程
/**文件下载URL*/
private String downloadURL;
/**文件保存路径*/
private File file;
/**单个线程下载文件大小*/
private int partSize;
/**当前已经下载的长度*/
private int currentLength;
/**线程id*/
private int threadID;
/**当前线程是否下载完成*/
private boolean isCompleted=false;
public DownloadSingleThread(String downloadURL,File file,int partSize,int threadID){
this.partSize=partSize;
this.downloadURL=downloadURL;
this.file=file;
this.threadID=threadID;
}
public void run(){
BufferedInputStream bis=null;//读取下载文件流
RandomAccessFile raf=null;//写入文件流
try {
//计算下载起点,终点
int startPos = partSize * threadID;//开始位置
int endPos = partSize * (threadID +1)-1;//结束位置
//建立连接
URL url=new URL(downloadURL);
URLConnection connection=url.openConnection();//获得连接
//设置本次连接的下载起点和终点
connection.setRequestProperty("Range","bytes="+startPos+"-"+endPos);
//创建输入流
bis=new BufferedInputStream(connection.getInputStream());
//创建文件写入流
raf=new RandomAccessFile(file,"rwd");
raf.seek(startPos);
byte[] buffer=new byte[1024];//一次读取一k
int len;
while((len=bis.read(buffer,0,1024))!=-1){//本线程下载是否完成
raf.write(buffer,0,len);//读取并写入
currentLength+=len;
}
isCompleted=true;//当前线程下载完成
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {//检查并关闭IO流
if(bis!=null)
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
if(raf!=null)
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public int getCurrentLength(){//返回当前下载长度
return currentLength;
}
public boolean isCompleted(){
return isCompleted;
}
}
-------------------------------------------------------
package com.study.daniel.downloadtest;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class MainActivity extends ActionBarActivity implements View.OnClickListener {
private TextView tv_downloaded;
private ProgressBar progressBar;
Handler handler = new Handler() {
public void handleMessage(Message msg) {
//更新进度条
progressBar.setProgress(msg.getData().getInt("size"));
//算算当前进度比
float temp = (float) progressBar.getProgress() / (float) progressBar.getMax();
//显示进度百分比
tv_downloaded.setText((int) (temp * 100) + "%");
//下载完成
if (progressBar.getProgress() == progressBar.getMax())
Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
msg.arg1 = 0;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_downloaded = (TextView) findViewById(R.id.tv_downloaded);
progressBar = (ProgressBar) findViewById(R.id.progressbar);
findViewById(R.id.bt_download).setOnClickListener(this);
}
public void onClick(View view) {
if (view.getId() == R.id.bt_download)
download();
}
public void download() {
//设置下载保存路径
String path = Environment.getExternalStorageDirectory()
+ "/newdownload/";//这是下载文件保存目录
File file = new File(path);
if (!file.exists()) {//如果该保存目录不存在就创建
file.mkdir();
}
//初始化进度条
progressBar.setProgress(0);
//设置下载URL
String downloadURL = "http://gdown.baidu.com/data/wisegame/91319a5a1dfae322/baidu_16785426.apk";
String fileName = "baidu_16785426.apk";
int threadNum = 5;
String filePath = path + fileName;
System.out.println("download file path:" + filePath);
DownloadTask downloadTask = new DownloadTask(threadNum, downloadURL, filePath);
downloadTask.start();
}
private class DownloadTask extends Thread {
private int threadNum;//开启线程数
private String downloadURL;//下载链接地址
private String savePath;//文件保存路径
private int partSize;//每一个线程的下载量,根据下载文件总长度计算得出
public DownloadTask(int threadNum, String downloadURL, String savePath) {
this.threadNum = threadNum;
this.downloadURL = downloadURL;
this.savePath = savePath;
}
public void run() {
try {
URL url = new URL(downloadURL);
URLConnection connection = url.openConnection();
int totalLength = connection.getContentLength();//总长度
if (totalLength <= 0) {
System.out.println("读取文件失败");
return;
}
//根据下载文件长度设置进度条的最大值
progressBar.setMax(totalLength);
//计算单个线程下载长度
partSize = (totalLength % threadNum == 0) ? totalLength / threadNum
: totalLength / threadNum + 1;
DownloadSingleThread[] threads = new DownloadSingleThread[threadNum];
File file = new File(savePath);
for (int i = 0; i < threads.length; i++) {//创建并开启单个线程
DownloadSingleThread thread = new DownloadSingleThread(downloadURL, file, partSize, i);
threads[i] = thread;
threads[i].start();
}
boolean isfinished = false;
int totalDownloadSize = 0;//5个线程当前下载总量
while (!isfinished) {
isfinished = true;
totalDownloadSize = 0;
for (int i = 0; i < threads.length; i++) {
if (!threads[i].isCompleted())//只要有一个线程没有完成,就没有完成下载
isfinished = false;
totalDownloadSize += threads[i].getCurrentLength();//累加
System.out.println("totalDownloadSize----------" + totalDownloadSize);
}
//更新视图
Message msg = handler.obtainMessage(0x11);//发送当前下载总量去更新视图
msg.getData().putInt("size", totalDownloadSize);
msg.sendToTarget();
sleep(1000);//每隔1秒更新一次
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
[2]方法1的代码是我参考方法2写的,自己实现。最后调很多遍不知为什么显示下载结束的Toast始终不消失,我以为是setRequestProperty()方法造成的下载没有结束引起,结果发现不是。后来终于发现是因为始终没有跳出while(!isfinished)循环,所以一直在更新handler,所以一直显示Toast,这个小问题搞了很久,逻辑上的,后来用了一点小技巧搞定。
demo如下:
package com.study.daniel.runnabletest;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class MainActivity extends ActionBarActivity implements View.OnClickListener{
/**本地保存目录*/
private String path= Environment.getExternalStorageDirectory()
+ "/RunnableDownload/";
private File directory;
/**下载连接URL*/
private String downloadURL="http://gdown.baidu.com/data/wisegame/91319a5a1dfae322/baidu_16785426.apk";
private String fileName="baidu_16785426.apk";
/**下载文件总大小*/
private int fileSize;
/**输入流读取连接*/
private BufferedInputStream bis;
/**随机流保存文件*/
private RandomAccessFile raf;
/**每次读入保存的字节数组*/
private byte[] buffer;
/**下载文件*/
private File file;
/**线程数组*/
private Thread[] threads;
/**自定义线程类完成下载前的准备工作*/
PrepareThread prepareThread;
/**记录已下载文件距离文件开头的位置,初始为0*/
private static int position=0;
private boolean isfinished=false;
private boolean runIsFinished=false;
/**控件*/
private TextView tv_downloaded;
private ProgressBar progressBar;
Handler handler=new Handler(){
public void handleMessage(Message msg){
progressBar.setProgress(msg.getData().getInt("size"));
float temp=(float)progressBar.getProgress() /
(float)progressBar.getMax();
tv_downloaded.setText((int)(temp*100)+"%");
if(progressBar.getMax()==progressBar.getProgress())
{//下载完成
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}
}
};
private Runnable runnable=new Runnable() {
@Override
public void run() {
try {
URL url=new URL(downloadURL);
URLConnection connection=url.openConnection();
connection.setRequestProperty("Range","bytes="+0+"-"+fileSize);
//输入流
bis=new BufferedInputStream(connection.getInputStream());
buffer=new byte[1024];
//写入流
raf=new RandomAccessFile(file,"rw");
raf.seek(0);
int len;
while((len=bis.read(buffer,0,1024))!=-1){
raf.write(buffer,0,len);
position+=len;//为了更新进度条
}
runIsFinished=true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
public void onClick(View view){
if(view.getId()==R.id.bt_download)
{
//下载前的准备,文件及连接
prepareThread=new PrepareThread();
prepareThread.start();
}
}
protected void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
setContentView(R.layout.activity_main);
tv_downloaded=(TextView)findViewById(R.id.tv_downloaded);
progressBar=(ProgressBar)findViewById(R.id.progressbar);
findViewById(R.id.bt_download).setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {//程序销毁时候销毁线程
super.onDestroy();
}
/**
* 自定义线程类,做初始化工作
*/
private class PrepareThread extends Thread{
int temp=0;
public void run(){
//设置本地下载文件保存目录
directory=new File(path);
if(!directory.exists()){//如果目录不存在则创建
directory.mkdir();
}
file=new File(path+fileName);//将下载文件保存为file
//连接
try {
URL url=new URL(downloadURL);
URLConnection connection=url.openConnection();
fileSize=connection.getContentLength();
progressBar.setMax(fileSize);
/**在这个线程里开启多线程,解决了执行先后的问题*/
threads=new Thread[3];//3个线程下载
for(int i=0;i<threads.length;i++){
Thread thread=new Thread(runnable);//所有线程都执行同一个任务
threads[i]=thread;
thread.start();
}
while(!isfinished){//更新视图
Message message=handler.obtainMessage(0x11);
message.getData().putInt("size",position);
message.sendToTarget();
sleep(1000);
isfinished=false;
if(temp==2 && runIsFinished){
isfinished=true;
}
if(runIsFinished ){
temp++;
}
}
System.out.println("----------------------out of while(!isfinished)");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
/**既然使用runnable,那么所有线程使用的都是一个流,那就不妨在这个线程里面,
* 等所有线程都结束再关闭流,就不存在问题了*/
if(bis!=null)
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
if(raf!=null)
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
还遇到流的关闭的顺序问题,我原先把流的关闭放在Runnable的最后,但是造成流关闭多次的异常。我还没太搞清楚为什么。但是我将操作放到了那个准备线程中,就解决了。
运行结果如下,测试没有问题:
我将两个demo工程上传到csdn,大家下载参考吧!我没有实现的是断点续传。实现之后更新博客,传代码。