多线程多任务断点续传

      多线程下载的原理是这样的:通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。如果你通过多个线程同时与服务器连接,那么你就可以榨取到较高的带宽了。
效果图:
                

FileDownload
任务类:
1:联网获取下载文件的信息,在SD卡上建立相应文件
2:给每个任务创建固定数目的线程,并给每个线程分配任务,然后开启
3:暂停、重启线程,根据每个线程的运行情况,通过Handler反馈信息到主线程(进度、是否结束)
源码:

public class FileDownloader {
private static final String TAG = "FileDownloader"; //方便调试语句的编写
private String urlStr; //文件下载位置
private int fileLength; //下载文件长度
private int downloadLength; //文件已下载长度
private File saveFile; //SD卡上的存储文件
private int block; //每个线程下载的大小
private int threadCount = 2; //该任务的线程数目
private DownloadThread[] loadThreads; 
private Handler handler;
private Boolean isFinished = false; //该任务是否完毕
private Boolean isPause = false;
private Context context;
RandomAccessFile accessFile;

protected synchronized void append(int size){ //多线程访问需加锁
this.downloadLength += size;
}
/*protected synchronized void update(int threadId , int thisThreadDownloadlength){ //写入数据库

}*/

public FileDownloader(String urlStr, Handler handler,Context context){
this.urlStr = urlStr;
this.handler = handler; 
this.loadThreads = new DownloadThread[threadCount];
this.context = context;
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(3000); //设置超时

if(conn.getResponseCode() == 200){
fileLength = conn.getContentLength(); //获取下载文件长度
String tmpFile = urlStr.substring(urlStr.lastIndexOf('/')+1); //获取文件名
print(tmpFile);
saveFile = new File(Environment.getExternalStorageDirectory(),tmpFile);
accessFile= new RandomAccessFile(saveFile,"rws");
accessFile.setLength(fileLength); //设置本地文件和下载文件长度相同
accessFile.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} 
}
public void Download(CompleteListener listener) throws Exception{

URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(3000); //设置超时

if(conn.getResponseCode() == 200){

//发送消息设置进度条最大长度
Message msg = new Message();
msg.what = 0;
msg.getData().putInt("filelength", fileLength);
handler.sendMessage(msg);

block = fileLength%threadCount == 0? fileLength/threadCount : fileLength/threadCount+1; //计算线程下载量
print(Integer.toString(block));
for(int i = 0 ; i < threadCount ; i++){
print("哈哈,你妹啊");
this.loadThreads = new DownloadThread(context,this,urlStr,saveFile,block,i); //新建线程开始下载
print("嘿嘿");
this.loadThreads.start();
print("线程:"+Integer.toString(i)+" 开始下载");
}

while(!isPause && !this.isFinished){
this.isFinished = true; 
Thread.sleep(900);
for(int i = 0 ; i < threadCount ; i++){
if(loadThreads != null && !loadThreads.isFinished()){
this.isFinished = false;
}
}

Message msg2 = new Message();
msg2.what = 1;
msg2.getData().putInt("currentlength", downloadLength); //
handler.sendMessage(msg2); //发送 消息更新进度条
//print(Integer.toString(downloadLength));
}
if(this.isFinished && listener != null){
listener.isComplete(downloadLength);
}
}
}

private void print(String msg){ //打印提示消息
Log.d(FileDownloader.TAG,msg);
}

public void setPause(){
isPause = true; //该任务暂停
for(int i = 0 ;i < threadCount ; i++){
if(loadThreads!= null && !loadThreads.isFinished()){
loadThreads.setPause(); //设置该线程暂停
print(Integer.toString(i)+"草泥马");
}
}
}

public void setResume(final CompleteListener listener) throws Exception{
isPause = false;
this.downloadLength = 0;
this.Download(new CompleteListener(){
public void isComplete(int size) {
listener.isComplete(size);
print("listener");
}
});
}
public Boolean isFinished(){
return this.isFinished;
}
}



