Android开发多线程断点续传下载器

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">转载自:http://blog.csdn.net/furongkang/article/details/6838521</span>



原文的代码有些问题,在其基础上做了小小修改~~

使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线、电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载。这里采用数据库来记录下载的进度。

效果图

       

 

断点续传

1.断点续传需要在下载过程中记录每条线程的下载进度

2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库

3.在每次向文件中写入数据之后,在数据库中更新下载进度

4.下载完成之后删除数据库中下载记录

Handler传输数据

这个主要用来记录百分比,每下载一部分数据就通知主线程来记录时间

1.主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据

2.我们使用Handler可以处理这种需求

   主线程中创建Handler,重写handleMessage()方法

   新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法

动态生成新View

可实现多任务下载

1.创建XML文件,将要生成的View配置好

2.获取系统服务LayoutInflater,用来生成新的View

   LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

3.使用inflate(int resource, ViewGroup root)方法生成新的View

4.调用当前页面中某个容器的addView,将新创建的View添加进来

示例

进度条样式 download.xml

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout   
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="wrap_content"  
  6.     >  
  7.     <LinearLayout   
  8.         android:orientation="vertical"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_weight="1"  
  12.         >  
  13.         <!--进度条样式默认为圆形进度条,水平进度条需要配置style属性,  
  14.         ?android:attr/progressBarStyleHorizontal -->  
  15.         <ProgressBar  
  16.             android:layout_width="fill_parent"   
  17.             android:layout_height="20dp"  
  18.             style="?android:attr/progressBarStyleHorizontal"  
  19.             />  
  20.         <TextView  
  21.             android:layout_width="wrap_content"   
  22.             android:layout_height="wrap_content"  
  23.             android:layout_gravity="center"  
  24.             android:text="0%"  
  25.             />  
  26.     </LinearLayout>  
  27.     <Button  
  28.         android:layout_width="40dp"  
  29.         android:layout_height="40dp"  
  30.         android:onClick="pause"  
  31.         android:text="||"  
  32.         />  
  33. </LinearLayout>  

顶部样式 main.xml

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"  
  6.     android:id="@+id/root"  
  7.     >  
  8.     <TextView    
  9.         android:layout_width="fill_parent"   
  10.         android:layout_height="wrap_content"   
  11.         android:text="请输入下载路径"  
  12.         />  
  13.     <LinearLayout   
  14.         android:layout_width="fill_parent"  
  15.         android:layout_height="wrap_content"  
  16.         android:layout_marginBottom="30dp"  
  17.         >  
  18.         <EditText  
  19.             android:id="@+id/path"  
  20.             android:layout_width="fill_parent"   
  21.             android:layout_height="wrap_content"   
  22.             android:singleLine="true"  
  23.             android:layout_weight="1"  
  24.             />  
  25.         <Button  
  26.             android:layout_width="wrap_content"   
  27.             android:layout_height="wrap_content"   
  28.             android:text="下载"  
  29.             android:onClick="download"  
  30.             />  
  31.     </LinearLayout>  
  32. </LinearLayout>  
  33.    

MainActivity.Java

<span style="font-size:10px;">package com.lv.learnmultidownload;

import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.lv.learnmultidownload.db.InfoDao;
import com.lv.learnmultidownload.download.Downloader;

public class MainActivity extends Activity {

	private LayoutInflater inflater;
	private LinearLayout rootLinearLayout;
	private EditText pathEditText;
	
	private static int THREAD_COUNT = 3;
	
	private int done1 = 0;
	private int done2 = 0;
	private int done3 = 0;
	
	private String TAG = "MainActivity";
	
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //动态生成新view, 获取系统服务LayoutInflater,用来生成新的view
        inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        rootLinearLayout = (LinearLayout) findViewById(R.id.layout_root);
        
        pathEditText = (EditText) findViewById(R.id.edt_path);
        
        //窗体创建之后,查询数据库是否有未完成任务,如果有,创建进度条等组件,继续下载
        List<String> list = new InfoDao(this).queryUpdone();
        
