Android多线程下载文件

这个demo是我仿照的资料上写的,本来应该实现断点续传的,但是最终结果却没有实现,具体问题我也没有发现。

实现的过程如下:

1)当DownLoadActivity.java中点击下载下载按钮的时候,触发单击事件,在单击事件内部调用download()方法实现下载功能

2)实现下载downLoad(),在run()方法中“new”FileDownLoader类,返回下载的文件大小和下载的数量

3)在FileDownLoader类中downLoad方法中调用Thread实现下载。

4)在FileDownLoader中的download()方法中继续未完成的下载(这点没有实现,每次继续下载时下载的进度都是从0开始的???)

5)下载完成,删除数据库中的文件

6)更新UI

首先建立数据库存放下载的资源

package com.hnust.cn.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
 * 创建数据库的工具类
 * @author zzg
 *
 */
public class DBOpenHelper extends SQLiteOpenHelper {

	private final static String DBNAME="eric.db";
	private final static int VERSION=1;
	public DBOpenHelper(Context context) {
		// TODO Auto-generated constructor stub
		super(context,DBNAME,null,VERSION);
	}
	@Override
	public void onCreate(SQLiteDatabase db) {
		// TODO Auto-generated method stub
		db.execSQL("create table if not exists filedownlog(id integer primary key autoincrement,downpath varchar(100),threadid integer,downlength integer)");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// TODO Auto-generated method stub
		db.execSQL("drop table if exists filedownlog");
		onCreate(db);
	}

}

package com.hnust.cn.db;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

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

/**
 * 操作数据库表的工具类
 * @author zzg
 *
 */
public class FileService {

	private DBOpenHelper helper;
	public FileService(Context context){
		helper=new DBOpenHelper(context);
	}
	/***
	 * 获取特定路径的每条线程已经下载的文件长度
	 * @param path
	 * @return
	 */
	public Map<Integer,Integer> getData(String path){
		SQLiteDatabase db=helper.getReadableDatabase();
		Cursor cursor=db.rawQuery("select threadid,downlength from filedownlog where downpath=?", new String[]{path});
		Map<Integer,Integer> data=new HashMap<Integer, Integer>();
		while(cursor.moveToNext()){
			data.put(cursor.getInt(0), cursor.getInt(1));
			data.put(cursor.getInt(cursor.getColumnIndex("threadid")),cursor.getInt(cursor.getColumnIndex("downlength")));
		}
		cursor.close();
		db.close();
		return data;
	}
	/*******
	 * 保存每条线程下载的长度
	 * @param path 下载的路径
	 * @param map 现在的id,与已经下载的长度的集合
	 */
	public void save(String path,Map<Integer,Integer> map){
		SQLiteDatabase db=helper.getWritableDatabase();
		db.beginTransaction();
		for(Map.Entry<Integer, Integer> entry:map.entrySet()){
			db.execSQL("insert into filedownlog(downlength,threadid,downlength) values (?,?,?)",new Object[]{path,entry.getKey(),entry.getValue()});
		}
		db.setTransactionSuccessful();
		db.endTransaction();
		db.close();
	}
	/**
	 * 实时更新每条线程已经下载的文件长度
	 * @param path
	 * @param threadid
	 * @param pos
	 */
	public void update(String path,int threadid,int pos){
		SQLiteDatabase db=helper.getWritableDatabase();
		db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?",new Object[]{pos,path,threadid});
		db.close();
	}
	public void delete(String path){
		SQLiteDatabase db=helper.getWritableDatabase();
		db.execSQL("delete from filedownlog where downpath=?",new Object[]{path});
		db.close();
	}
}

实现文件下载类:

package com.hnust.cn.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.util.Log;

import com.hnust.cn.db.FileService;

/***
 * 文件下载的工具类
 * @author zzg
 *
 */
public class FileDownLoader {