DownloadThread
下载线程类:
1:需要参数:下载地址 、存储位置、起始结束下载位置、结束位置。  (时间、数据库等信息)
2:读取数据库判断是否有下载记录,然后根据情况计算下载的起始与结束位置,进行下载
3:每读取一定数据需要通过任务类更新任务显示,并写入数据库(多线程访问数据库需要加锁)
4:根据任务类的需要,暂停则让该线程运行结束,而非阻塞该线程(考虑到android内存的需要)
源码:

public class DownloadThread extends Thread{
private String urlStr; //下载地址
private int startPosition; //开始下载位置
private int downloadLength=0; //已下载字节数
private int endPosition; //结束位置
private File saveFile; //文件存储位置
private int threadId; //线程ID
private FileDownloader fileDownloader; //文件任务类
private Boolean isFinished = false; //文件快是否下载完毕
private DatabaseUtil databaseUtil;
private Boolean isPause = false; //默认为运行(没有暂停)
private Context context;

public DownloadThread(String urlStr){
this.urlStr = urlStr;
}
public DownloadThread(Context context,FileDownloader fileDownloader,String urlStr , File file , int block ,int threadId){
this.context = context;
this.fileDownloader = fileDownloader; //
this.urlStr = urlStr;
this.saveFile = file;
this.threadId = threadId;
this.startPosition = threadId * block; //计算起始下载位置
this.endPosition = (threadId+1) * block -1; //计算下载结束位置
}

public void run(){
try{
RandomAccessFile accessFile = new RandomAccessFile(saveFile,"rwd");

databaseUtil = new DatabaseUtil(context); //context设置需思考
ItemRecord record = databaseUtil.query(urlStr, threadId); //查询是否有记录存在
if(record != null){ 
downloadLength = record.getDownloadLength(); //读取已下载字节数 
fileDownloader.append(downloadLength); //更新总下载字节数
print("线程"+Integer.toString(threadId)+"存在记录"+Integer.toString(downloadLength));
}else{
synchronized(DatabaseUtil.lock){
databaseUtil.insert(urlStr, threadId, downloadLength); //插入未完成线程任务
print("线程"+Integer.toString(threadId)+"不存在记录");
}
}
accessFile.seek(startPosition+downloadLength); //设置写入起始位置
if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){
print(Integer.toString(endPosition)+"endPosition");
print(Integer.toString(startPosition)+"startPosition");
print(Integer.toString(downloadLength)+"downloadLength");
isFinished = true; //更新下载标志
print("线程:"+Integer.toString(threadId)+" 曾成功下载");
}else{
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(3000); //设置超时
conn.setRequestMethod("GET");
int tmpStartPosition = startPosition + downloadLength;
conn.setRequestProperty("Range", "bytes=" + tmpStartPosition + "-" + endPosition);

if(conn.getResponseCode()==206){
InputStream inStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
while(!isPause && (len = inStream.read(buffer))!= -1){
accessFile.write(buffer, 0, len); //写入文件
fileDownloader.append(len); //动态更新下载数、
downloadLength += len; //已下载数更新
//写入数据库
synchronized(DatabaseUtil.lock){
databaseUtil.update(urlStr, threadId, downloadLength);
}
}
if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){
print(Integer.toString(endPosition)+"endPosition");
print(Integer.toString(startPosition)+"startPosition");
print(Integer.toString(downloadLength)+"downloadLength");
isFinished = true; //更新下载标志
print("线程:"+Integer.toString(threadId)+" 下载完毕");
}else{
print(Integer.toString(endPosition)+"endPosition");
print(Integer.toString(startPosition)+"startPosition");
print(Integer.toString(downloadLength)+"downloadLength");
print("线程:"+Integer.toString(threadId)+" 未下载完!");
}
}else{
print(conn.getResponseMessage());
print(Integer.toString(conn.getResponseCode()));
print("擦,线程:"+Integer.toString(threadId)+" 没开始下载");
}
}
}catch(Exception e){
e.printStackTrace();
}
}

