Android多线程断点下载完整示例详解

MainActivity如下:

package cc.activity;

import java.io.File;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import cc.download.DownloadProgressListener;
import cc.download.FileDownloader;
import cn.com.downloadActivity.R;

/**
 * Demo描述:
 * 多线程断点下载文件的实现
 *
 * 思路梳理:
 * 1 将待下载文件切分成几部分,每部分开启一条线程进行下载
 * 2 将每条线程下载的部分利用RandomAccessFile写入本地文件
 * 3 在下载过程中不断将该线程已经下载的数据位置保存至数据库
 * 4 若下载过程中突然断电,下次下载时则从数据库中取出每条线程
 *   的断点值,继续下载即可
 *
 * 注意事项:
 * 1 示例中的图片链接选自凤凰网,图片版权属于凤凰网(http://www.ifeng.com/)
 * 2 该图片以后可能会被凤凰网删除,所以在测试时可选择其他网络图片
 */
public class MainActivity extends Activity {

    private Context mContext;
    private EditText  mUrlEditText;
    private ProgressBar mProgressBar;
    private Button mDownLoadButton;
    private TextView mPercentTextView;
    private UIHandler mHandler=new UIHandler();
    private final int NORMAL=9527;
    private final int ERROR=250;
    private final String MESSAGE_KEY="size";
    private DownloadProgressListener mDownloadProgressListener;
    @Override
   public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        init();
    }

	private void init() {
		mContext = this;
		mUrlEditText = (EditText) findViewById(R.id.urlEditText);
		mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
		mPercentTextView = (TextView) findViewById(R.id.percentTextView);
		mDownLoadButton = (Button) findViewById(R.id.downloadButton);
		mDownLoadButton.setOnClickListener(new ClickListenerImpl());
		mDownloadProgressListener=new DownloadProgressListenerImpl();
	}

   private class ClickListenerImpl implements OnClickListener{
	@Override
	 public void onClick(View v) {
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
			String path=mUrlEditText.getText().toString();
			download(path,Environment.getExternalStorageDirectory());
		}else{
			Toast.makeText(mContext, R.string.SDCardError, Toast.LENGTH_SHORT).show();
		}
	 }
   }

   private void download(String path,File saveDir){
	   new Thread(new DownloadRunnableImpl(path, saveDir)).start();
   }

   //下载文件的子线程
   private class DownloadRunnableImpl implements Runnable{
      private String path;
      private File SaveDir;

	  public DownloadRunnableImpl(String path, File saveDir) {
		this.path = path;
		this.SaveDir = saveDir;
	  }

	  public void run(){
	    try {
			FileDownloader fileDownloader=new FileDownloader(getApplicationContext(), path, 4, SaveDir);
			mProgressBar.setMax(fileDownloader.getFileRawSize());
			//置显示进度的回调
			fileDownloader.setDownloadProgressListener(mDownloadProgressListener);
			//开始下载
			fileDownloader.startDownload();

		} catch (Exception e) {
			Message message=new Message();
			message.what=ERROR;
			mHandler.sendMessage(message);
			e.printStackTrace();
		}

	 }

   }

	private class DownloadProgressListenerImpl implements DownloadProgressListener {
		@Override
		public void onDownloadSize(int size) {
			Message message = new Message();
			message.what = NORMAL;
			message.getData().putInt(MESSAGE_KEY, size);
			mHandler.sendMessage(message);
		}
	}

	// 显示下载进度
	private class UIHandler extends Handler {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case NORMAL:
				int size = msg.getData().getInt(MESSAGE_KEY);
				mProgressBar.setProgress(size);
				float percentFloat = (float) (mProgressBar.getProgress() / (float) mProgressBar.getMax());
				int percentInt = (int) (percentFloat * 100);
				mPercentTextView.setText(percentInt + "%");
				System.out.println("size="+size+",mProgressBar.getProgress()="+mProgressBar.getProgress()+",mProgressBar.getMax()="+mProgressBar.getMax());
				if (mProgressBar.getProgress() == mProgressBar.getMax()) {
					Toast.makeText(mContext, R.string.success,Toast.LENGTH_SHORT).show();
				}
				break;

			case ERROR:
				Toast.makeText(mContext, R.string.error,Toast.LENGTH_SHORT).show();
				break;

			default:

				break;
			}

		}

	}

}