	private final static String TAG="FileDownLoader";
	private Context context;
	private FileService fileService;
	private boolean exited;   //停止下载的标志
	private int downLoadSize=0;   //已下载文件的长度
	private int fileSize=0;        //原始文件长度
	private DownLoadThread[] threads;  //根据线程数目设置下载线程池
	private File saveFile;
	private Map<Integer,Integer> data=new ConcurrentHashMap<Integer, Integer>();
	private int block;//每条线程下载的长度
	private String downLoadUrl;
	/***
	 * 获取线程数目
	 * @return
	 */
	public int getThreadSize(){
		return threads.length;
	}
	/**
	 * 退出下载
	 */
	public void exit(){
		this.exited=true;
	}
	public boolean getExited(){
		return this.exited;
	}
	/**
	 * 获取文件大小
	 * @return
	 */
	public int getFileSize(){
		return fileSize;
	}
	/**
	 * 累计已经下载的大小
	 * synchronized 解决并发的问题
	 * @param size
	 */
	protected synchronized void append(int size){
		downLoadSize+=size;
	}
	/**
	 * 更新指定线程最后下载的位置(就是线程下载到的长度)
	 * @param threadId
	 * @param pos
	 */
	protected synchronized void update(int threadId,int pos){
		this.data.put(threadId, pos);
		this.fileService.update(downLoadUrl, threadId, pos);   
	}
	/***
	 * 构建文件下载器
	 * @param context
	 * @param downLoadUrl  下载路径
	 * @param fileSaveDir   文件保存目录
	 * @param threadNum    下载线程数目
	 */
	public FileDownLoader(Context context,String downLoadUrl,File fileSaveDir,int threadNum){
		try {
			this.context = context;
			this.downLoadUrl=downLoadUrl;
			fileService=new FileService(this.context);
			URL url=new URL(this.downLoadUrl);
			if(!fileSaveDir.exists()){
				fileSaveDir.mkdirs();//指定的文件不存在则创建此目录
			}
			this.threads=new DownLoadThread[threadNum];
			HttpURLConnection connection=(HttpURLConnection) url.openConnection();
			connection.setConnectTimeout(5*1000);
			connection.setRequestMethod("GET");
			connection.setRequestProperty("Accept", "image/gif,image/jpeg,image/pjpeg"
					+ ",application/vnd.ms-xpsdocument,application/x-shockwave-flash,application/xaml+xml"
					+ ",application/x-ms-xbap,application/x-ms-application,application/vnd.ms-excel"
					+ ",application/vnd.ms-powerpoint,application/msword,*/*");//设置客户端可以接收的媒体类型
			connection.setRequestProperty("Accept-Language", "zh-CN");//设置客户端编码
			connection.setRequestProperty("Referer", downLoadUrl);
			connection.setRequestProperty("Charset", "UTF-8");   //设置客户端编码
//			connection.setRequestProperty("User-Agent", "Mo");
			connection.setRequestProperty("Connection", "Keep-Alive");//设置连接方式,长连接
			connection.connect();//建立连接
			printResponseHeader(connection);
			if(connection.getResponseCode()==200){
				this.fileSize=connection.getContentLength();
				System.out.println("文件长度:"+fileSize);
				if(this.fileSize<=0){
					throw new RuntimeException("文件大小未知");
				}
				String fileName=getFileName(connection);
				this.saveFile=new File(fileSaveDir,fileName);
				Map<Integer,Integer> logdata=fileService.getData(downLoadUrl);  //获取下载记录
				if(logdata.size()>0){
					for(Map.Entry<Integer, Integer> entry:logdata.entrySet()){
						data.put(entry.getKey(),entry.getValue());//把每条线程的长度放入data中
					}
				}
				if(this.data.size()==this.threads.length){
					//如果已经下载的线程数目和设置的线程数目一样则计算所有下载的数据总长度
					for(int i=0;i<this.threads.length;i++){
						this.downLoadSize+=this.data.get(i+1);
					}
					Log.i(TAG,"已经下载的长度"+this.downLoadSize+"个字节");
				}
				this.block=(this.fileSize%this.threads.length)==0?this.fileSize/this.threads.length:this.fileSize/this.threads.length+1;
				
			}else{
				Log.i(TAG, "服务器异常");
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new RuntimeException("服务器异常");
		}
		
	}
	/**
	 * 开始下载文件 
	 * @param listener
	 * @return
	 */
	public int download(DownLoadProgressListener listener){
		try {
			RandomAccessFile raf=new RandomAccessFile(this.saveFile,"rwd");
			if(this.fileSize>0){
				raf.setLength(this.fileSize);
			}
			raf.close();
			URL url=new URL(this.downLoadUrl);
			if(this.data.size()!=this.threads.length){
				this.data.clear();
				for(int i=0;i<this.threads.length;i++){
					this.data.put(i+1, 0);//初始化每条线程的长度为0
				}
				this.downLoadSize=0;//设置已经下载的长度为0
			}
			for(int i=0;i<this.threads.length;i++){
				//开启线程下载
				int downloadLength=this.data.get(i+1);
				if(downloadLength<this.block&& this.downLoadSize<this.fileSize){
					this.threads[i]=new DownLoadThread(this,url,this.saveFile,this.block,this.data.get(i+1),i+1);
					this.threads[i].setPriority(7);
					this.threads[i].start();
				}else{
					this.threads[i]=null;//表明线程已经完成下载任务
				}
			}
			fileService.delete(this.downLoadUrl);
			fileService.save(this.downLoadUrl,this.data);
			boolean notFinished=true;
			while(notFinished){
				Thread.sleep(1000);
				notFinished=false;
				for(int i=0;i<this.threads.length;i++){
					if(this.threads[i]!=null && !this.threads[i].isFinished()){
						//如果有线程未完成下载
						notFinished=true;
						if(this.threads[i].getDownLoadedLength()==-1){
							//如果有线程下载失败,再重新在原有的基础上继续下载
							this.threads[i]=new DownLoadThread(this,url,this.saveFile,this.block,this.data.get(i+1),i+1);
							this.threads[i].setPriority(7);
							this.threads[i].start();
						}
					}
				}
				if(listener!=null){
					listener.onDownLoadSize(this.downLoadSize);
				}
			}
			if(downLoadSize==this.fileSize){
				fileService.delete(downLoadUrl);
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
//			throw new Exception("文件下载异常");
		}
		return this.downLoadSize;
	}
	/**
	 * 获取文件名
	 * @param connection
	 * @return
	 */
	private String getFileName(HttpURLConnection connection){
		String fileName=this.downLoadUrl.substring(this.downLoadUrl.lastIndexOf('/')+1);
		if(fileName==null||"".equals(fileName.trim())){
			for(int i=0;;i++){
				String mine=connection.getHeaderField(i);
				if(mine==null)break;
				if("content-disposition".equals(connection.getHeaderFieldKey(i).toLowerCase())){
					Matcher m=Pattern.compile(".*fileName=(.*)").matcher(mine.toLowerCase());
					if(m.find())
						return m.group(i);
				}
			}
			fileName=UUID.randomUUID()+".tmp";
		}
		return fileName;
	}
	/***
	 * 获取http响应的头字段
	 * @param http
	 * @return
	 */
	public static Map<String,String> getHttpResponseHeader(HttpURLConnection http){
		System.out.println("getHttpResponseHeader");
		Map<String,String> header=new LinkedHashMap<String, String>();//保证写入和遍历的时候顺序相同,而且允许空值存在
		for(int i=0;;i++){
			String fieldValue=http.getHeaderField(i);//返回第n个头字段的值
			if(fieldValue==null){
				break;
			}
			header.put(http.getHeaderFieldKey(i), fieldValue);
		}
		return header;
	}
	/**
	 * 打印http头字段
	 * @param http
	 */
	public static void printResponseHeader(HttpURLConnection http){
		Map<String,String> header=getHttpResponseHeader(http);
		for(Map.Entry<String, String> entry:header.entrySet()){
			String key=entry.getKey()!=null?entry.getKey()+":":"";
			//打印出头字段
			Log.i(TAG,key+entry.getValue());
		}
	}
}
创建线程类,实现真正的下载工作

package com.hnust.cn.util;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.util.Log;

public class DownLoadThread extends Thread {

	private final static String TAG="DownLoadThread";
	private FileDownLoader downLoader;
	private URL downUrl;
	private File saveFile;
	private int block;   //每条线程下载的大小
	private int downLoadedLength;
	private int threadId=-1;
	private boolean finished=false;
	public DownLoadThread(FileDownLoader downLoader,URL downUrl,File saveFile,int block,int downLoadedLength,int threadId){
		this.downUrl=downUrl;
		this.saveFile=saveFile;
		this.block=block;
		this.downLoader=downLoader;
		this.threadId=threadId;
		this.downLoadedLength=downLoadedLength;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if(downLoadedLength<block){
			HttpURLConnection connection;
			try {
				connection = (HttpURLConnection) downUrl.openConnection();
				connection.setConnectTimeout(5000);
				connection.setRequestMethod("GET");
				connection.setRequestProperty("Accept", "image/gif,image/jpeg,image/pjpeg"
						+ ",application/vnd.ms-xpsdocument,application/x-shockwave-flash,application/xaml+xml"
						+ ",application/x-ms-xbap,application/x-ms-application,application/vnd.ms-excel"
						+ ",application/vnd.ms-powerpoint,application/msword,*/*");//设置客户端可以接收的媒体类型
				connection.setRequestProperty("Accept-Language", "zh-CN");//设置客户端编码
				connection.setRequestProperty("Referer", downUrl.toString());
				int startPos=block*(threadId-1)+downLoadedLength;//开始的位置
				int endPos=block * threadId-1;//结束的位置
				connection.setRequestProperty("Range", "bytes="+startPos+"-"+endPos);
				connection.setRequestProperty("Connection", "Keep-Alive");
				InputStream is=connection.getInputStream();
				byte[] buffer=new byte[1024];
				Log.i(TAG, this.threadId+"从"+startPos+"开始下载");
				RandomAccessFile threadFile=new RandomAccessFile(this.saveFile, "rwd");
				threadFile.seek(startPos);
				int len=0;
				while(!downLoader.getExited() && (len=is.read(buffer))!=-1){
					threadFile.write(buffer, 0, len);
					downLoadedLength+=len;  //把新下载的文件加入到下载长度中
					downLoader.update(this.threadId,downLoadedLength);
					downLoader.append(len);
				}
				threadFile.close();
				is.close();
				if(downLoader.getExited()){
					Log.i(TAG, "线程"+"threadId"+"已经暂停");
					this.downLoadedLength=-1;
				}else{
					Log.i(TAG, "线程"+"threadId"+"已经下载完成");
				}
				this.finished=true;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				this.downLoadedLength=-1;
			}
		}
	}
	/**
	 * 下载是否完成
	 * @return
	 */
	public boolean isFinished(){
		return finished;
	}
	/***
	 * 已经下载的内容大小
	 * @return
	 */
	public long getDownLoadedLength(){
		return downLoadedLength;
	}
}

进度监接口:

package com.hnust.cn.util;
/**
 * 下载进度监听器
 * @author zzg
 *
 */
public interface DownLoadProgressListener {

	/**
	 * 下载进度监听方法获取和处理下载点数据的大小
	 * @param size
	 */
	public void onDownLoadSize(int size);
}

主activity:

package com.hnust.cn;

import java.io.File;

import com.hnust.cn.util.DownLoadProgressListener;
import com.hnust.cn.util.FileDownLoader;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
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;

public class MainActivity extends Activity implements OnClickListener {

	private static final int FAILURE=-1;//下载失败的标志
	private static final int PROCESSING=1;//正在下载实时传输Message标志
	private EditText et_url;
	private Button mStart;
	private Button mStop;
	private ProgressBar pd;
	private TextView tv;
	private Handler handler=new Handler(){
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case PROCESSING:
				int size=msg.getData().getInt("size");
				pd.setProgress(size);
				float num=(float)pd.getProgress()/(float)pd.getMax();
				int result=(int)(num*100);
				tv.setText(result+"%");
				int len=pd.getProgress();
				if(len==pd.getMax()){
					Toast.makeText(getApplicationContext(), "下载完成", 0).show();
//					len=0;
				}
				break;
			case FAILURE:
				Toast.makeText(getApplicationContext(), "下载失败", 0).show();
				break;
			default:
				break;
			}
		};
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		mStart.setOnClickListener(this);
		mStop.setOnClickListener(this);
	}
	private void initView(){
		et_url=(EditText) findViewById(R.id.et_url);
		mStart=(Button) findViewById(R.id.startDownLoad);
		mStop=(Button) findViewById(R.id.stopDownLoad);
		pd=(ProgressBar) findViewById(R.id.pd);
		tv=(TextView) findViewById(R.id.tv);
		et_url.setText("http://125.221.225.103:8888/upload/p.pdf");
	}
	@SuppressLint("NewApi")
	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch (v.getId()) {
		case R.id.startDownLoad:
			String path=et_url.getText().toString().trim();
			if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
				File saveDir=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
//				getExternalFilesDirs(Environment.DIRECTORY_MOVIES);
				//下载文件
				download(path,saveDir);
			}else{
				Toast.makeText(this, "sd卡不存在", 0).show();
			}
			mStart.setEnabled(false);
			mStop.setEnabled(true);
			break;
		case R.id.stopDownLoad:
			//退出下载
			exit();
			mStart.setEnabled(true);
			mStop.setEnabled(false);
			break;
		}
	}
	private DownLoadTask task;
	/***
	 * 退出下载
	 */
	public void exit(){
		if(task!=null){
			task.exit();
		}
	}
	/**
	 * 下载文件
	 */
	public void download(String path,File saveDir){
		task=new DownLoadTask(path,saveDir);
		new Thread(task).start();
	}
	private final class DownLoadTask implements Runnable{

		private String path;
		private File saveDir;
		private FileDownLoader loader;
		public DownLoadTask(String path,File saveDir) {
			// TODO Auto-generated constructor stub
			this.path=path;
			this.saveDir=saveDir;
		}
		public void exit(){
			if(loader!=null){
				loader.exit();
			}
		}
		DownLoadProgressListener listener=new DownLoadProgressListener() {
			
			@Override
			public void onDownLoadSize(int size) {
				// TODO Auto-generated method stub
				Message msg=new Message();
				msg.what=PROCESSING;
				msg.getData().putInt("size",size);
				handler.sendMessage(msg);
			}
		};
		@Override
		public void run() {
			// TODO Auto-generated method stub
			try{
				System.out.println("下载开始");
				loader=new FileDownLoader(getApplicationContext(), path, saveDir, 3);
				pd.setMax(loader.getFileSize());
				loader.download(listener);
			}catch(Exception e){
				handler.sendMessage(handler.obtainMessage(FAILURE));
			}
			
		}
		
	}
}


你可能感兴趣的:(Android多线程下载文件)