//返回下载结束的标志
public Boolean isFinished(){
return this.isFinished;
}
//设置暂停
public void setPause(){
this.isPause = true;

}
private void print(String msg){
Log.d("DownloadThrea",msg);
}
}


DatabaseHelper
建立数据库文件,并建表
源码:

public class DatabaseHelper extends SQLiteOpenHelper{
//private SQLiteDatabase database;
public DatabaseHelper(Context context , String name ,CursorFactory factory , int version) {
super(context, "downloadFile.db", null,1);
// TODO Auto-generated constructor stub
}

@Override
public void onCreate(SQLiteDatabase db) {
Cursor cursor = null;
try {
Log.d("哈哈哈","leixun");
cursor = db.rawQuery("SELECT * FROM sqlite_master WHERE type = ? AND name = ?", new String[]{"table","info"});
if(cursor.moveToNext()){
Log.d("DatabaseHelper","该表已经存在");
}else{
Log.d("DatabaseHelper","该表不存在 ,马上建立");
db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +
"downloadlength INTEGER , PRIMARY KEY(path,threadid))");
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
/*db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +
"downloadlength INTEGER , PRIMARY KEY(path,threadid))");*/
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub

}
}



DatabaseUtil
1:增删改查任务列表
2:通过静态常量lock实现数据库写入时的并发锁功能
源码:
public class DatabaseUtil {
private DatabaseHelper helper;
public static final String lock = "访问";
public DatabaseUtil(Context context){
helper = new DatabaseHelper(context, "downloadFile.db", null,1);
}
//添加记录
public void insert(String path , int threadid , int downloadlength){
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("INSERT INTO info(path,threadid,downloadlength) VALUES (?,?,?)", new Object[]{path,threadid,downloadlength});
print("成功添加");
db.close();
}
//删除记录
public void delete(String path , int threadid){
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("DELETE FROM info WHERE path = ? AND threadid = ?",new Object[]{path,threadid});
print("成功删除");
db.close();
}
//更新记录
public void update(String path , int threadid , int downloadlength){
SQLiteDatabase db = helper.getWritableDatabase();
//print("准备更新1");
db.execSQL("UPDATE info SET downloadlength = ? WHERE path = ? AND threadid = ?",new Object[]{downloadlength,path,threadid});
//print("成功更新2");
db.close();
}
//查询线程是否存在
public ItemRecord query(String path, int threadid){
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT path,threadid,downloadlength FROM info WHERE path = ? AND threadid = ?", new String[]{path,Integer.toString(threadid)});
ItemRecord record = null;
if(c.moveToNext()){
record = new ItemRecord(c.getString(0),c.getInt(1),c.getInt(2));
}
c.close();
db.close();
return record;
}
//查询未完成任务
public List<String> query(String path){
print("List<String> query 开始");
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", new String[]{path});
List<String> arrayList = new ArrayList<String>();
while(c.moveToNext()){
arrayList.add(c.getString(0));
}
c.close();
db.close();
print("List<String> query 结束");
return arrayList;
}
//调试信息输出
private void print(String msg){
Log.d("DatabaseUtil",msg);
}
}

ItemRecord
任务记录类,方便数据库操作而建立的任务记录类

public class ItemRecord {

private String path;
private int threadId;
private int downloadLength;

public ItemRecord(String path,int threadId,int downloadLength){
this.path = path;
this.threadId = threadId;
this.downloadLength = downloadLength;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public int getThreadId() {
return threadId;
}

public void setThreadId(int threadId) {
this.threadId = threadId;
}

public int getDownloadLength() {
return downloadLength;
}

public void setDownloadLength(int downloadLength) {
this.downloadLength = downloadLength;
}


}


ApplicationUrlSpinner操作用来完成应用名称与下载地址的对应关系源码:

public class ApplicationUrl {
private String applicationName = "";
private String applicationUrl = "";

public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public String getApplicationUrl() {
return applicationUrl;
}
public void setApplicationUrl(String applicationUrl) {
this.applicationUrl = applicationUrl;
}

public String toString() {
// TODO Auto-generated method stub
return applicationName;
}
}


CompleteListener下载完成判断接口:

public interface CompleteListener {
   public void isComplete(int size);
}



MainActivity
1:动态添加下载任务
2:根据任务动态添加布局,并给相应按钮添加监听,开启下载任务
源码:

public class MaintActivity extends Activity{

private LayoutInflater inflater;
private LinearLayout root;
private Spinner spinnerApp;
private List<ApplicationUrl> appUrl;
private ArrayAdapter appAdapter;
private Button startButton;
FileDownloader fileDownloader;
private String url5 = "http://update.android.doplive.com.cn/dopoolv2.4.player.apk";
private String url5App = "Dopool手机电视";
private String url = "http://www.ipmsg.org.cn/downloads/ipmsg.apk";
private String urlApp = "飞鸽传书";
private String url2 = "http://down1.cnmo.com/app/a135/aiku_2.0.apk";
private String url2App = "爱酷天气";
private ApplicationUrl userChoose;

@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);

inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);
root = (LinearLayout)findViewById(R.id.root);

spinnerApp = (Spinner)findViewById(R.id.urlSpinner);
startButton = (Button)findViewById(R.id.startBtn);

//createDownloadTask(url); //创建任务 添加View
//createDownloadTask(url2);

init();
}