        for (String path : list) {
			createDownload(path);
		}
    }
    
    public void downloadClick(View view){
    	String path = "http://192.168.1.6:8080/ECServer_D/xia.apk";
    	if(!((pathEditText.getText().toString()).isEmpty())){
    		path = "http://192.168.1.6:8080/ECServer_D/" + pathEditText.getText().toString();
    	}
    	createDownload(path);
    }
    
    
    private void createDownload(String path){
    	//获取系统服务LayoutInflater,用来生成新的View
    	LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    	LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.download, null);
    	
    	LinearLayout childLinearLayout = (LinearLayout) linearLayout.getChildAt(0);
    	ProgressBar progressBar = (ProgressBar) childLinearLayout.getChildAt(0);
    	TextView textView = (TextView) childLinearLayout.getChildAt(1);
    	Button button = (Button) linearLayout.getChildAt(1);
    	
    	button.setOnClickListener(new MyListener(progressBar, textView, path));
    	rootLinearLayout.addView(linearLayout);
    }
    
    private class MyListener implements OnClickListener{

    	private ProgressBar progressBar;
    	private TextView textView;
    	private int fileLen;
    	private Downloader downloader;
    	private String name;
    	
    	public MyListener(ProgressBar progressBar, TextView textView, final String path){
    		this.progressBar = progressBar;
    		this.textView = textView;
    		name = path.substring(path.lastIndexOf("/") + 1);
    		
    		downloader = new Downloader(getApplicationContext(), handler);
    		
    		new Thread(new Runnable() {
				
				@Override
				public void run() {
					
					try {
						downloader.download(path, THREAD_COUNT);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}).start();
    	}
    	
    	
    	private Handler handler = new Handler(){
    		public void handleMessage(android.os.Message msg) {
    			
    			if(msg.what == 0){
    				//获取文件的大小
    				fileLen = msg.getData().getInt("fileLen");
    				//设置进度条最大刻度:setMax()
    				progressBar.setMax(fileLen);
    			}else{
    				//获取当前各线程下载量
    				if(msg.what == 1){
    					done1 = msg.getData().getInt("done1");
    				}
    				if(msg.what == 2){
    					done2 = msg.getData().getInt("done2");
    				}
    				if(msg.what == 3){
    					done3 = msg.getData().getInt("done3");
    				}
    				//获取当前总下载量
    				int done = done1 + done2 +done3;
    				Log.d(TAG, "fileLen:"+ fileLen +"  done:"+ done + "  done1:"+done1 + "  done2:"+done2+ "  done3:"+done3);
    				//当前进度的百分比
    				textView.setText(name+"\t"+done*100/fileLen+"%");
    				//设置进度条当前进度
    				progressBar.setProgress(done);
    				if(done == fileLen){
    					Toast.makeText(getApplicationContext(), name+"下载完成", 0).show();
    					//下载完成后退出进度条
    					rootLinearLayout.removeView((View)progressBar.getParent().getParent());
    				}
    			}
    			
    		};
    	};
    	
		@Override
		public void onClick(View v) {
			Button pauseBtn = (Button) v;
			if("||".equals(pauseBtn.getText())){
				downloader.pause();
				pauseBtn.setText("▶");
			}else{
				downloader.resume();
				pauseBtn.setText("||");
			}
		}
    }
}</span><span style="font-size: 14px;">
</span>


Downloader.java

<span style="font-size:10px;">package com.lv.learnmultidownload.download;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.lv.learnmultidownload.db.InfoDao;

public class Downloader {

	private int done;
	private InfoDao dao;
	private int fileLen;
	private Handler handler;
	private static boolean isPause;
	private Context context;
	
	private String TAG = "Downloader";
	
	public Downloader(Context context, Handler handler){
		
		Log.d(TAG, "构造方法");
		
		this.context = context;
		this.handler = handler;
		
		dao = new InfoDao(context);
	}
	
	/**
	 * 多线程下载
	 * path 下载路径
	 * thCount 需要开启多少线程
	 * throws Exception
	 * 
	 * 	1.r(Read,读取):对文件而言,具有读取文件内容的权限;对目录来说,具有浏览目 录的权限。 

		2.w(Write,写入):对文件而言,具有新增、修改文件内容的权限;对目录来说,具有删除、移动目录内文件的权限。
		
		3.x(eXecute,执行):对文件而言,具有执行文件的权限;对目录了来说该用户具有进入目录的权限。
		
		4.s或S(SUID,Set UID):可执行的文件搭配这个权限,便能得到特权,任意存取该文件的所有者能使用的全部系统资源。请注意具备SUID权限的文件,黑客经常利用这种权限,以SUID配上root帐号拥有者,无声无息地在系统中开扇后门,供日后进出使用。
		
		5.t或T(Sticky):/tmp和 /var/tmp目录供所有用户暂时存取文件,亦即每位用户皆拥有完整的权限进入该目录,去浏览、删除和移动文件。
	 * 
	 */
	public void download(String path, int thCount) throws Exception{
		Log.d(TAG, "download(String path, int thCount)");
		URL url = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		conn.setConnectTimeout(5000);
		
		if(conn.getResponseCode() == 200){
			fileLen = conn.getContentLength();
			Log.d(TAG, "download() fileLen:"+fileLen);
			String name = path.substring(path.lastIndexOf("/") + 1);
			File file = new File(Environment.getExternalStorageDirectory(), name);

			RandomAccessFile raf = new RandomAccessFile(file, "rws");
			raf.setLength(fileLen);
			raf.close();
			
			Message msg = Message.obtain();
			msg.what = 0;
			msg.getData().putInt("fileLen", fileLen);
			handler.sendMessage(msg);
			
			int partLen = (fileLen + thCount - 1)/thCount;
			Log.d(TAG, "download() partLen:"+partLen);
			
			for (int i = 0; i < thCount; i++) {
				/*
				 * 参数 :
				 * dao 要传入 若new 的话,则notify()不起作用
				 */
				new DownloadThread(context, handler, url, file, partLen, i, fileLen, dao).start();
			}
		}else{
			Log.d(TAG, "download() if(conn.getResponseCode() else");
			throw new IllegalArgumentException("conn.getResponseCode():"+conn.getResponseCode()+"   path:"+path);
		}
	}
	
	public static boolean getIsPause(){
		return isPause;
	}
	
	public void pause(){
		isPause = true;
	}
	
	public void resume(){
		isPause = false;
		
		//恢复所有线程
		synchronized (dao) {
			dao.notifyAll();
			Log.d(TAG, "dao.notifyAll()");
		}
	}
	
}</span><span style="font-size: 14px;">
</span>

DownloadThread.java

<span style="font-size:10px;">package com.lv.learnmultidownload.download;

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 android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.lv.learnmultidownload.bean.Info;
import com.lv.learnmultidownload.db.InfoDao;

public class DownloadThread extends Thread{

	private URL url;
	private File file;
	private int partLen;
	private int id;
	private InfoDao dao;
	private int done;
	private Handler handler;
	private int fileLen;
	
	private String TAG = "DownloadThread";
	
	public DownloadThread(Context context, Handler handler, URL url, File file, 
			int partLen, int id, int fileLen, InfoDao dao){
		
		Log.d(TAG, "构造方法");
		
		this.handler = handler;
		this.url = url;
		this.file = file;
		this.partLen = partLen;
		this.id = id;
		this.fileLen = fileLen;
		
		this.dao = dao;
	}
	
	@Override
	public void run() {
		
		Log.d(TAG, "run()" + "   thread:" + (id + 1));
		
		Info info = dao.query(url.toString(), id);
		
		if (info != null) {
			//如果有,读取当前线程已下载量
			done += info.getDone();
		}else{
			info = new Info(url.toString(), id, 0);
			dao.insert(info);
		}
		
		//开始位置 += 已下载
		int start = id * partLen + info.getDone(); 
		
		int end = (id + 1) * partLen - 1;
		
		Log.d(TAG, "start:"+start+"  end:"+end + "   thread:" + (id + 1));
		
		try {
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setReadTimeout(3000);
			conn.setRequestProperty("Range", "bytes="+start+"-"+end);
			RandomAccessFile raf = new RandomAccessFile(file, "rws");
			
			raf.seek(start);
			//开始读写数据
			InputStream inputStream = conn.getInputStream();
			
			byte[] buf = new byte[1024 * 10];
			
			int len;
			
			while((len = inputStream.read(buf)) != -1){
				
				Log.d(TAG, "while()" + "   thread:" + (id + 1));
				
				if(Downloader.getIsPause()){
					
					Log.d(TAG, "Downloader.getIsPause():"+Downloader.getIsPause());
					
					//使用线程锁锁定该线程
					synchronized (dao){
						try {
							Log.d(TAG, "dao.wait()  1");
							dao.wait();
							Log.d(TAG, "dao.wait()  2");
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
				
				raf.write(buf, 0, len);
				done += len;
				
				if(id == 0){
					Log.d(TAG, "done:"+done + "   thread:" + (id + 1));
				}else if(id == 1){
					Log.d(TAG, "done:"+ (done + partLen - 1) + "   thread:" + (id + 1));
				}else if(id == 2){
					Log.d(TAG, "done:"+(done + partLen * 2 - 1) + "   thread:" + (id + 1));
				}
				
				
				info.setDone(info.getDone() + len);
					
				//记录每个线程已下载的数据量
				dao.update(info);
				
				//新线程中用handler发送消息,主线程接收
				Message msg = Message.obtain();
				msg.what = id + 1;
				msg.getData().putInt("done" + (id + 1), done);
				handler.sendMessage(msg);
				
				
				
			}
			
			inputStream.close();
			raf.close();
			
			//删除下载记录
			dao.deleteAll(info.getPath(), fileLen);
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}</span><span style="font-size: 18px;">
</span>


Dao:

 

DBOpenHelper:

<span style="font-size:10px;">package com.lv.learnmultidownload.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBOpenHelper extends SQLiteOpenHelper{

	private static DBOpenHelper dbInstance;
	private String TAG = "DBOpenHelper";
	
	public synchronized static DBOpenHelper getInstanse(Context context){
		
		if(dbInstance == null){
			dbInstance = new DBOpenHelper(context);
		}
		
		return dbInstance;
	}
	
	public DBOpenHelper(Context context) {
		super(context, "download.db", null, 1);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("create table info(path varchar(1024), thid integer, " +
				" done integer, primary key(path, thid))");		
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// TODO Auto-generated method stub
	}
}</span><span style="font-size: 14px;">
</span>


InfoDao:

<span style="font-size:10px;">package com.lv.learnmultidownload.db;

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

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

import com.lv.learnmultidownload.bean.Info;

public class InfoDao {

	private DBOpenHelper helper;
	private SQLiteDatabase db;
	private String TAG = "InfoDao";
	
	public InfoDao(Context context){
		
		Log.d(TAG, "构造方法");
		
		db = DBOpenHelper.getInstanse(context).getWritableDatabase();
	}
	
	/*
	 * 一个info就是一条线程下载记录
	 */
	
	//插入一条info
	public void insert(Info info){
		
		Log.d(TAG, "insert()");
		
		String sql = "insert into info(path, thid, done) " +
				"values(?, ?, ?)";
		db.execSQL(sql, new Object[]{info.getPath(), info.getThid(), 
				info.getDone()});
	}
	
	//通过path和线程id 查找info
	public Info query(String path, int thid){
		
		String sql = "select path, thid, done from info where " +
				"path = ? and thid = ?";
		
		Cursor cursor = db.rawQuery(sql, new String[]{path, String.valueOf(thid)});
		
		Info info = null;
		
		if(cursor.moveToNext()){
			info = new Info(cursor.getString(0), cursor.getInt(1), cursor.getInt(2));
		}
		
		cursor.close();
		
		return info;
	}
	
	//更新下载信息
	public void update(Info info){
		String sql = "update info set done = ? where path = ? and " +
				"thid = ?";
		db.execSQL(sql, new Object[]{info.getDone(), info.getPath(), info.getThid()});
	}
	
	public void deleteAll(String path, int fileLen){
		
		Log.d(TAG, "deleteAll()");
		
		String sql = "select sum(done) from info where path = ?";
		Cursor cursor = db.rawQuery(sql, new String[]{path});
		if(cursor.moveToNext()){
			int result = cursor.getInt(0);
			if(result == fileLen){
				String sqlDel = "delete from info where path = ?";
				db.execSQL(sqlDel, new String[]{path});
			}
		}
	}
	
	//获得整个列表list
	public List<String> queryUpdone(){
		
		//返回无重复值的列
		String sql = "select distinct path from info";
		
		Cursor cursor = db.rawQuery(sql, null);
		
		List<String> pathList = new ArrayList<String>();
		
		while(cursor.moveToNext()){
			pathList.add(cursor.getString(0));
		}
		
		cursor.close();
		
		return pathList;
	}
}</span><strong style="font-size: 14px;">
</strong>


 Info

<span style="font-size:10px;">package com.lv.learnmultidownload.bean;

public class Info {

	private String path;
	private int thid;
	private int done;
	
	public Info(String path, int thid, int done) {
		super();
		this.path = path;
		this.thid = thid;
		this.done = done;
	}

	public String getPath() {
		return path;
	}

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

	public int getThid() {
		return thid;
	}

	public void setThid(int thid) {
		this.thid = thid;
	}

	public int getDone() {
		return done;
	}

	public void setDone(int done) {
		this.done = done;
	}
}</span><span style="font-size: 14px;">
</span>



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值