断点续传和多线程下载(一)

时间:2022-08-28 18:33:49

最近在学习Android中的 断点续传 和 多线程下载 这方面的知识,一边从公司中的项目开始学习,一边也从网上查阅这方面的知识,然后发现公司中的项目代码关于这块都封装的比较好,比较大,不太容易看懂,所以准备参照网上的Demo,自己仿照着写一个。

首先我觉得学习一个比较复杂的知识点,不应当在一个demo里把这个所有复杂的知识点全都实现出来,这样对于初学者来说比较困难,从而不能完全的掌握知识点,所以我觉得应该先对这个知识点有个整体的把握,然后把这些东西给分成一个模块一个模块的,然后把这些局部的知识一个一个实现出来,这是比较简单的,最后将这些局部的知识再组装成一个整体的功能,这样一个比较复杂的功能就比较好实习出来了,就拿断点续床和多线程下载来说,一般涉及到下载的项目,都会用到这部分的知识,所以按照我的思路就是,如果之前没有接触过这块,那么先做一个简单的下载功能,能够下载,然后在UI界面上进行进度更新,然后在加上断点续传的功能,这块没问题了之后加上多线程下载。具体里面涉及到哪些东西和别的知识点,在写的过程中,去查阅。

我分以下几步来慢慢实现这个功能:

1、简单的下载一个APK,实现断点续传,能够开始和暂停

2、加上多线程下载功能,将一个下载的内容分给几个线程来下载

3、加入ListView,显示多个应用,点击下载,然后切换到下载界面,进行进度更新


这篇我先做一第一部分,简单实现一个断点续传功能。关于断点续传,其实就是在用HttpURLConnection得到输入流,一点一点往文件里写入数据的时候,把这个完成度给记录下来,放在数据库中,下次再下载的时候,先从数据库中得到之前下载的完成度,然后根据这个完成度,在设置HttpURLConnection的请求头里的属性时,从得到的断点处开始请求数据到文件的长度,然后再去从输入流中读取数据到文件里,这样就基本实现了断点续传的功能了。

下面贴一下部分代码:

布局很简单,一个按钮和进度条

断点续传和多线程下载(一)


main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.shulf.downloadtest.MainActivity" >

<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="24dp"
android:layout_marginTop="18dp"
android:text="新浪微博"
android:textSize="20sp" />

<Button
android:id="@+id/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/name"
android:text="开始" />

<TextView
android:id="@+id/progressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/name"
android:layout_below="@+id/startBtn" />

<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/progressText"
android:layout_marginTop="15dp" />

</RelativeLayout>

MainActivity.java

package com.shulf.downloadtest;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity implements OnClickListener {

public final static String URL = "http://gdown.baidu.com/data/wisegame/f58a940533df3890/weibo_1477.apk";
private Button startBtn;
private TextView progressText;
private ProgressBar progressBar;
private MyHander handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

startBtn = (Button) findViewById(R.id.startBtn);
startBtn.setOnClickListener(this);

progressText = (TextView) findViewById(R.id.progressText);
progressBar = (ProgressBar) findViewById(R.id.progressBar);

handler = new MyHander(progressText, progressBar);
}

@Override
public void onClick(View view) {

if (startBtn.getText().equals("开始") || startBtn.getText().equals("继续")) {
startBtn.setText("暂停");
download();
} else {
startBtn.setText("继续");
pause();
}
}

public void download() {
DownloadAsyncTask task = new DownloadAsyncTask(
this.getApplicationContext(), handler);
task.execute(URL);
}

public void pause() {
DownloadTask task = DownloadManager.getInstance(
this.getApplicationContext()).getTask(URL);
task.STATE = 2;
}

/**
* 负责接收DownTask下载线程中的传来的消息,更新UI线程中的进度条和下载进度
* @author Administrator
*
*/

private class MyHander extends Handler {
private TextView tv;
private ProgressBar pb;

public MyHander(TextView tv, ProgressBar pb) {
this.tv = tv;
this.pb = pb;
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
tv.setText(msg.arg1 * 100 / msg.arg2 + "%");
pb.setMax(msg.arg2);
pb.setProgress(msg.arg1);
}
}
}
}

DownloadAsyncTask.java
package com.shulf.downloadtest;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;

/**
* 下载之前的准备,从DB中得到下载实体,或者创建下载实体
* @author Administrator
*
*/