public void init(){
//-----------------给Spinner制作数据----------------
appUrl = new ArrayList<ApplicationUrl>(); 
ApplicationUrl[] tmp = new ApplicationUrl[3];
for(int i = 0 ; i<3;i++){
tmp = new ApplicationUrl();
}
tmp[0].setApplicationName(urlApp);
tmp[0].setApplicationUrl(url);
tmp[1].setApplicationName(url2App);
tmp[1].setApplicationUrl(url2);
tmp[2].setApplicationName(url5App);
tmp[2].setApplicationUrl(url5);
appUrl.add(tmp[0]);
appUrl.add(tmp[1]);
appUrl.add(tmp[2]);

userChoose = new ApplicationUrl();
userChoose.setApplicationName(tmp[0].getApplicationName());
userChoose.setApplicationUrl(tmp[0].getApplicationUrl());
appAdapter = new ArrayAdapter(MaintActivity.this,android.R.layout.simple_spinner_item,appUrl);
appAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerApp.setAdapter(appAdapter);
spinnerApp.setSelection(0, true);
spinnerApp.setOnItemSelectedListener(new OnItemSelectedListener(){

@Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int position, long arg3) {
print("onItemSelected:"+appUrl.get(position).getApplicationName()+Integer.toString(position));
userChoose.setApplicationName(appUrl.get(position).getApplicationName());
userChoose.setApplicationUrl(appUrl.get(position).getApplicationUrl());
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub

}
});
startButton.setOnClickListener(new View.OnClickListener() { 
@Override
public void onClick(View v) {
if(!appUrl.isEmpty()){
createDownloadTask(userChoose);
}else{
Toast.makeText(MaintActivity.this, "没有应用程序可下载!", Toast.LENGTH_LONG).show();
}
int count = appUrl.size();
print(Integer.toString(count));
for(int i = 0 ;i< count;i++){
if(appUrl.get(i).getApplicationName().equals(userChoose.getApplicationName())){
print("+++++++++++++++++"+Integer.toString(i)+"++++++++++++++++");
printappUrl();
appUrl.remove(i);
printappUrl();
count = appUrl.size();
print("for循环找到"+Integer.toString(count));
appAdapter.notifyDataSetChanged();
if(!appUrl.isEmpty()){
spinnerApp.setSelection(0,true);
userChoose.setApplicationName(appUrl.get(0).getApplicationName()); //擦 必须这样赋值
userChoose.setApplicationUrl(appUrl.get(0).getApplicationUrl());

}
printappUrl();
print(userChoose.getApplicationName()+"数据源已更新"+Integer.toString(count));
}else{
print("数据源未更新");
}
}
}
});

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_layout, menu);
return true;
}


