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

简介: MainActivity如下: package cc.activity;import java.io.File;import 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>


 

相关文章
|
17天前
|
Linux C++
LInux下Posix的传统线程示例
LInux下Posix的传统线程示例
15 1
|
1月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
35 4
|
1月前
|
API 数据库 Android开发
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第14天】随着移动设备性能的日益强大,用户对应用程序的响应速度和流畅性要求越来越高。在Android开发中,合理利用多线程技术是提升应用性能的关键手段之一。Kotlin作为一种现代的编程语言,其协程特性为开发者提供了更为简洁高效的多线程处理方式。本文将深入探讨使用Kotlin进行Android多线程编程的最佳实践,包括协程的基本概念、优势以及在实际项目中的应用场景和性能优化技巧,旨在帮助开发者构建更加高效稳定的Android应用。
|
3月前
|
存储 算法 开发工具
OpenCV 安卓编程示例:1~6 全
OpenCV 安卓编程示例:1~6 全
|
3月前
|
Java 调度 数据库
Android 性能优化: 如何进行多线程编程以提高应用性能?
Android 性能优化: 如何进行多线程编程以提高应用性能?
46 0
|
7月前
|
存储 SQL 安全
Android面试中问的线程相关问题
Android面试中问的线程相关问题
40 0
|
5天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
16天前
|
安全 Linux API
Android进程与线程
Android进程与线程
18 0
|
1月前
|
Java Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第17天】 随着移动设备性能的不断提升,用户对应用的响应速度和稳定性要求越来越高。在Android开发中,Kotlin语言以其简洁、安全的特点受到开发者青睐。然而,面对复杂的多线程任务,如何有效利用Kotlin进行优化,以提升应用性能,是本文探讨的重点。通过分析Kotlin并发工具的使用场景与限制,结合实例演示其在Android开发中的实践,旨在为开发者提供实用的多线程处理指南。
|
1月前
|
存储 Java
什么是线程池,线程池的状态,线程池的用法示例
什么是线程池,线程池的状态,线程池的用法示例