DownloadProgressListener如下:

package cc.download;
/**
 * 下载的监听接口
 */
public interface DownloadProgressListener {
    public void onDownloadSize(int size);
}

 

FileDownloader如下:

package cc.download;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.HttpStatus;
import android.content.Context;
import cc.helper.DownloadThreadHelper;
/**
 * 该类用于下载文件
 * 思路梳理:
 * 1 在FileDownloader的构造方法里做一些下载的准备操作
 *   1.1 获取源文件的大小
 *   1.2 计算每条线程需要下载的数据长度
 *   1.3 生成本地文件用于存储下载的文件并设置本地RandomAccessFile的大小
 *   1.4 取出每条线程的上次下载的情况和已下载的总长度
 *
 * 2 利用startDownload()方法执行文件下载
 *
 * 分析说明:
 * 关于1.2 计算每条线程需要下载的数据长度 的原理及影响的详细分析:
 * everyThreadNeedDownloadLength=rawFileSize%threadNum==0 ? rawFileSize/threadNum : rawFileSize/threadNum+1;
 * 1 如果资源大小模于线程数时结果为0,那么表示每条线程需要下载的大小恰好将原大小等分
 * 2 当然更多的情况是有余数的(即不能整除).那么此时该怎么办呢?每条线程该下载的长度是多少呢?
 *   我们可以这么做:
 *   2.1 原大小/除以线程的条数
 *   2.2 在2.1的基础上+1
 *   即代码rawFileSize/threadNum+1
 *   这样就表示每条线程要下载的大小长度
 *
 *   带来的问题:
 *   按照上面的方式,我们期望每条线程下载相同的数据量.但是存在个小问题:
 *   这样各线程累加起来的的下载总量是要大于原大小的.一般会多几个字节.
 *   这几个字节是多余的(redundant).
 *   而且这些几个多余的字节是出现在最后一条下载线程中,它的终止位置已经
 *   超过了原文件的末尾.
 *   这样就造成了下载的文件与原文件大小不一致.
 *   所以在下载过程中需要处理该情况,处理方式参见DownloadThread类
 *
 */
public class FileDownloader {
	private Context mContext;
	//下载路径
    private String mDownloadPath;
    //待下载文件的原始长度
    private int rawFileSize=0;
    //保存下载文件的本地文件
    private File mLocalFile;
    //已下载大小
    private int downloadTotalSize=0;
    //下载此文件需要的各个线程
    private DownloadThread [] downloadThreadsArray;
    //每条线程需下载的长度
    private int everyThreadNeedDownloadLength;
    //存放目前每条线程的信息包含其id和已下载大小
    private Map<Integer,Integer> mCurrentEveryThreadInfoMap;
    //用于对各个线程进行操作
    private DownloadThreadHelper mDownloadThreadHelper;

    private DownloadProgressListener mDownloadProgressListener;

