您的位置:首页 > 其它

单线程下载,并实现断点续传

2017-02-23 15:56 295 查看
作者: 夏至 转载请保留以下申明,谢谢

http://blog.csdn.net/u011418943/article/details/56674086

现在网络框架越来越多,对于下载一个文件来说,用一个别人封装好的库,基本很容易就可以搞定,比如用okhttp3这个比较优秀的框架,里面它封装好的网络框架就非常优秀。

当然,有时候我们需要自定义我们自己的下载器,比如显示一些其他要实现其他属性,这个时候,就不得不自己去封装了。其实也不是太难,就是可能在考虑上欠缺了吧。

这篇博客是看完慕课网的异步下载笔记,不了解的可以先去看。连接如下:http://www.imooc.com/learn/363

废话不多说,先上效果图:



工程下载地址如下:

https://git.oschina.net/zhengshaorui/filedownloaduesdb.git

扩展之后,我们可以实现apk的在线更新功能,效果图如下所示:



工程下载地址如下:

https://git.oschina.net/zhengshaorui/ApkAutoUpdateDemo.git

首先,通过 Serioliable 传递参数,定义一个实体类:

/**
* 用serializable就可以接受intent的参数了
* @author zhengshaorui
*
*/
public class FileInfo implements Serializable{

private int id;
private String apkurl;
private String fileDir;
private String fileName;
private int length;
private File Dir;
private int finished;
private int threadCount;

public FileInfo() {
super();
}

public FileInfo(int id, String apkurl, String fileDir, String fileName,
int length, File dir, int finished, int threadCount) {
this.id = id;
this.apkurl = apkurl;
this.fileDir = fileDir;
this.fileName = fileName;
this.length = length;
Dir = dir;
this.finished = finished;
this.threadCount = threadCount;
}

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getApkurl() {
return apkurl;
}
public void setApkurl(String apkurl) {
this.apkurl = apkurl;
}
public String getFileName() {
return fileName;
}

@Override
public String toString() {
return "FileInfo{" +
"id=" + id +
", apkurl='" + apkurl + '\'' +
", fileDir='" + fileDir + '\'' +
", fileName='" + fileName + '\'' +
", length=" + length +
", Dir=" + Dir +
", finished=" + finished +
", threadCount=" + threadCount +
'}';
}

public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getFinished() {
return finished;
}
public void setFinished(int finished) {
this.finished = finished;
}

public String getFileDir() {
return fileDir;
}

public void setFileDir(String fileDir) {
this.fileDir = fileDir;
}

public File getDir() {
return Dir;
}

public void setDir(File dir) {
Dir = dir;
}

public int getThreadCount() {
return threadCount;
}

public void setThreadCount(int threadCount) {
this.threadCount = threadCount;
}
}


这里你可能有疑问,threadcount是什么gui,还有其他乱七八糟的属性,别急,这个是为了接下来的多线程的封装的。

然后,main函数中,把参数传递给service

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fileDir = getFilesDir()+"/mydownload/";
mFileInfo = new FileInfo(0,"http://images.infzm.com/mobile/infzmreader.apk",
fileDir,"north.apk",0,null,0);
}
public void start(View view){
Intent start = new Intent(this,DowaloadService.class);
start.putExtra("fileinfo",mFileInfo);
start.putExtra("service",1);
startService(start);
}
public void stop(View view){
Intent start = new Intent(this,DowaloadService.class);
start.putExtra("fileinfo",mFileInfo);
start.putExtra("service",0);
startService(start);
}


service 中,先启动线程检测文件的大小;把它保存到Fileinfo里面,再启动下城下载:

public int onStartCommand(Intent intent, int flags, int startId) {
FileInfo mFileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");
int status = intent.getIntExtra("service",0);
if (status == 1){
Log.d(TAG, "start: "+mFileInfo);
}else{
Log.d(TAG, "stop: ");
}
new download(mFileInfo).start();
return super.onStartCommand(intent, flags, startId);
}
class download extends  Thread{
FileInfo mFileInfo;
public download(FileInfo fileinfo){
this.mFileInfo = fileinfo;
}
@Override
public void run() {
super.run();
HttpURLConnection conn = null;
try {
URL url = new URL(mFileInfo.getApkurl());
conn = (HttpURLConnection) url.openConnection();
//加上这句是为了防止connection.getContentLength()获取不到
conn.setRequestProperty("Accept-Encoding", "identity");
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
if (conn.getResponseCode() == 200){
int length = conn.getContentLength();
if (length < 0) return;
else mFileInfo.setLength(length);
}
File dir = new File(mFileInfo.getFileDir());
if (!dir.exists()){
dir.mkdir();
}
mFileInfo.setDir(dir);
Log.d(TAG, "run: "+mFileInfo);
new DownloadTask(mContext).download(mFileInfo);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "error: "+e.toString());
}finally {
conn.disconnect();
}
}
}


其中下载方法 DownloadTask 如下所示:

public class DownloadTask {
private static final String TAG = "zsr";
private Context mContext;
private int downloadlength = 0;
public DownloadTask(Context mContext) {
this.mContext = mContext;
}
public void download(FileInfo fileInfo){
new DownloadThread(fileInfo).start();
}
class DownloadThread extends Thread{
FileInfo mFileInfo;
public DownloadThread(FileInfo mFileInfo) {
this.mFileInfo = mFileInfo;
}
@Override
public void run() {
super.run();
HttpURLConnection conn = null;
RandomAccessFile raf = null;
BufferedInputStream bis = null;
try {
URL url = new URL(mFileInfo.getApkurl());
conn = (HttpURLConnection) url.openConnection();
//加上这句是为了防止connection.getContentLength()获取不到
conn.setRequestProperty("Accept-Encoding", "identity");
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
int start = 0;
int end = mFileInfo.getLength();
//设置下载位置
conn.setRequestProperty("Range", "bytes="+start+"-"+end);
//文件写入位置
File file = new File(mFileInfo.getDir(),mFileInfo.getFileName());
raf = new RandomAccessFile(file,"rwd");
raf.seek(start);
int len = -1;
byte[] bytes = new byte[1024*2];
bis = new BufferedInputStream(conn.getInputStream());
while( (len = bis.read(bytes)) != -1 ){
raf.write(bytes,0,len);
downloadlength += len;
Log.d(TAG, "run: "+downloadlength);
}
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "downloaderror: "+e.toString());
}finally {
if (bis != null) try {
bis.close();
if (raf != null) raf.close();
} catch (IOException e) {
e.printStackTrace();
}
if (conn != null) conn.disconnect();
}
}
}
}


log 和文件如下:





那么如何把文件保存到数据库中,实现断点续传呢 ,其实说白了,就是把上次的断点保存在文件、map或者数据库中。这里保存在数据库中

首先定义一个实体类,ThreadInfo:

public class ThreadInfo {
private int id;
private String url;
private int start;
private int end;
private int finished;

public ThreadInfo() {
super();
}

public ThreadInfo(int id, String url, int start, int end, int finished) {
super();
this.id = id;
this.url = url;
this.start = start;
this.end = end;
this.finished = finished;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getFinished() {
return finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start
+ ", end=" + end + ", finished=" + finished + "]";
}

}


ThreadInfo 是来记录线程信息的 ,所以,而数据的保存,我们用数据库来保存;首先,先定义表单:

public class DBhelper extends SQLiteOpenHelper{
public static final String DB_NAME = "download.db";
public static final int VERSION = 1;
private static final String SQL_CREATE_TABLE = "create table if not exists thread_info(" +
"_id integer primary key autoincrement," +
"thread_id integer," +
"url text," +
"start integer," +
"end integer," +
"finished interger)";
private static final String SQL_DROP = "drop table if exists thread_info";

public DBhelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(SQL_CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {
// TODO Auto-generated method stub
db.execSQL(SQL_DROP);
db.execSQL(SQL_CREATE_TABLE);
}
}


上面的实现很简单,就是创建一个数据库,然后,我们需要给它添加 listener回调接口:

public interface DBTreadListener {
/**
* 插入线程信息
* @param threadInfo
*/
public void insertThread(ThreadInfo threadInfo);
/**
* 删除数据库,用两个标志位来判断
* @param url
* @param thread_id
*/
public void deleteThread(String url, int thread_id);
/**
* 更新线程下载进度 finished
* @param url
* @param thread_id
* @param finished
*/
public void updateThread(String url, int thread_id, int finished);
/**
* 查询文件的线程信息,以一个List的形式返回
* @param url
* @return
*/
public List<ThreadInfo> getThreads(String url);
/**
* 判断线程是否存在,如果存在则更新,如果不存在则创建
* @param url
* @return
*/
public boolean isThreadExsits(String url, int thread_id);
}


回调接口中,我们要实现的方法就是数据库的 增删查改,还有就是检查该数据库,是否有线程和判断线程是否存在。接下来就是数据库的实现方法了:

/**
* 数据库接口的实现
*
* @author zhengshaorui
*
*/
public class DBThreadListenerImp implements DBTreadListener {
private DBhelper mDBhelper = null;
public DBThreadListenerImp(Context context) {
// 在接口实现的时候,创建数据库表
mDBhelper = new DBhelper(context, DBhelper.DB_NAME, null,
DBhelper.VERSION);
}
public void insertThread(ThreadInfo threadInfo) {
// TODO Auto-generated method stub
SQLiteDatabase db = mDBhelper.getWritableDatabase();
db.execSQL(
"insert into thread_info(thread_id,url,start,end,finished) values(?,?,?,?,?)",
new Object[] {threadInfo.getId(), threadInfo.getUrl(),
threadInfo.getStart(), threadInfo.getEnd(),
threadInfo.getFinished() });
db.close();
}
@Override
public void deleteThread(String url, int thread_id) {
// TODO Auto-generated method stub
SQLiteDatabase db = mDBhelper.getWritableDatabase();
db.execSQL("delete from thread_info where url = ? and thread_id = ?",
new Object[] { url, thread_id });
db.close();
}
@Override
public void updateThread(String url, int thread_id, int finished) {
// TODO Auto-generated method stub
Log.d("zsr", "finish: "+finished);
SQLiteDatabase db = mDBhelper.getWritableDatabase();
db.execSQL(
"update thread_info set finished = ? where url = ? and thread_id = ?",
new Object[] { finished, url, thread_id });
db.close();
}
public List<ThreadInfo> getThreads(String url) {
List<ThreadInfo> mList = new ArrayList<ThreadInfo>();
SQLiteDatabase db = mDBhelper.getWritableDatabase();
Cursor cursor = db.rawQuery("select * from thread_info where url = ?",
new String[] { url });
if (cursor.moveToFirst()) {
do {
ThreadInfo mThreadInfo = new ThreadInfo();
mThreadInfo.setUrl(cursor.getString(cursor
.getColumnIndex("url")));
mThreadInfo.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
mThreadInfo.setStart(cursor.getInt(cursor
.getColumnIndex("start")));
mThreadInfo.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
mThreadInfo.setFinished(cursor.getInt(cursor
.getColumnIndex("finished")));
mList.add(mThreadInfo);
} while (cursor.moveToNext());
}
db.close();
cursor.close();
return mList;
}
@Override
public boolean isThreadExsits(String url, int thread_id) {
boolean isExsits = false;
SQLiteDatabase db = mDBhelper.getWritableDatabase();
Cursor cursor = db.rawQuery(
"select * from thread_info where url = ? and thread_id = ?",
new String[] { url, thread_id + "" });
isExsits = cursor.moveToNext();
db.close();
cursor.close();
return isExsits;
}
}


ok,这样,我们的续传的数据库就写好了,接下来就是对把线程信息给保存起来了;我们要修改一下我们的 DownloadTask 这个类:

首先,先初始化数据库:

private DBThreadListener mDbThreadListener;
public DownloadTask(Context mContext) {
this.mContext = mContext;
mDbThreadListener = new DBThreadListenerImp(mContext);
}


然后修改其中的download:

public void download(FileInfo fileInfo){
mFileInfo = fileInfo;
List<ThreadInfo> threadInfos = mDbThreadListener.getThreads(fileInfo.getApkurl());
ThreadInfo mThreadInfo;
if (threadInfos.size() == 0){ //第一次启动
mThreadInfo = new ThreadInfo(0,fileInfo.getApkurl(),0,fileInfo.getLength(),0,fileInfo.getLength());
}else{
mThreadInfo = threadInfos.get(0);
}
new DownloadThread(mThreadInfo).start();
}


然后修改DownloadThread:

class DownloadThread extends Thread{
ThreadInfo mThreadInfo;
public DownloadThread(ThreadInfo threadinfo) {
this.mThreadInfo = threadinfo;
}
@Override
public void run() {
super.run();
HttpURLConnection conn = null;
RandomAccessFile raf = null;
BufferedInputStream bis = null;
//开始之前先检测是否数据库有线程信息
if (!mDbThreadListener.isThreadExsits(mThreadInfo.getUrl(),mThreadInfo.getId())){
mDbThreadListener.insertThread(mThreadInfo); //没有则把线程信息保存进去
}
try {
URL url = new URL(mThreadInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
//加上这句是为了防止connection.getContentLength()获取不到
conn.setRequestProperty("Accept-Encoding", "identity");
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
//设置下载位置
int start = mThreadInfo.getStart()+mThreadInfo.getFinished(); //起始位置
int end = mThreadInfo.getEnd(); //结束位置
conn.setRequestProperty("Range", "bytes="+start+"-"+end);
//文件写入位置
File file = new File(mFileInfo.getDir(),mFileInfo.getFileName());
raf = new RandomAccessFile(file,"rwd");
raf.seek(start);

int len = -1;
downloadlength += len; //主要是暂停之后,重新下载,获取以前的进度
byte[] bytes = new byte[1024*2];
bis = new BufferedInputStream(conn.getInputStream());
while( (len = bis.read(bytes)) != -1 ){
raf.write(bytes,0,len);
downloadlength += len;
if (isPause) {
//数据库保存当前信息
mDbThreadListener.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(),
downloadlength);
return;
}
//以百分比的形式传递
Log.d(TAG, "run: "+downloadlength*100/mFileInfo.getLength());
}
//如果完成,则把以前的线程信息干掉
mDbThreadListener.deleteThread(mThreadInfo.getUrl(),mThreadInfo.getId());
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "downloaderror: "+e.toString());
}finally {
if (bis != null) try {
bis.close();
if (raf != null) raf.close();
} catch (IOException e) {
e.printStackTrace();
}
if (conn != null) conn.disconnect();
}
}
}


这样就完成了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息