Android之断点续传(二)

时间:2022-06-28 04:01:45

许久没有更新了,上次写的Android之断点续传(一)也有段时间了,上次实现了单个文件单线程的断点续传,但还是远远不够,这次在这基础实现多个文件多线程下载还有一些优化。

这次的改动,除了将界面修改成ListView,这里就不多说,重点是单个文件的多线程下载,这里引用网上常用的一个方法,将文件分成几块(几个线程就区分成几块),然后每个线程负责下载各自的文件块,在将各个线程下载进度累加以广播形式发送到主Activity更新进度条。如果暂停,将多个线程信息保存到数据库,继续下载的时候从数据库拿出来。在做的过程也出现了一些线程安全问题,数据库锁的问题,具体介绍在代码都做了注释。下面给出代码。

MainActivity.java

package com.linxiaosheng.test3;

import java.util.ArrayList;
import java.util.List;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {
private List<FileInfo> files;
private ListView listView;
private FileItemAdapter adapter;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
listView=(ListView)findViewById(R.id.listView1);
files=new ArrayList<FileInfo>();
files.add(new FileInfo(0,"http://download.firefox.com.cn/releases/stub/official/zh-CN/Firefox-latest.exe", "Firefox-latest.exe", 0, 0));
files.add(new FileInfo(1,"http://dl.liebao.cn/kb/KSbrowser_5.3.100.10349.exe", "liebao.exe", 0, 0));
files.add(new FileInfo(2,"http://dldir1.qq.com/invc/tt/QQBrowser_Setup_SEM1.exe", "qq.exe", 0, 0));
files.add(new FileInfo(3,"http://download.firefox.com.cn/releases/stub/official/zh-CN/Firefox-latest.exe", "Firefox-latest.exe", 0, 0));
files.add(new FileInfo(4,"http://download.firefox.com.cn/releases/stub/official/zh-CN/Firefox-latest.exe", "Firefox-latest.exe", 0, 0));
adapter=new FileItemAdapter(this, files);
listView.setAdapter(adapter);
//广播
IntentFilter filter=new IntentFilter();
filter.addAction(DownloadService.ACTION_UPDATE);
filter.addAction(DownloadService.ACTION_FINISH);
registerReceiver(receiver, filter);
}
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
};

BroadcastReceiver receiver=new BroadcastReceiver(){

@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
if(DownloadService.ACTION_UPDATE.equals(intent.getAction())){
int finished=intent.getIntExtra("finished", 0);
int id=intent.getIntExtra("id", 1);
Log.i("sysout", finished+"");
adapter.updateProgressBar(id,finished);
}else if(DownloadService.ACTION_FINISH.equals(intent.getAction())){
FileInfo fileInfo=(FileInfo) intent.getSerializableExtra("fileinfo");
adapter.updateProgressBar(fileInfo.getId(), 100);
Toast.makeText(context, fileInfo.getFileName()+"下载完成!", Toast.LENGTH_SHORT).show();
}
}

};

}

DownloadService.java