    public FileDownloader(Context context,String downloadPath,int threadNum,File fileSaveDir){
    	System.out.println("源文件路径 downloadPath= "+downloadPath);
    	System.out.println("下载开启的线程数 threadNum="+threadNum);
    	try {
			mContext=context;
			mDownloadPath=downloadPath;
			mCurrentEveryThreadInfoMap=new HashMap<Integer,Integer>();
			mDownloadThreadHelper=new DownloadThreadHelper(context);
			downloadThreadsArray=new DownloadThread[threadNum];

			URL downloadUrl=new URL(downloadPath);
			HttpURLConnection httpURLConnection=(HttpURLConnection) downloadUrl.openConnection();
			httpURLConnection.setReadTimeout(5*1000);
			httpURLConnection.setRequestMethod("GET");
			httpURLConnection.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
			httpURLConnection.setRequestProperty("Accept-Language", "zh-CN");
			httpURLConnection.setRequestProperty("Referer", downloadPath);
			httpURLConnection.setRequestProperty("Charset", "UTF-8");
			httpURLConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
			httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
			httpURLConnection.connect();
			if(httpURLConnection.getResponseCode()==HttpStatus.SC_OK){
				//第一步:获得源文件大小
				rawFileSize=httpURLConnection.getContentLength();
				System.out.println("源文件大小 rawFileSize="+rawFileSize);

				//第二步:计算每条线程需要下载的数据长度
				everyThreadNeedDownloadLength=rawFileSize%threadNum==0 ? rawFileSize/threadNum : rawFileSize/threadNum+1;
				System.out.println("每条线程应下载大小 everyThreadNeedDownloadLength="+everyThreadNeedDownloadLength);
				if(rawFileSize<=0){
					throw new RuntimeException("file is not found");
				}

				//第三步:建立本地文件并设置本地RandomAccessFile的大小
				String rawFileName=getFileName(httpURLConnection);
				if(!fileSaveDir.exists()){
					fileSaveDir.mkdirs();
				}
				mLocalFile=new File(fileSaveDir, rawFileName);
				System.out.println("本地文件路径 mLocalFile.getAbsolutePath()="+mLocalFile.getAbsolutePath());

				RandomAccessFile randomAccessFile=new RandomAccessFile(mLocalFile, "rw");
				if(rawFileSize>0){
					randomAccessFile.setLength(rawFileSize);
				}
				randomAccessFile.close();

				//第四步:取出每条线程的上次下载的情况和已下载的总长度
				/**
				 * 以下操作围绕断点进行的:
				 * 1 从数据库取出每条线程上一次的下载情况,存入everyThreadLastDownloadLengthMap
				 * 2 判断上次下载时开启的线程数是否和本次下载开启线程数一致
				 *   若不一致则无法在原基础上继续断点下载,则将mCurrentEveryThreadInfoMap中各条线程下载量设置为0
				 *   若一致则取出已下载的数据总量
				 */

				//若以前下载过,则取出每条线程以前的情况存入mCurrentEveryThreadInfoMap
				Map<Integer,Integer> everyThreadLastDownloadLengthMap=mDownloadThreadHelper.getEveryThreadDownloadLength(downloadPath);
				if(everyThreadLastDownloadLengthMap.size()>0){
					for(Map.Entry<Integer, Integer> entry:everyThreadLastDownloadLengthMap.entrySet()){
						mCurrentEveryThreadInfoMap.put(entry.getKey(), entry.getValue());
						System.out.println("--> 断点回复处 --> threadID="+entry.getKey()+",已下载数据量 length="+entry.getValue());
					}
				}

				//若以往的线程条数和现在的线程条数不一致,则无法按照
				//断点继续下载.所以将每条已下载的数据量置为0,并更新数据库
				//若以往的线程条数和现在的线程条数一致,则取出已下载的数据总量
				if(downloadThreadsArray.length!=mCurrentEveryThreadInfoMap.size()){
					mCurrentEveryThreadInfoMap.clear();
					for(int i=1;i<=downloadThreadsArray.length;i++){
					    mCurrentEveryThreadInfoMap.put(i, 0);
					}
					mDownloadThreadHelper.saveEveryThreadDownloadLength(mDownloadPath, mCurrentEveryThreadInfoMap);
				}else{
					for(int i=1;i<=threadNum;i++){
						downloadTotalSize=downloadTotalSize+mCurrentEveryThreadInfoMap.get(i);
					}
					// 更新已经下载的数据量
					if (mDownloadProgressListener != null) {
						mDownloadProgressListener.onDownloadSize(downloadTotalSize);
					}
					System.out.println("--> 断点回复处 --> 已经下载  downloadTotalSize="+downloadTotalSize);
				}
			}else{
				throw new RuntimeException("The HttpURLConnection is fail");
			}

		} catch (Exception e) {
			throw new RuntimeException("Init FileDownloader is fail");
		}
    }

