许久没有更新了,上次写的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;ThreadInfo.java
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();
}
}
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 + "]"; } }