一、断点下载原理及步骤
对于断点下载,就是下载的过程中,都会出现一些异常情况,导致下载中断。虽说可以重新下载,但是这对于数据比较大的来说,这是很麻烦很蛋疼的事。
原理:
在下载中断的时候,通过数据库,记录中断的文件信息。待恢复上传的时候,从数据库中读取中断的文件信息。通过RandomAccessFile这个类可以让文件继续从中断的位置上传。
步骤:
- 获取下载链接,首先根据下载链接到数据库查找一下是否有重复的下载任务,有的话获取数据继续下载,没有的话,新建任务,获取文件对象,传给下载服务。
- 新建一个下载服务,方便应用退出时,能继续在后台下载。
- 创建一个数据库,用来存储程序出现异常中断时,能够及时保存下载信息,方便下次读取继续下载。
- 创建一个线程,用来获取待下载文件的长度和执行下载线程。
- 在不断写文件的过程中,通过广播刷新下载进度。
二、代码实现
package com.example.river.download;
import java.io.Serializable;
/**
* Created by Administrator on 2017/10/18.
*/
public class FileInfo implements Serializable{
private String fileName;
private String url;
//文件的大小
private int len;
//文件结束位置
private int finished;
private boolean isDownloading;
public FileInfo(){
}
public FileInfo(String fileName,String url){
this.fileName = fileName;
this.url = url;
}
public boolean isDownloading() {
return isDownloading;
}
public void setDownloading(boolean downloading) {
isDownloading = downloading;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public int getFinished() {
return finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, DownloadService.class);
if (fileInfo.isDownloading()) {
fileInfo.setDownloading(false);
start.setText("继续");
} else {
fileInfo.setDownloading(true);
start.setText("暂停");
}
intent.setAction("start");
intent.putExtra("fileInfo", fileInfo);
startService(intent);
}
});
restart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
start.setText("暂停");
fileInfo.setDownloading(true);
Intent intent = new Intent(MainActivity.this, DownloadService.class);
fileInfo.setFinished(0);
intent.setAction("restart");
intent.putExtra("fileInfo", fileInfo);
startService(intent);
}
});
receiver = new ProgressBroadcast();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.ProgressBroadcast");
//注册receiver
registerReceiver(receiver, filter);
广播更新进度
public class ProgressBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int progress = intent.getIntExtra("finished", 0);
mProgressBar.setProgress(progress);
}
}
检查数据库
private FileInfo checkDB(){
DBHelper dbHelper = new DBHelper(MainActivity.this);
SQLiteDatabase db = dbHelper.getReadableDatabase();
fileInfo= dbHelper.queryData(db,"http://www.21yey.com/clientdownload/android/family.apk");
if (fileInfo.getFinished() > 0) {
mProgressBar.setProgress(fileInfo.getFinished() * 100 / fileInfo.getLen());
start.setText("继续");
}else {
fileInfo = new FileInfo("family.apk", "http://www.21yey.com/clientdownload/android/family.apk");
}
return fileInfo;
}
后台下载服务,以便应用退出可以继续下载
public class DownloadService extends IntentService{
public static final String ACTION_START = "start";
public static final String ACTION_RESTART = "restart";
public static final String ACTION_UPDATE = "update";
public DownloadService() {
super("download");
}
@Override
protected void onHandleIntent(Intent intent) {
TaskManager task = TaskManager.getInstance();
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
if(intent.getAction().equals(ACTION_START)){
if(fileInfo.isDownloading()){
task.start(DownloadService.this,fileInfo);
}else {
task.stop();
}
}
if(intent.getAction().equals(ACTION_RESTART)){
task.restart(DownloadService.this,fileInfo);
}
}
}
任务调度
public class TaskManager {
private Map<String,FileInfo> map = new HashMap<>();
private boolean isPause;
public static class TaskHolder{
private static final TaskManager instance = new TaskManager();
}
public static TaskManager getInstance(){
return TaskHolder.instance;
}
//恢复任务
public void start(Context context,FileInfo fileInfo){
if(map.get(fileInfo.getUrl())==null){
map.put(fileInfo.getUrl(),fileInfo);
}
DownloadTask task = new DownloadTask(map.get(fileInfo.getUrl()),context);
isPause = false;
task.start();
}
public void stop(){
isPause =true;
}
public void restart(Context context,FileInfo fileInfo){
try {
map.clear();
File file = new File(DownloadTask.FILE_PATH,fileInfo.getFileName());
if (file.exists()){
file.delete();
}
Thread.sleep(100);
}catch (Exception e){
return;
}
start(context,fileInfo);
}
public boolean isPause() {
return isPause;
}
public void setPause(boolean pause) {
isPause = pause;
}
}
获取待下载的文件长度
int length = -1;
try {
HttpURLConnection conn = null;
URL url = new URL(info.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
if(conn.getResponseCode() ==200){
length =conn.getContentLength();
}
if(length<0){
return;
}
File dir = new File(DownloadTask.FILE_PATH);
if(!dir.exists()){
dir.mkdir();
}
info.setLen(length);
conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
执行下载任务
HttpURLConnection connection = null;
RandomAccessFile raf = null;
try {
URL urls = new URL(info.getUrl());
connection = (HttpURLConnection) urls.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(3000);
//获取上次下载位置
int start = info.getFinished();
connection.setRequestProperty("Range","bytes="+start+"-"+length);
//设置文件写入位置
File file = new File(FILE_PATH,info.getFileName());
raf= new RandomAccessFile(file,"rwd");
raf.seek(start);
finished += info.getFinished();
if(connection.getResponseCode() == 206){
InputStream is =connection.getInputStream();
byte[] bytes = new byte[1024*4];
int len;
while ((len = is.read(bytes))!=-1){
raf.write(bytes,0,len);
finished+=len;
info.setFinished(finished);
if(TaskManager.getInstance().isPause()){
info.setDownloading(false);
dbHelper.insert(db,info);
db.close();
return;
}
//实时更新下载进度
Intent intent = new Intent(DownloadService.ACTION_UPDATE);
intent.putExtra("finished", finished * 100 / length);
intent.setAction("android.intent.action.ProgressBroadcast");
context.sendBroadcast(intent);
}
info.setDownloading(false);
dbHelper.insert(db,info);
db.close();
}
} catch (Exception e) {
e.printStackTrace();
}
运行效果:
三、总结
我简单的实现单任务单线程的下载(适合文件较小)。后续会实现单任务多线程下载、多任务多线程下载(文件较大的)和上传,敬请关注。虽然代码简陋,但简洁思路清晰比较好理解。我的目的不是实现一个高性能可扩展的下载器,而是展示具体如何实现下载的一个流程。当然,我实现的代码都是比较基础的,比较好理解。
扫描领红包