    public int startDownload( ){
    	try {

			URL downloadURL=new URL(mDownloadPath);

			/**
			 * 对每条线程的下载情况进行判断
			 * 如果没有下载完,则继续下载
			 * 否则将该线程置为空
			 */
			for(int i=1;i<=downloadThreadsArray.length;i++){
				//取出此线程已经下载的大小
				int existDownloadSize=mCurrentEveryThreadInfoMap.get(i);
				if(existDownloadSize<everyThreadNeedDownloadLength && downloadTotalSize<rawFileSize){
					downloadThreadsArray[i-1]=new DownloadThread(this, i, everyThreadNeedDownloadLength, mCurrentEveryThreadInfoMap.get(i), downloadURL, mLocalFile);
					//设置优先级
					downloadThreadsArray[i-1].setPriority(7);
					//开始下载,注意数组的开始角标是从零开始的
					downloadThreadsArray[i-1].start();
				}else{
					downloadThreadsArray[i-1]=null;
				}
			}

			/**
			 * 注意:
			 * 对于下载失败的线程(-1)重新开始下载
			 */
			Boolean isAllFinish=true;
			while (isAllFinish) {
				isAllFinish = false;
				for (int i = 1; i <= downloadThreadsArray.length; i++) {
					if (downloadThreadsArray[i - 1] != null && !downloadThreadsArray[i - 1].isFinish()) {
						isAllFinish = true;
						if (downloadThreadsArray[i - 1].getDownloadSize() == -1) {
							downloadThreadsArray[i - 1] = new DownloadThread(this, i, everyThreadNeedDownloadLength,mCurrentEveryThreadInfoMap.get(i), downloadURL,mLocalFile);
							downloadThreadsArray[i - 1].setPriority(7);
							downloadThreadsArray[i - 1].start();
						}

					}
				}
			}

			//下载完成,删除记录
			mDownloadThreadHelper.deleteEveryThreadDownloadRecord(mDownloadPath);

		} catch (Exception e) {
			throw new RuntimeException("the download is fail");
		}
    	return downloadTotalSize;
    }

     //获取线程数
    public int getThreadsNum(){
    	return downloadThreadsArray.length;
    }

    //获取原始文件大小
    public int getFileRawSize(){
    	return rawFileSize;
    }

    //更新已经下载的总数据量
    protected synchronized void appendDownloadTotalSize(int newSize){
    	downloadTotalSize=downloadTotalSize+newSize;
    	if (mDownloadProgressListener!=null) {
    		mDownloadProgressListener.onDownloadSize(downloadTotalSize);
		}
    	System.out.println("当前总下载量 downloadTotalSize="+downloadTotalSize);
    }

     //更新每条线程已经下载的数据量
    protected synchronized void updateEveryThreadDownloadLength(int threadid,int position){
    	mCurrentEveryThreadInfoMap.put(threadid, position);
    	mDownloadThreadHelper.updateEveryThreadDownloadLength(mDownloadPath, mCurrentEveryThreadInfoMap);
    }

    //获取文件名
    public String getFileName(HttpURLConnection conn){
    	String filename = mDownloadPath.substring(mDownloadPath.lastIndexOf('/') + 1);
		if(filename==null || "".equals(filename.trim())){
			for (int i = 0;; i++) {
				String mine = conn.getHeaderField(i);
				if (mine == null) break;
				if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){
					Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());
					if(m.find()) return m.group(1);
				}
			}
			filename = UUID.randomUUID()+ ".tmp";
		}
		return filename;
	}

    public void setDownloadProgressListener(DownloadProgressListener downloadProgressListener){
    	mDownloadProgressListener=downloadProgressListener;
    }
}

DownloadThread如下:

