Android多线程续传下载是Android应用中常见的功能。今天我们就来实现这样一个实例。实现效果如图:
结构如下图:
1)Activity向service传参数:将文件名,文件路径等传入service。
2)service 启动新的线程下载网络文件并存到本地。
3)向本地数据库写入下载的进度,以便确定下次下载的起始点。
4)broadcast 回传进度到Activity。
网络下载的关键点如下:
1)获取网络文件的长度
2)在本地创建文件,设置其长度和网络文件长度相同。
3)从上次下载的位置下载数据,同时保存进度到数据库。
4)将下载进度回传给Activity
开发过程如下:
(一)编写UI和实体类
文件类
public class FileInfo implements Serializable{
private int id;
private String url;
private String fileName;
private int length;
private int finished;
}
线程类
public class ThreadInfo {
private int id;
private String url;
private int start;
private int end;
private int finished;
}
(二)编写service获得Activity传入的参数
public class DownloadService extends Service{
public static final String ACTION_START ="ACTION_START";
public static final String ACTION_STOP="ACTION_STOP";
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 获得Activity传来的参数
if(ACTION_START.equals(intent.getAction()))
{
FileInfo fileInfo=(FileInfo)intent.getSerializableExtra("fileInfo");
Log.i("test ","start:"+fileInfo.toString());
}else if(ACTION_STOP.equals(intent.getAction())){
FileInfo fileInfo=(FileInfo)intent.getSerializableExtra("fileInfo");
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}
(四)在Activity中设置点击传递文件参数
final FileInfo fileInfo=new FileInfo(0, "http://101.4.136.35:9999/fw1.dl.wdjcdn.com/files/third/WanDouJiaSetup_a9.exe",
"王纪坤大逗比", 0, 0);
mBtStart.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
// 通过intent,传递参数给service
Intent intent=new Intent(MainActivity.this,DownloadService.class);
intent.setAction(DownloadService.ACTION_START);
intent.putExtra("fileinfo", fileInfo);
startService(intent);
}
});
(五)在service中创建子线程,实现文件的下载
在子线程中,连接网络,获取网络文件长度。在本地目录创建相同长度的文件。并返回本地文件。
class InitThread extends Thread{
private FileInfo mFileInfo=null;
public InitThread(FileInfo mFileInfo)
{
this.mFileInfo=mFileInfo;
}
public void run(){
HttpURLConnection httpUrlConn = null;
RandomAccessFile raf = null;
try {
//1连接网络文件
URL url=new URL(mFileInfo.getUrl());
httpUrlConn=(HttpURLConnection)url.openConnection();
httpUrlConn.setConnectTimeout(3000);
httpUrlConn.setRequestMethod("GET");
int length=-1;
if(httpUrlConn.getResponseCode()==HttpStatus.SC_OK)
{
//2获得文件长度
length=httpUrlConn.getContentLength();
}
if(length<=0)
{
return ;
}else{
//3在本地创建文件
File dir=new File(DOWNLOAD_PATH);
if(!dir.exists()){
dir.mkdir();
}
File file=new File(dir,mFileInfo.getFileName());
raf=new RandomAccessFile(file,"rwd");
raf.setLength(length);
mFileInfo.setLength(length);
mHandler.obtainMessage(MSG_INIT,mFileInfo).sendToTarget();
}
//4设置本地文件长度
} catch (Exception e) {
// TODO: handle exception
}finally{
httpUrlConn.disconnect();
try {
raf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
(六)创建数据库的连接和数据库访问接口
在数据库中保持下载进度
数据库的字段和线程信息相对应
private static final String SQL_CREATE="create table thread_info(_id integer primary key autoincreament," +
"thread_id integer,url text, start integer, end integer ,finished integer)";
定义数据库接口
public interface ThreadDAO {
/*
*插入线程信息
*/
public void insertThread(ThreadInfo threadInfo);
/*
* 删除线程信息
*/
public void deleteThread(String url,int thread_id);
/*
* 更新线程的下载进度
*/
public void updateThread(String url,int thread_id,int finished);
/*
* 查询文件的线程信息,以集合的形式返回
*/
public List<Thread> getThreads(String url);
/*
* 判断线程的信息是否存在
*/
public boolean isExists(String url,int thread_id);
}
实现数据库接口
public class ThreadDAOImpl implements ThreadDAO{
private DBhelper dbHelper;
public ThreadDAOImpl(Context context){
dbHelper=new DBhelper(context);
}
@Override
public void insertThread(ThreadInfo threadInfo) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.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 database=dbHelper.getWritableDatabase();
database.execSQL("delete from thread_info where url="+url+"and thread_id="+thread_id);
database.close();
}
@Override
public void updateThread(String url, int thread_id, int finished) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getWritableDatabase();
db.execSQL("update thread_info set finished=? where url= ?and thread_id=?", new Object[]{finished,url,thread_id});
}
@Override
public List<ThreadInfo> getThreads(String url) {
// TODO Auto-generated method stub
List<ThreadInfo> list=new ArrayList<ThreadInfo>();
SQLiteDatabase db=dbHelper.getWritableDatabase();
Cursor cursor=db.rawQuery("select * from thread_info where url=?", new String[]{url});
while(cursor.moveToNext()){
ThreadInfo thread=new ThreadInfo();
thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));
thread.setStart(cursor.getInt(cursor.getColumnIndex("start")));
thread.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
thread.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
list.add(thread);
}
db.close();
return list;
}
@Override
public boolean isExists(String url,int thread_id) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getWritableDatabase();
Cursor cursor=db.rawQuery("select * from thread_info where url=? and thread_id=?", new String[]{url,thread_id+""});
boolean exists=cursor.moveToNext();
cursor.close();
db.close();
return exists;
}
}
(七)定义下载任务类
public class DownloadTask {
private Context mContext=null;
private FileInfo mFileInfo=null;
private ThreadDAO mDao=null;
private int mFinished=0;
public boolean isPaused=false;
HttpURLConnection conn=null;
RandomAccessFile raf=null;
InputStream input=null;
public DownloadTask(Context context,FileInfo fileInfo)
{
this.mContext=context;
this.mFileInfo=fileInfo;
mDao=new ThreadDAOImpl(mContext);
}
public void download(){
//读取数据库的线程信息
List<ThreadInfo> threadInfos= mDao.getThreads(mFileInfo.getUrl());
ThreadInfo threadInfo=null;
if(threadInfos.size()==0){
//初始化县城信息对象
threadInfo=new ThreadInfo(0, mFileInfo.getUrl(), 0, mFileInfo.getLength(), 0);
}else{
threadInfo=threadInfos.get(0);
}
new DownloadThread(threadInfo).start();
}
/**
* 下载线程
*/
class DownloadThread extends Thread{
private ThreadInfo mThreadInfo=null;
public DownloadThread(ThreadInfo mThreadInfo) {
super();
this.mThreadInfo = mThreadInfo;
}
public void run(){
//向数据库插入线程信息
if(!mDao.isExists(mThreadInfo.getUrl(), mThreadInfo.getId())){
mDao.insertThread(mThreadInfo);
}
try {
URL url=new URL(mThreadInfo.getUrl());
conn=(HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
//设置下载位置
int start=mThreadInfo.getStart()+mThreadInfo.getFinished();
conn.setRequestProperty("Range", "bytes="+start+"-"+mThreadInfo.getEnd());
//设置文件写入位置
File file=new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());
raf=new RandomAccessFile(file, "rwd");
raf.seek(start);
Intent intent=new Intent(DownloadService.ACTION_UPDATE);
mFinished+=mThreadInfo.getFinished();
//开始下载
if(conn.getResponseCode()==HttpStatus.SC_OK){
//读取数据
input=conn.getInputStream();
byte[] buffer=new byte[1024*4];
int len=-1;
long time=System.currentTimeMillis();
while((len=input.read(buffer))!=-1){
//写入文件
raf.write(buffer,0,len);
//把下载进度发送广播给Activity
mFinished+=len;
if(System.currentTimeMillis()-time>500){
time=System.currentTimeMillis();
intent.putExtra("finished", mFinished*100/mFileInfo.getLength());
mContext.sendBroadcast(intent);
}
//在下载暂停时,保存下载进度
if(isPaused){
mDao.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mThreadInfo.getFinished());
return;
}
}
//删除线程信息
mDao.deleteThread(mThreadInfo.getUrl(), mThreadInfo.getId());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
conn.disconnect();
try {
raf.close();
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}