package com.linxiaosheng.test3;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import java.util.LinkedHashMap;import java.util.Map;import org.apache.http.HttpStatus;import android.app.Service;import android.content.Intent;import android.os.Environment;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.util.Log;public class DownloadService extends Service {	public static final String ACTION_START = "ACTION_START";	public static final String ACTION_STOP = "ACTION_STOP";	public static final String ACTION_UPDATE="ACTION_UPDATE";	public static final String ACTION_FINISH="ACTION_FINISH";	public static final String DOWNLOAD_PATH=Environment.getExternalStorageDirectory().getAbsolutePath()+"/downloads/";	public static final int MSG_INIT=0;	private Map<Integer, DownloadTask> mTasks=new LinkedHashMap<Integer, DownloadTask>();	@Override	public int onStartCommand(Intent intent, int flags, int startId) {		// TODO Auto-generated method stub		if(ACTION_START.equals(intent.getAction())){			FileInfo fileInfo=(FileInfo) intent.getSerializableExtra("fileInfo");			Log.i("sysout", "开始下载");			//文件初始化			new MyThread(fileInfo).start();		}		if(ACTION_STOP.equals(intent.getAction())){			Log.i("sysout", "停止下载");			FileInfo fileInfo=(FileInfo) intent.getSerializableExtra("fileInfo");			//从集合中去除下载任务			DownloadTask task=mTasks.get(fileInfo.getId());			if(task!=null){				//暂停下载任务				task.pause=true;			}		}		return super.onStartCommand(intent, flags, startId);	}	@Override	public IBinder onBind(Intent intent) {		// TODO Auto-generated method stub		return null;	}	Handler handler=new Handler(){		@Override		public void handleMessage(Message msg) {			// TODO Auto-generated method stub			switch(msg.what){			case MSG_INIT:				FileInfo fileInfo=(FileInfo)msg.obj;				Log.i("sysout", fileInfo.toString());				DownloadTask downloadTask=new DownloadTask(DownloadService.this, fileInfo,2);				downloadTask.pause=false;				downloadTask.download();				mTasks.put(fileInfo.getId(), downloadTask);				break;			}					}	};	/**	 * 用于初始化mfileInfo,联网更新mfileInfo	 * 一切的联网操作都要在子线程中执行	 */	class MyThread extends Thread{		private FileInfo mfileInfo;		public MyThread(FileInfo mfileInfo) {			// TODO Auto-generated constructor stub			this.mfileInfo=mfileInfo;		}		@Override		public void run() {			// TODO Auto-generated method stub			RandomAccessFile accessFile=null;			HttpURLConnection connection=null;			try {				URL httpurl=new URL(mfileInfo.getUrl());				connection=(HttpURLConnection) httpurl.openConnection();				connection.setConnectTimeout(5000);				connection.setRequestMethod("GET");				File dir=new File(DOWNLOAD_PATH);				if(!dir.exists()){					dir.mkdir();				}				File file=new File(DOWNLOAD_PATH,mfileInfo.getFileName());				int length=-1;				//判断网络连接是否成功				if(connection.getResponseCode()==HttpStatus.SC_OK){					//设置网络文件的长度					length=connection.getContentLength();				}				if(length<=0)					return;								accessFile=new RandomAccessFile(file, "rwd");				accessFile.setLength(length);				mfileInfo.setLength(length);				//发送消息				Message msg=handler.obtainMessage(MSG_INIT,mfileInfo);				msg.sendToTarget();			} catch (IOException e) {				// TODO Auto-generated catch block				e.printStackTrace();			}finally{				if(accessFile!=null){					try {						accessFile.close();					} catch (IOException e) {						// TODO Auto-generated catch block						e.printStackTrace();					}				}				if(connection!=null){					connection.disconnect();				}			}		}	}}

DownloadTask.java

package com.linxiaosheng.test3;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import org.apache.http.HttpStatus;import android.content.Context;import android.content.Intent;import android.util.Log;public class DownloadTask {	private Context mContexta=null;	private FileInfo mFileInfo=null;	private IThreadDAO threadDAO=null;	private long mFinished=0;	public boolean pause=false;	private int mThreadNum=1;	private List<DownloadThread> threads;	private static ExecutorService executorService=Executors.newCachedThreadPool();	public DownloadTask(Context mContexta,FileInfo mFileInfo,int mThreadNum){		this.mContexta=mContexta;		this.mFileInfo=mFileInfo;		this.mThreadNum=mThreadNum;		this.threadDAO=new ThreadImplDAO(mContexta);	}	public void download(){		//读取数据库线程的信息		List<ThreadInfo> list=threadDAO.getThreadsByUrl(mFileInfo.getUrl());		if(list.size()==0){			//获取每个线程下载长度			int length=mFileInfo.getLength()/mThreadNum;			for(int i=0;i<mThreadNum;i++){				ThreadInfo info=new ThreadInfo(i+1,mFileInfo.getUrl(), i*length,(i+1)*length-1,0);				if(i==mThreadNum-1){					info.setEnd(mFileInfo.getLength());				}				//添加到线程集合				list.add(info);				//向数据库插入线程信息				threadDAO.insertThread(info);			}					}		threads=new ArrayList<DownloadTask.DownloadThread>();		//启动多个线程进行下载		for(ThreadInfo info:list){			DownloadThread thread=new DownloadThread(info);			//thread.start();			DownloadTask.executorService.execute(thread);			threads.add(thread);		}	}			public synchronized void checkAllThreadIfFinish(){		boolean allFinish=true;		for(int i=0;i<threads.size();i++){			if(threads.get(i).isFinish){							}else{				allFinish=false;				break;			}		}		//文件下载完成,发送广播		if(allFinish){			//下载完毕,删除线程信息			threadDAO.removeThread(mFileInfo.getUrl());			//发送广播			Intent intent=new Intent();			intent.setAction(DownloadService.ACTION_FINISH);			intent.putExtra("fileinfo", mFileInfo);			mContexta.sendBroadcast(intent);		}	}	class DownloadThread extends Thread{		private ThreadInfo threadInfo=null;		private boolean isFinish=false;		public DownloadThread(ThreadInfo threadInfo) {			super();			this.threadInfo = threadInfo;		}		@Override		public void run() {						InputStream inputStream=null;			HttpURLConnection connection=null;			RandomAccessFile accessFile=null;			try {				URL url=new URL(threadInfo.getUrl());				connection=(HttpURLConnection) url.openConnection();				connection.setConnectTimeout(5000);				connection.setRequestMethod("GET");				//设置下载位置				int start=threadInfo.getStart()+threadInfo.getFinished();				//设置请求属性,设置下载的范围,避免从头开始下载				//Log.i("sysout", "byte="+start+"-"+threadInfo.getEnd());				//注意这里是bytes带有s的				connection.setRequestProperty("Range", "bytes="+start+"-"+threadInfo.getEnd());				//设置下载文件				File file=new File(DownloadService.DOWNLOAD_PATH,mFileInfo.getFileName());				accessFile=new RandomAccessFile(file, "rwd");				//设置文件的写入位置				accessFile.seek(start);				//更新				Intent intent=new Intent();				intent.setAction(DownloadService.ACTION_UPDATE);				//设置整个文件已完成进度				mFinished+=threadInfo.getFinished();				Log.i("sysout","回复码"+connection.getResponseCode());				//这里是选择性下载的,返回的状态码是206				if(connection.getResponseCode()==HttpStatus.SC_PARTIAL_CONTENT){					//读取数据					inputStream=connection.getInputStream();					byte[] b=new byte[1024*4];					int len=-1;					long time=System.currentTimeMillis();					while((len=inputStream.read(b))!=-1){						accessFile.write(b,0,len);						//把下载的进度广播给activity						mFinished+=len;						//设置单个线程下载的进度						threadInfo.setFinished(threadInfo.getFinished()+len);						if(System.currentTimeMillis()-time>1000){							Log.i("sysout","mFinished:"+mFinished);							intent.putExtra("finished",Integer.parseInt(String.valueOf(mFinished*100/mFileInfo.getLength())));							intent.putExtra("id", mFileInfo.getId());							time=System.currentTimeMillis();							mContexta.sendBroadcast(intent);						}												if(pause){							Log.i("sysout","状态pause"+pause);							//保存单个线程下载情况							Log.i("sysout", threadInfo.toString());							threadDAO.updateThread(threadInfo.getThreadid(),threadInfo.getUrl(), threadInfo.getFinished());							return;						}					}					//标识线程下载完毕					isFinish=true;										//检查文件的所有线程是否都下载完毕					checkAllThreadIfFinish();											}			} catch (Exception e) {				// TODO Auto-generated catch block				e.printStackTrace();			}finally{				if(inputStream!=null){					try {						inputStream.close();					} catch (IOException e) {						// TODO Auto-generated catch block						e.printStackTrace();					}				}				if(accessFile!=null){					try {						accessFile.close();					} catch (IOException e) {						// TODO Auto-generated catch block						e.printStackTrace();					}				}				if(connection!=null){					connection.disconnect();				}			}											}	}}
FileInfo.java

package com.linxiaosheng.test3;import java.io.Serializable;public class FileInfo implements Serializable{	private int id;	private String url;	private String fileName;	private int length;	private int finished;	public FileInfo() {		super();	}	public FileInfo(int id, String url, String fileName, int length,			int finished) {		super();		this.id = id;		this.url = url;		this.fileName = fileName;		this.length = length;		this.finished = finished;	}	public int getId() {		return id;	}	public void setId(int id) {		this.id = id;	}	public String getUrl() {		return url;	}	public void setUrl(String url) {		this.url = url;	}	public String getFileName() {		return fileName;	}	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;	}	@Override	public String toString() {		return "FileInfo [id=" + id + ", url=" + url + ", fileName=" + fileName				+ ", length=" + length + ", finished=" + finished + "]";	}	}
FileItemAdapter.java
package com.linxiaosheng.test3;import java.util.List;import android.content.Context;import android.content.Intent;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.Button;import android.widget.ProgressBar;import android.widget.TextView;public class FileItemAdapter extends BaseAdapter {	private Context mContext;	private List<FileInfo> mListFiles;		public FileItemAdapter(Context context,List<FileInfo> list){		this.mContext=context;		this.mListFiles=list;	}	@Override	public int getCount() {		// TODO Auto-generated method stub		return mListFiles.size();	}	@Override	public Object getItem(int position) {		// TODO Auto-generated method stub		return mListFiles.get(position);	}	@Override	public long getItemId(int position) {		// TODO Auto-generated method stub		return position;	}	@Override	public View getView(int position, View convertView, ViewGroup parent) {		// TODO Auto-generated method stub		ViewHolder holder=null;		final FileInfo fileInfo=mListFiles.get(position);		if(convertView==null){			convertView=LayoutInflater.from(mContext).inflate(R.layout.item,null);			holder=new ViewHolder();			holder.textView1=(TextView) convertView.findViewById(R.id.textView1);			holder.progressBar1=(ProgressBar) convertView.findViewById(R.id.progressBar1);			holder.button1=(Button)convertView.findViewById(R.id.button1);			holder.button2=(Button)convertView.findViewById(R.id.button2);			//初始化执行一次即可			holder.textView1.setText(fileInfo.getFileName());			holder.progressBar1.setMax(100);			holder.button1.setOnClickListener(new OnClickListener() {								@Override				public void onClick(View v) {					// TODO Auto-generated method stub					Intent intent= new Intent(mContext,DownloadService.class);					intent.setAction(DownloadService.ACTION_START);					intent.putExtra("fileInfo", fileInfo);					mContext.startService(intent);				}			});			holder.button2.setOnClickListener(new OnClickListener() {								@Override				public void onClick(View v) {					// TODO Auto-generated method stub					Intent intent= new Intent();					intent.setAction(DownloadService.ACTION_STOP);					intent.putExtra("fileInfo", fileInfo);					intent.setClass(mContext, DownloadService.class);					mContext.startService(intent);				}			});			convertView.setTag(holder);		}else{			holder=(ViewHolder)convertView.getTag();		}				holder.progressBar1.setProgress(fileInfo.getFinished());		return convertView;	}			public void updateProgressBar(int id,int finish){		FileInfo fileInfo=mListFiles.get(id);		fileInfo.setFinished(finish);		//更新listview		notifyDataSetChanged();	}		static class ViewHolder{		TextView textView1;		ProgressBar progressBar1;		Button button1;		Button button2;			}}
IThreadDAO.java
package com.linxiaosheng.test3;import java.util.List;/** * 线程接口 */public interface IThreadDAO {	/**	 * 插入线程信息	 * @param threadInfo	 */	public void insertThread(ThreadInfo threadInfo);	/**	 * 删除线程信息	 * @param url	 * @param thread_id	 */	public void removeThread(String url);			/**	 * 根据url查询线程	 * @param u	 */	public List<ThreadInfo> getThreadsByUrl(String u);	/**	 * 查询线程信息	 * @param url	 * @param thread_id	 * @return 	 */	public ThreadInfo getThreadById(String url,int thread_id);	/**	 * 更新线程完成进度	 * @param url	 * @param thread_id	 * @param finished	 */	public void updateThread(int thread_id,String url,int finished);	/**	 * 获得线程列表	 * @return	 */	public List<ThreadInfo> getThreads();		public boolean isExistThread(String url,int thread_id);}

ThreadImplDAO.java

package com.linxiaosheng.test3;

import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public class ThreadImplDAO implements IThreadDAO{

private DBHelper dbHelper=null;
public ThreadImplDAO(Context context){
dbHelper=DBHelper.getInstance(context);
}
@Override
public synchronized void insertThread(ThreadInfo threadInfo) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getWritableDatabase();
ContentValues values=new ContentValues();
//values.put("id", threadInfo.getId());
values.put("threadid", threadInfo.getThreadid());
values.put("url", threadInfo.getUrl());
values.put("start", threadInfo.getStart());
values.put("end", threadInfo.getEnd());
values.put("finished", threadInfo.getFinished());
db.insert("tb_thread", null, values);
db.close();
}

@Override
public synchronized void removeThread(String url) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getWritableDatabase();
db.delete("tb_thread", "url=?", new String[]{url});
db.close();
}

@Override
public ThreadInfo getThreadById(String url, int tb_thread) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getReadableDatabase();
Cursor cursor=db.query("tb_thread", new String[]{"id","tb_thread","url","start","end","finished"},"url=? and id=?",new String[]{url,tb_thread+""}, null,null,null);
ThreadInfo info=null;
while(cursor.moveToNext()){
int id=cursor.getInt(cursor.getColumnIndex("id"));
int start=cursor.getInt(cursor.getColumnIndex("start"));
int end=cursor.getInt(cursor.getColumnIndex("end"));
int finished=cursor.getInt(cursor.getColumnIndex("finished"));
info=new ThreadInfo(id,tb_thread,url,start,end,finished);
}
db.close();
return info;
}

@Override
public synchronized void updateThread(int thread_id,String url,int finished) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getWritableDatabase();
ContentValues values=new ContentValues();
values.put("finished", finished);
db.update("tb_thread", values, "threadid=? and url=?", new String[]{thread_id+"",url});
db.close();
}

@Override
public List<ThreadInfo> getThreads() {
// TODO Auto-generated method stub
List<ThreadInfo> list=new ArrayList<ThreadInfo>();
SQLiteDatabase db=dbHelper.getReadableDatabase();
Cursor cursor=db.query("tb_thread", new String[]{"id","url","start","end","finished"},null, null, null, null, null);
while(cursor.moveToNext()){
int id=cursor.getInt(cursor.getColumnIndex("id"));
String url=cursor.getString(cursor.getColumnIndex("url"));
int start=cursor.getInt(cursor.getColumnIndex("start"));
int end=cursor.getInt(cursor.getColumnIndex("end"));
int finished=cursor.getInt(cursor.getColumnIndex("finished"));
ThreadInfo info=new ThreadInfo(id,url,start,end,finished);
list.add(info);
}
return list;
}

@Override
public List<ThreadInfo> getThreadsByUrl(String u) {
// TODO Auto-generated method stub
List<ThreadInfo> list=new ArrayList<ThreadInfo>();
SQLiteDatabase db=dbHelper.getReadableDatabase();
Cursor cursor=db.query("tb_thread", new String[]{"id","threadid","url","start","end","finished"},"url=?",new String[]{u}, null, null, null);
while(cursor.moveToNext()){
int id=cursor.getInt(cursor.getColumnIndex("id"));
int threadid=cursor.getInt(cursor.getColumnIndex("threadid"));
String url=cursor.getString(cursor.getColumnIndex("url"));
int start=cursor.getInt(cursor.getColumnIndex("start"));
int end=cursor.getInt(cursor.getColumnIndex("end"));
int finished=cursor.getInt(cursor.getColumnIndex("finished"));

ThreadInfo info=new ThreadInfo(id,threadid,url,start,end,finished);
list.add(info);
}
return list;
}

@Override
public boolean isExistThread(String url, int thread_id) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getReadableDatabase();
Cursor cursor=db.query("tb_thread", new String[]{"id","url","start","end","finished"},"url=? and id=?",new String[]{url,thread_id+""}, null,null,null);
return cursor.moveToNext();
}

}
ThreadInfo.java
package com.linxiaosheng.test3;public class ThreadInfo {	private int id;	private int threadid;	private String url;	private int start;	private int end;	private int finished;	public ThreadInfo() {		super();	}	public ThreadInfo(int id,int threadid, String url, int start, int end, int finished) {		super();		this.id=id;		this.threadid=threadid;		this.url = url;		this.start = start;		this.end = end;		this.finished = finished;	}	public ThreadInfo(int threadid, String url, int start, int end, int finished) {		super();		this.threadid=threadid;		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 String getUrl() {		return url;	}	public void setUrl(String url) {		this.url = url;	}	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 int getThreadid() {		return threadid;	}	public void setThreadid(int threadid) {		this.threadid = threadid;	}	@Override	public String toString() {		return "ThreadInfo [id=" + id + ", threadid=" + threadid + ", url="				+ url + ", start=" + start + ", end=" + end + ", finished="				+ finished + "]";	}		}