package cc.download;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
 * 该类表示每一条进行下载的线程
 * 在下载的过程中不断地实时更新该线程的已下载量,从而刷新每条线程的已下载量
 * 亦实时更新已经下载的总数据量
 *
 * 分析说明
 * 1 在FileDownloader中提到最后一条线程下载的数据可能有redundant数据的问题
 *   在此描述解决办法如下:
 *   在计算每条线程下载终止位置(endPosition)时,需要对最后一条线程做特殊的处理
 *   当endPosition大于了原文件的大小,即代码endPosition>fileDownloader.getFileRawSize()
 *   此时需要修改该线程的结束位置endPosition和应该下载的数据量everyThreadNeedDownloadLength
 *
 * 2 遇到的一个问题
 *   在此处我们为每条线程设置了读取网络数据的范围,即代码:
 *   httpURLConnection.setRequestProperty("Range","bytes="+startPosition+"-"+endPosition);
 *   然后在InputStreamwhile不断读取数据的时候,采用的方式是:
 *   ((len=inputStream.read(b))!=-1)来判断是否已经读到了endPosition.
 *   之所以这么做是依据以往的经验且以为设置了Range,所以在读到endPosition的时候就应该返回-1
 *   但是这么做是错误的!!!!!!!!!!
 *   在读到endPosition的时候并没有返回-1,会一直往下读取数据导致错误.
 *   所以:
 *   需要给while()多加一个判断downLength<everyThreadNeedDownloadLength
 *   表示当前该线程已经下载的数据量小于它本该读取的数据量,此时才可以继续读数据.
 *
 *   请注意另外一个重要问题:
 *   每条线程最后一次读取可能会多读数据的问题.
 *   比如:还有1000个字节就完成了该线程本该操作的数据,但是inputStream在接下来的最后一次
 *   读数据时候却读了缓存byte [] b=new byte[1024]大小的字节数,造成了redundant数据的问题
 *   产生这个问题的原因还是因为在读到endPosition的时候并没有返回-1,所以会一直往下读取数.
 *   所以在此需要特殊处理:
 *   2.1 修正downLength即代码:downLength=everyThreadNeedDownloadLength;
 *   2.2 修正实际下载有用的数据的长度即代码:
 *       fileDownloader.appendDownloadTotalSize(len-redundant);
 *       而不是fileDownloader.appendDownloadTotalSize(len)
 *       从而确保了DownloadTotalSize值的准确
 */
public class DownloadThread extends Thread{
    private FileDownloader fileDownloader;
    private int threadId;
    private int everyThreadNeedDownloadLength;
    private int downLength;
    private URL downUrl;
    private File localFile;
    private Boolean isFinish=false;

	public DownloadThread(FileDownloader fileDownloader, int threadId, int everyThreadNeedDownloadLength,int downLength, URL downUrl, File localFile) {
		this.fileDownloader = fileDownloader;
		this.threadId = threadId;
		this.everyThreadNeedDownloadLength = everyThreadNeedDownloadLength;
		this.downLength = downLength;
		this.downUrl = downUrl;
		this.localFile = localFile;
	}

	@Override
	public void run() {
	    try {
	    	//当此线程已下载量小于应下载量
			if(downLength<everyThreadNeedDownloadLength){
				HttpURLConnection httpURLConnection=(HttpURLConnection) downUrl.openConnection();
				httpURLConnection.setConnectTimeout(5*1000);
				httpURLConnection.setRequestMethod("GET");
				httpURLConnection.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
				httpURLConnection.setRequestProperty("Accept-Language", "zh-CN");
				httpURLConnection.setRequestProperty("Referer", downUrl.toString());
				httpURLConnection.setRequestProperty("Charset", "UTF-8");

				//开始下载位置
				int startPosition=everyThreadNeedDownloadLength*(threadId-1)+downLength;
				//结束下载位置
				int endPosition=everyThreadNeedDownloadLength*threadId-1;
				//处理最后一条下载线程的特殊情况
				if (endPosition>fileDownloader.getFileRawSize()) {
					int redundant=endPosition-(fileDownloader.getFileRawSize()-1);
					endPosition=fileDownloader.getFileRawSize()-1;
					everyThreadNeedDownloadLength=everyThreadNeedDownloadLength-redundant;
				}
				//设置下载的起止位置
				httpURLConnection.setRequestProperty("Range","bytes="+startPosition+"-"+endPosition);
				System.out.println("====> 每条线程的下载起始情况 threadId="+threadId+",startPosition="+startPosition+",endPosition="+endPosition);
				httpURLConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
				httpURLConnection.setRequestProperty("Connection", "Keep-Alive");

				RandomAccessFile randomAccessFile=new RandomAccessFile(localFile, "rwd");
				randomAccessFile.seek(startPosition);
				InputStream inputStream=httpURLConnection.getInputStream();
				int len=0;
				byte [] b=new byte[1024];
				while((len=inputStream.read(b))!=-1 && downLength<everyThreadNeedDownloadLength){
					downLength=downLength+len;
					int redundant =0;
					//处理每条线程最后一次读取可能会多读数据的问题
					if (downLength>everyThreadNeedDownloadLength) {
						redundant =downLength-everyThreadNeedDownloadLength;
						downLength=everyThreadNeedDownloadLength;
					}
					randomAccessFile.write(b, 0, len-redundant);

					//实时更新该线程的已下载量,从而刷新每条线程的已下载量
					fileDownloader.updateEveryThreadDownloadLength(threadId, downLength);
					//实时更新已经下载的总量
					fileDownloader.appendDownloadTotalSize(len-redundant);
				}
				inputStream.close();
				randomAccessFile.close();
				//改变标志位
				isFinish=true;
			}

		} catch (Exception e) {
			downLength=-1;
			e.printStackTrace();
		}

	}