private void createDownloadTask(ApplicationUrl path){
LayoutInflater inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE); //获取系统服务layoutinflater

LinearLayout linearLayout = (LinearLayout)inflater.inflate(R.layout.download_layout, null);

LinearLayout childLayout = (LinearLayout)linearLayout.getChildAt(0);
ProgressBar progressBar = (ProgressBar)childLayout.getChildAt(0); //获取进度条
TextView textView = (TextView)childLayout.getChildAt(1); //获取textview

Button button = (Button)linearLayout.getChildAt(1); //获取下载按钮
try{
button.setOnClickListener(new MyListener(progressBar,textView,path));
root.addView(linearLayout); //将创建的View添加到主线程 的布局页面中
}catch(Exception e){
e.printStackTrace();
}
}

private final class MyListener implements OnClickListener{
private ProgressBar pb ;
private TextView tv;
private String url;
private FileDownloader fileDownloader;
private String name ; 

public MyListener(ProgressBar pb ,TextView tv, ApplicationUrl url){
this.pb = pb;
this.tv = tv;
this.url = url.getApplicationUrl();
this.name = url.getApplicationName();
}

public void onClick(View v) {
final Button pauseButton = (Button)v;
final Handler mainHandler = new Handler(){

@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 2:
pauseButton.setText("安装");
break;
}
}

};
if(pauseButton.getText().equals("开始")){
pauseButton.setText("暂停");
new Thread(){
public void run(){
try{
fileDownloader = new FileDownloader(url,handler,MaintActivity.this);
fileDownloader.Download(new CompleteListener(){
public void isComplete(int size) {
Message msg = new Message();
msg.what = 2;
mainHandler.sendMessage(msg);
}
});
}catch(Exception e){
e.printStackTrace();
}
}
}.start();
}else if(pauseButton.getText().equals("暂停")){
print("暂停");
fileDownloader.setPause();
pauseButton.setText("继续");
}else if(pauseButton.getText().equals("继续")){
print("继续");
pauseButton.setText("暂停");
new Thread(){
public void run(){
try{
fileDownloader.setResume(new CompleteListener(){
public void isComplete(int size) {
print("妹纸");
Message msg = new Message();
msg.what = 2;
mainHandler.sendMessage(msg);
}
});
}catch(Exception e){
e.printStackTrace();
}
}
}.start();
}else if(pauseButton.getText().equals("安装")){
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String installPath = Environment.getExternalStorageDirectory() + "/"+url.substring(url.lastIndexOf('/')+1);
print(installPath);
intent.setDataAndType(Uri.fromFile(new File(installPath)), "application/vnd.android.package-archive");
MaintActivity.this.startActivity(intent);
}
}

private Handler handler = new Handler(){
int fileLength = 1;
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0: //得到进度条的最大长度
fileLength = msg.getData().getInt("filelength");
pb.setMax(fileLength); //设置进度条最大长度
break;
case 1: //设置进度条现在的长度
int currentLength = msg.getData().getInt("currentlength");
pb.setProgress(currentLength); //设置当前进度条长度
tv.setText(name+" 已下载:"+currentLength*100 / fileLength+"%");
if(currentLength == fileLength){
tv.setText(name+" 下载完成:"+currentLength*100 / fileLength+"%");
}
break;
default: print("handleMessage msg.what 没有曲子好");
}
}

};

}

/**
* 调试信息输出
* @param msg
*/
private void print(String msg){
Log.d("MainActivity",msg);
}

private void printappUrl(){
Log.d("=========================","=========================");
for(int i = 0;appUrl!=null && i<appUrl.size();i++){
print(appUrl.get(i).getApplicationName());
}
Log.d("=========================","=========================");
}
}

源码下载


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值