public class DownloadAsyncTask extends AsyncTask<String, Void, DownloadInfo> {

private Context context;
private Handler handler;
private DownloadManager dm;

public DownloadAsyncTask(Context context, Handler handler) {
this.handler = handler;
this.context = context;
dm = DownloadManager.getInstance(context);
}

@Override
protected DownloadInfo doInBackground(String... args) {
DownloadInfo info = DownloadDao.getInstance(context).getDownloadInof(
args[0]);
if (info == null) {
int fileSize = 0;
try {
URL url = new URL(args[0]);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setReadTimeout(5000);
conn.setRequestMethod("GET");
fileSize = conn.getContentLength();

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
info = new DownloadInfo();
info.completeSize = 0;
info.fileSize = fileSize;
info.url = args[0];
DownloadDao.getInstance(context).saveDownloadInfo(info);

return info;
}
return info;
}

@Override
protected void onPostExecute(DownloadInfo info) {
super.onPostExecute(info);

DownloadTask task = new DownloadTask(context, info, handler);
task.STATE=1;
task.start();
return;

}

}

DownloadTask.java

package com.shulf.downloadtest;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;

import android.content.Context;
import android.os.Handler;
import android.os.Message;

/**
* 下载线程类,得到下载实体中的信息,开启线程进行下载
* @author Administrator
*
*/

public class DownloadTask extends Thread {

private String urlStr;
private Handler handler;
private int fileSize;
private Context context;
private DownloadInfo info;
public static int STATE = 1;// 1表示未下载状态,2表示暂停状态

public DownloadTask(Context context, DownloadInfo info, Handler handler) {
this.info = info;
this.context = context;
this.urlStr = info.url;
this.fileSize = info.fileSize;
this.handler = handler;
}

@Override
public void run() {
HttpURLConnection conn = null;
InputStream in = null;
RandomAccessFile randomAccessFile = null;
File saveFile = new File("/mnt/sdcard/", "test.apk");
int completeSize = info.completeSize;
try {
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
/**设置请求头的属性,从断点处开始到文件的长度*/
conn.setRequestProperty("Range", "bytes=" + completeSize + "-"
+ fileSize);

randomAccessFile = new RandomAccessFile(saveFile, "rwd");
randomAccessFile.seek(completeSize);

in = conn.getInputStream();
byte[] buffer = new byte[4096];
int length = -1;
while ((length = in.read(buffer)) != -1) {
randomAccessFile.write(buffer, 0, length);
completeSize += length;
DownloadDao.getInstance(context).updateComplete(urlStr,
completeSize);
Message msg = Message.obtain();
msg.what = 1;
msg.arg1 = completeSize;
msg.arg2 = fileSize;
handler.sendMessage(msg);
if (STATE == 2) {
return;
}

}
} catch (ProtocolException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
if (in != null) {
in.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
}catch(Exception e){
e.printStackTrace();
}

}
}

}

DownloadDao.java

package com.shulf.downloadtest;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
* 数据访问类
* @author Administrator
*
*/

public class DownloadDao {
private static DownloadDao dao;
private Context context;

private DownloadDao(Context context) {
this.context = context;
}

public static DownloadDao getInstance(Context context) {
if (dao == null) {
dao = new DownloadDao(context);
}
return dao;
}

public SQLiteDatabase getConnection() {
SQLiteDatabase db = null;
db = new DBHelper(context).getReadableDatabase();
return db;
}

/**
* 得到下载的完成进度
* @param url
* @return
*/
public synchronized int getComplete(String url) {
int complete = 0;
SQLiteDatabase db = getConnection();
String sql = "select compele_size from download_info where url = ?";
Cursor cursor = null;
try {
cursor = db.rawQuery(sql, new String[] { url });
while (cursor.moveToNext()) {
complete = cursor.getInt(0);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (db != null) {
db.close();
}
if (cursor != null) {
cursor.close();
}
}
return complete;
}

/**
*更新表中的下载进度
* @param url
* @param complete
*/
public synchronized void updateComplete(String url, int complete) {
SQLiteDatabase db = getConnection();
try {
String sql = "update download_info set compelete_size = ? where url = ?";
Object[] bindArgs = { complete, url };
db.execSQL(sql, bindArgs);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (db != null) {
db.close();
}
}
}

/**
* 从数据库中得到下载实体信息,然后转换成实体返回
* @param url
* @return
*/
public synchronized DownloadInfo getDownloadInof(String url) {
SQLiteDatabase db = getConnection();
Cursor cursor = null;
DownloadInfo info = null;
String sql = "select * from download_info where url = ?";
try {
cursor = db.rawQuery(sql, new String[] { url });
while (cursor.moveToNext()) {
info = new DownloadInfo();
info.completeSize = cursor.getInt(5);
info.fileSize = cursor.getInt(6);
info.url = cursor.getString(2);
}

} catch (Exception e) {
e.printStackTrace();
} finally {
if (db != null) {
db.close();
}
if (cursor != null) {
cursor.close();
}
}
return info;
}

/**
* 保存下载实体的信息
* @param info
*/
public synchronized void saveDownloadInfo(DownloadInfo info) {
SQLiteDatabase db = getConnection();
try {
String sql = "insert into download_info(thread_id,url,start_pos,end_pos,compelete_size,file_size) values (?,?,?,?,?,?)";
Object[] bindArgs = { 0, info.url, 0, 0, info.completeSize,
info.fileSize };
db.execSQL(sql, bindArgs);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (db != null) {
db.close();
}
}
}

}

DownloadInfo.java

package com.shulf.downloadtest;

/**
* 下载内容的实体
* @author Administrator
*
*/

public class DownloadInfo {

public int fileSize;

public int completeSize;

public String url;

public DownloadInfo() {
}

public DownloadInfo(int fileSize, int completeSize, String url) {
super();
this.fileSize = fileSize;
this.completeSize = completeSize;
this.url = url;
}

public int getFileSize() {
return fileSize;
}

public void setFileSize(int fileSize) {
this.fileSize = fileSize;
}

public int getCompleteSize() {
return completeSize;
}

public void setCompleteSize(int completeSize) {
this.completeSize = completeSize;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

@Override
public String toString() {
return "DownloadInfo [fileSize=" + fileSize + ", completeSize="
+ completeSize + ", url=" + url + "]";
}

}

DBHelper.java

package com.shulf.downloadtest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "download.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table download_info (_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer,"
+ "url char,start_pos integer , end_pos integer, compelete_size integer,file_size integer)");
}

@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {

}

}

这部分简单的实现了断线续传的功能,能够下载暂停了,实现的比较粗糙,很多细节的东西都还没有注意