	//判断是否已经下载完成
    public Boolean isFinish(){
    	return isFinish;
    }

     //返回该线程已经下载的数据量,若-1则代表失败
    public int getDownloadSize(){
		return downLength;
    }

}

DBOpenHelper如下:

package cc.helper;

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

/**
 * 总结:
 * 1 继承自SQLiteOpenHelper
 * 2 完成构造方法
 */
public class DBOpenHelper extends SQLiteOpenHelper {
	private final static String DBName = "download.db";
	private final static int VERSION = 1;

	public DBOpenHelper(Context context) {
		super(context, DBName, null, VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE IF NOT EXISTS filedownload(id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");

	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("DROP TABLE IF EXISTS filedownload");
		onCreate(db);
	}

}

DownloadThreadHelper如下:

package cc.helper;

import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 该类主要用来操作每条线程对应的保存在数据库中的下载信息
 * 比如:某条线程已经下载的数据量
 */

public class DownloadThreadHelper {

	private DBOpenHelper dbOpenHelper;

	public DownloadThreadHelper(Context context) {
		dbOpenHelper = new DBOpenHelper(context);
	}

    /**
     * 保存每条线程已经下载的长度
     */
	public void saveEveryThreadDownloadLength(String path, Map<Integer, Integer> map) {
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		db.beginTransaction();
		try {
			for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
				db.execSQL("insert into filedownload(downpath, threadid, downlength) values(?,?,?)",
						   new Object[] { path, entry.getKey(), entry.getValue() });
			}
			db.setTransactionSuccessful();
		} finally {
            db.endTransaction();
            db.close();
		}
	}

	/**
	 * 获取每条线程已经下载的长度
	 */
	public Map<Integer, Integer> getEveryThreadDownloadLength(String path){
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		Cursor cursor=db.rawQuery("select threadid,downlength from filedownload where downpath=?", new String[]{path});
		Map<Integer, Integer> threadsMap=new HashMap<Integer, Integer>();
		while(cursor.moveToNext()){
			int threadid=cursor.getInt(0);
			int downlength=cursor.getInt(1);
			threadsMap.put(threadid, downlength);
		}
		cursor.close();
		db.close();
		return threadsMap;
	}

	/**
	 * 实时更新每条线程已经下载的数据长度
	 * 利用downPath和threadid来确定其已下载长度
	 */
	public void updateEveryThreadDownloadLength(String path, Map<Integer, Integer> map) {
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		db.beginTransaction();
		try {
			for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
				db.execSQL("update filedownload set downlength=? where threadid=? and downpath=?",new Object[] { entry.getValue(), entry.getKey(), path});
			    System.out.println("更新该线程下载情况  threadID="+entry.getKey()+",length="+entry.getValue());
			}
			db.setTransactionSuccessful();
		} finally {
            db.endTransaction();
            db.close();
		}
	}

    /**
     * 下载完成后,删除每条线程的记录
     */
	public void deleteEveryThreadDownloadRecord(String path){
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		db.execSQL("delete from filedownload where downpath=?", new String[]{path});
		db.close();
	}
}

main.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="文件下载路径:" />

    <EditText
        android:id="@+id/urlEditText"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="http://y1.ifengimg.com/dc14f57c79882c4a/2013/1003/re_524cb964403c1.jpg" />

    <Button
        android:id="@+id/downloadButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下载" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="18dip" />

    <TextView
        android:id="@+id/percentTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center" />

</LinearLayout>

 

时间: 2024-11-02 08:07:03

Android多线程断点下载完整示例详解的相关文章

Java 多线程断点下载文件_详解

基本原理:利用URLConnection获取要下载文件的长度.头部等相关信息,并设置响应的头部信息.并且通过URLConnection获取输入流,将文件分成指定的块,每一块单独开辟一个线程完成数据的读取.写入.通过输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中.同时,每个线程写入的数据都文件指针也就是写入数据的长度,需要保存在一个临时文件中.这样当本次下载没有完成的时候,下次下载的时候就从这个文件中读取上一次下载的文件长度,然后继续接着上一次的位

Android编程之SurfaceView学习示例详解_Android

本文实例讲述了Android编程之SurfaceView学习示例.分享给大家供大家参考,具体如下: SurfaceView是View的子类,使用的方式与任何View所派生的类都是完全相同的,可以像其他View那样应用动画,并把它们放到布局中. SurfaceView封装的Surface支持使用本章前面所描述的所有标准Canvas方法进行绘图,同时也支持完全的OpenGL ES库. 使用OpenGL,你可以再Surface上绘制任何支持的2D或者3D对象,与在2D画布上模拟相同的效果相比,这种方法

Android编程之SurfaceView学习示例详解

本文实例讲述了Android编程之SurfaceView学习示例.分享给大家供大家参考,具体如下: SurfaceView是View的子类,使用的方式与任何View所派生的类都是完全相同的,可以像其他View那样应用动画,并把它们放到布局中. SurfaceView封装的Surface支持使用本章前面所描述的所有标准Canvas方法进行绘图,同时也支持完全的OpenGL ES库. 使用OpenGL,你可以再Surface上绘制任何支持的2D或者3D对象,与在2D画布上模拟相同的效果相比,这种方法

Android如何自定义升级对话框示例详解

前言 本文主要给大家介绍了关于Android自定义升级对话框的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 实现的效果如下所示 其实这也只是一个DialogFragment 而已,重点只是在于界面的设计 想要使用做出这样一个DialogFragment ,需要自定义一个View,然后将该View传入到该Dialog中 先定义布局,一个TextView用于标题,一个TextView用于升级内容阐述,一个ImageView,一个确认升级的按钮 <?xml version

python多线程编程方式分析示例详解_python

在Python多线程中如何创建一个线程对象如果你要创建一个线程对象,很简单,只要你的类继承threading.Thread,然后在__init__里首先调用threading.Thread的__init__方法即可 复制代码 代码如下: import threading  class mythread(threading.Thread):  def __init__(self, threadname):  threading.Thread.__init__(self, name = thread

我的Android进阶之旅------&amp;gt;Android基于HTTP协议的多线程断点下载器的实现

一.首先写这篇文章之前,要了解实现该Android多线程断点下载器的几个知识点  1.多线程下载的原理,如下图所示 注意:由于Android移动设备和PC机的处理器还是不能相比,所以开辟的子线程建议不要多于5条.当然现在某些高端机子的处理器能力比较强了,就可以多开辟几条子线程. 2.为了实现断点下载,采用数据库方式记录下载的进度,这样当你将该应用退出后,下次点击下载的时候,程序会去查看该下载链接是否存在下载记录,如果存在下载记录就会判断下载的进度,如何从上次下载的进度继续开始下载. 3.特别注意

Android实现多线程断点下载的方法

  本文实例讲述了Android实现多线程断点下载的方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

Android入门:多线程断点下载详细介绍_Android

本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度.也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载.当下载完成后,自动删除本地文件. 一.多线程断点下载介绍 所谓的多线程断点下载就是利用多线程下载,并且可被中断,如果突然没电了,重启手机后可以继续下载,而不需要重新下载: 利用的技术有:SQLite存储各个线程的

C#多线程、跨线程与线程安全的示例详解

C#多线程.跨线程与线程安全的示例详解(三种不同方法)  代码如下 复制代码 using System.Threading; public static class Extensions     {         //控件扩展方法(用于跨线程操作),因为为了线程的安全,防止资源竞争出现死锁或不一致的状态,.NET是不允许进行跨线程访问窗体控件的.         public static void SafeCall(this Control ctrl, Action callback)