Android中多线程下载列表实现

       实现了一下Android中的文件多线程下载模块,支持自定义线程数、断点续传、下载任务的删除,添加等功能,这里封装了一下,功能已全部实现。不过由于使用的是最简单的手动线程数组及消息通知实现,可能还存在某些小问题。笔者会在后面的使用过程中再进行优化完善。先看一下程序测试效果,这里指定了5个下载任务,以及2个下载线程,具体如下:

        要运行以上Demo需要自己搭建服务器,和简单,只需要把所需的文件拷贝到Tomcat中的../webapps/ROOT文件夹下即可,以下是笔者的电脑:

       其中的0.mp3,1.mp3....11.mp3及本Demo的测试数据。

       除此之外还需要引入Afinal类库,已包含在工程下载中了(下载地址在本文最后)

       主要类介绍

       主界面MainActivity,负责初始化任务参数,用户可以单击列表中的按钮来删除、暂停、继续任务。可以看到有一个refreshHandler负责根据不同的Message来对列表进行不同的刷新操作。

package com.wly.filedownloader;
import java.util.List;
import com.wly.filedownloader.dao.BeanHelper;
import net.tsz.afinal.FinalDb;
import net.tsz.afinal.exception.AfinalException;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.graphics.Bitmap.Config;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends Activity{

	DownloadHelper downHelper;
	private LayoutInflater inflater;
	private ListView lv;
	
	//最大支持同时进行的线程数
	private int maxThread = 2;
	
	//测试下载任务数量
	private int taskSize = 5;
		
	private DynamicArray<Bean> waitArray; //等待列表
	private DynamicArray<Bean> workArray; //用来存放已完成及下载中的任务
	List<Bean> list;
	
	//专门负责刷新界面的Handler
	private Handler refreshHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			switch(msg.what) {
			case Conf.State_FINISH:
				myAdapter.notifyDataSetChanged();
				break;
			case Conf.State_CANCEL:
				for(int i=0;i<workArray.size();i++) {
					if(workArray.getObjectAt(i).getGuid().equals(msg.obj)) {
						workArray.delete(i);
					}
				}
				downHelper.refreshDownloaderArray();
				myAdapter.notifyDataSetChanged();
				break;
			case Conf.MSG_STATECHANGED:
				myAdapter.notifyDataSetChanged();
				break;
			case Conf.State_DOWNLOAD:
				//刷新可见区域列表进度值
				int firstV = lv.getFirstVisiblePosition();
				int lastV = lv.getLastVisiblePosition();
				for (int i = firstV; i <= lastV; i++)  {
					
					View cell = lv.findViewById(i);
					cell.getId();
					if(cell != null) {
						Bean bean = workArray.getObjectAt(i);
						if(bean != null) {
							((ProgressBar)cell
									.findViewById(R.id.download_progress))
									.setProgress(bean.getPercent());
						}
					}
				}
				break;
			case Conf.State_FILEERROR:
				//文件版本异常
				break;
			}
		}
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		lv = (ListView) findViewById(R.id.listView1);
		Bean[] beanArray = new Bean[taskSize];
		workArray = new DynamicArray<Bean>();
		waitArray = new DynamicArray<Bean>();

		
		for(int i=0;i<beanArray.length;i++) {
			beanArray[i] = new Bean("AA_" + i,"http://10.0.2.2:8080/" + i + ".mp3", i + ".mp3"
					,Conf.State_WAITTASK,0);
		}
	
		//1.添加测试数据到本地数据库
		List<Bean> list = FinalDb.create(this).findAll(Bean.class);
		if(list.size() == 0) {
			for(int i=0;i<beanArray.length;i++) {
				FinalDb.create(this).save(beanArray[i]);
			}

			list = FinalDb.create(this).findAll(Bean.class);	
		}
		
		
		for(Bean bean:list) {
			System.out.println("id:" + bean.getId() + "," + "title:" + bean.getTitle() 
					+ "," + "url:" + bean.getUrl()  + "," + "isEnabled:" + bean.getIsEnabled() 
					+ ",isFinished:" + bean.getIsFinished() 
					+ "," + "cZise:" + bean.getCompleteSize() + ",percent:" + bean.getPercent());
		}
		for(Bean bean:list) {
			waitArray.insert(bean);
		}
		
		downHelper = new DownloadHelper(this,maxThread,waitArray,workArray,refreshHandler);

		downHelper.assignTaskToDownloaders();

		inflater = LayoutInflater.from(this);
		
		lv.setAdapter(myAdapter);

	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		//停止所有下载器任务
		downHelper.kill();
	}
	
	class ViewHolder {
		ProgressBar progressBar;
		Button pauseBtn,cancelBtn,watingBtn,resumeBtn;
		RelativeLayout level_1,level_2,level_3;
		TextView title;
	}
	
	
	ViewHolder holder;

	BaseAdapter myAdapter = new BaseAdapter() {
		
		@Override
		public View getView(final int position, View convertView, ViewGroup parent) {
			
			final Bean bean;
			//先显示"完成列表",再显示"下载中及等待列表"
			if(position < workArray.size()) {
				bean = workArray.getObjectAt(position);
			} else {
				bean = waitArray.getObjectAt(position-workArray.size());
			}
			final Downloader downloader = downHelper.getDownloaderByGuid(bean.getGuid());
			
			convertView = inflater.inflate(R.layout.download_list_item, null);
			ProgressBar progressBar = (ProgressBar) convertView.findViewById(R.id.download_progress);
			final Button pauseBtn = (Button)convertView.findViewById(R.id.download_btn_pause);
			Button cancelBtn = (Button)convertView.findViewById(R.id.download_btn_cancel);
			Button resumeBtn = (Button)convertView.findViewById(R.id.download_btn_resume);
			Button waitCancelBtn = (Button)convertView.findViewById(R.id.wait_btn_cancel);
			RelativeLayout download_level = (RelativeLayout)convertView.findViewById(R.id.download_level);
			RelativeLayout waiting_level = (RelativeLayout)convertView.findViewById(R.id.waiting_level);
			RelativeLayout finish_level = (RelativeLayout)convertView.findViewById(R.id.finish_level);
			TextView title = (TextView)convertView.findViewById(R.id.download_title);

			//根据Bean的状态来决定暂停、继续、取消等按钮的显示状态
			progressBar.setProgress((int)bean.getPercent());
			title.setText(bean.getTitle());
			final String guid = bean.getGuid();
			
			if(bean.getIsEnabled() == Conf.TRUE) { //对应下载进度(0,100) => 暂停/继续
				download_level.setVisibility(View.VISIBLE);
				waiting_level.setVisibility(View.INVISIBLE);
				finish_level.setVisibility(View.INVISIBLE);
				if(downloader.getstate() == Conf.State_DOWNLOAD) { //下载中
					resumeBtn.setVisibility(View.INVISIBLE); 
					pauseBtn.setVisibility(View.VISIBLE); 
				} else { //暂停中
					resumeBtn.setVisibility(View.VISIBLE); 
					pauseBtn.setVisibility(View.INVISIBLE); 
					resumeBtn.setText("继续");
				}
			} else if(bean.getIsFinished() == Conf.TRUE) { //对应下载进度100 => 完成
				download_level.setVisibility(View.INVISIBLE);
				waiting_level.setVisibility(View.INVISIBLE);
				finish_level.setVisibility(View.VISIBLE);
			} else { //对应下载进度0 => 开始/等待
				//检查是否处于等待状态
				System.out.println("--开始/等待--");
				if(downloader != null) {
					download_level.setVisibility(View.VISIBLE);
					waiting_level.setVisibility(View.INVISIBLE);
					finish_level.setVisibility(View.INVISIBLE);
					
					resumeBtn.setText("开始");
				} else {
					download_level.setVisibility(View.INVISIBLE);
					waiting_level.setVisibility(View.VISIBLE);
					finish_level.setVisibility(View.INVISIBLE);
				}
			}
			
			pauseBtn.setOnClickListener(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					downloader.pause();
				}
			});
			
			resumeBtn.setOnClickListener(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					if(downloader != null && downloader.isActive()) {
						downloader.recovery();
						System.out.println("--recovery--");
					} else {
						downloader.start();
						System.out.println("--start--");
					}
				}
			});
			
			//下载中"取消"按钮
			cancelBtn.setOnClickListener(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					downloader.cancel();
				}
			});
			
			//等待中"取消"按钮
			waitCancelBtn.setOnClickListener(new View.OnClickListener() {
				
				@Override
				public void onClick(View v) {
					if(position < workArray.size()) {
						downloader.cancel();
					} else {
						waitArray.delete(position-workArray.size());
						BeanHelper.delete(MainActivity.this,bean);
						myAdapter.notifyDataSetChanged();
					}
				}
			});
			
			convertView.setId(position);
			return convertView;
		}
		
		@Override
		public long getItemId(int position) {
			return 0;
		}
		
		@Override
		public Object getItem(int position) {
			if(position < workArray.size()) {
				return workArray.getObjectAt(position);
			} else {
				return waitArray.getObjectAt(position-workArray.size());
			}
		}
		
		@Override
		public int getCount() {
			return waitArray.size() + workArray.size();
		}
	};
}
      

       下载器调度类DownloaderHelper,负责检查任务队列和下载器数组,并将下载任务分配给空闲的下载器。

package com.wly.filedownloader;

import com.wly.filedownloader.Downloader.MyDownloadListener;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;

/**
 * 本类中包含两个队列一个是等待中的实体Bean,另一个是下载中的任务队列, 当添加一个任务实体Bean时,会先检查是否有空余的下载器没有使用,如果有,就
 * 使用refreshDownloaderArray将该任务Bean从"等待下载队列"移动到"下载中队列"
 * 同样的,当一个任务下载成功后,先从"下载中队列"移除,然后得到一个空闲的下载器,最后调用插入流程,将一个新的任务Bean 添加到到"下载中队列"中去
 * 最后是删除(取消),如果发生在"等待"队列则删除数据即可,如果发生在"下载中"队列则复用下载完成逻辑即可。
 * 
 * @author wly
 * 
 */
public class DownloadHelper {

	private DynamicArray<Bean> waitArray;
	private DynamicArray<Bean> workArray;
	private int maxThread; // 最大同时进行任务数量,默认是1
	private Context mContext;

	/**
	 * 下载器队列
	 */
	private Downloader[] downladerArray;

	private Handler mHandler; // Activity中的Handler

	private long lastRefreshTime = 0;

	private int refresh_time = 0; // 每次刷新进度的最少间隔时间

	// 做一层过滤,控制界面刷新频率
	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if (msg.what == Conf.State_FILEERROR) {
				System.out.println("--文件异常,发送重置对应下载任务界面显示信息!!!--");
			} else {
				// 每隔refresh_time时间发送一次刷新界面信息
				if ((System.currentTimeMillis() - lastRefreshTime) > refresh_time) {
					mHandler.sendEmptyMessage(Conf.State_DOWNLOAD);
					lastRefreshTime = System.currentTimeMillis();
				}
			}
		}
	};

	private MyDownloadListener myDownloadListener = new MyDownloadListener() {

		@Override
		public void finished(Bean bean) {

			// 1.尝试新的任务给空闲的下载器
			refreshDownloaderArray();
			// 2.刷新界面
			Message msg = new Message();
			msg.what = Conf.State_FINISH;
			mHandler.sendMessage(msg);

			System.out.println("--finished");
		}

		@Override
		public void started() {
			Message msg = new Message();
			msg.what = Conf.MSG_STATECHANGED;
			mHandler.sendMessage(msg);
		}

		@Override
		public void paused() {
			Message msg = new Message();
			msg.what = Conf.MSG_STATECHANGED;
			mHandler.sendMessage(msg);
		}

		@Override
		public void resumed() {
			Message msg = new Message();
			msg.what = Conf.MSG_STATECHANGED;
			mHandler.sendMessage(msg);
		}

		@Override
		public void canceled(String guid) {

			// 发送消息给Activity
			Message msg = new Message();
			msg.what = Conf.State_CANCEL;
			msg.obj = guid;
			mHandler.sendMessage(msg);
		}

		/**
		 * 准备就绪(主要是文件校验过程),可以开始下载了,
		 */
		@Override
		public void ready(String guid) {
			System.out.println("--准备就绪,可以开始下载了--");
		}
	};

	/**
	 * 创建实体Bean的队列
	 * 
	 * @param context
	 * @param maxThread
	 * @param beans
	 * @param handler
	 */
	public DownloadHelper(Context context, int maxThread,
			DynamicArray<Bean> dArray, DynamicArray<Bean> workArray,
			Handler handler) {
		this.maxThread = maxThread;
		this.mContext = context;
		this.waitArray = dArray;
		this.workArray = workArray;
		this.mHandler = handler;

		downladerArray = new Downloader[maxThread];
	}

	/**
	 * 添加任务到队列
	 * 
	 * @param bean
	 */
	public void insert(Bean bean) {
		waitArray.insert(bean);
		refreshDownloaderArray();
	}
	
	/**
	 * 初始启动Activity时,为下载器数组中的下载器分配下载任务
	 */
	public void assignTaskToDownloaders() {
		int i = 0;
		while (i < maxThread && !waitArray.isEmpty()) {
			// 为下载器分配下载任务
			Bean bean = waitArray.poll();
			workArray.insert(bean); // 不管任务完成与否都插入到"工作队列"中去

			if (bean.getIsFinished() == Conf.FALSE) { // 只为未完成的任务分配下载器
				downladerArray[i] = new Downloader(mContext);
				downladerArray[i].assignTask(bean, myDownloadListener, handler);
				i++;
			}
		}
	}

	/**
	 * 取消整个队列任务
	 */
	public void kill() {
		for (Downloader d : downladerArray) {
			if (d != null) {
				d.kill();
			}
		}
	}

	/**
	 * 检查下载器队列的状态,负责刷新下载器中的任务。通常在新增、更新、删除任务后调用
	 */
	public void refreshDownloaderArray() {
		for (int i = 0; i < maxThread; i++) {
			if (downladerArray[i] != null && downladerArray[i].isIdle()) {
				if (!waitArray.isEmpty()) {
					Bean bean = waitArray.poll();
					workArray.insert(bean);
					// 为下载器分配下载任务
					downladerArray[i].assignTask(bean, myDownloadListener,
							handler);
					// 开始下载
					downladerArray[i].recovery();
				} else {
					downladerArray[i].kill();
				}
			}
		}
	}

	/**
	 * 根据guid查询得到其对应的实体bean对象
	 * 
	 * @param guid
	 * @return
	 */
	public Downloader getDownloaderByGuid(String guid) {
		for (Downloader d : downladerArray) {
			if (d != null && d.getBean().getGuid().equals(guid)) {
				return d;
			}
		}
		return null;
	}

	// /**
	// * 检查网络环境
	// */
	// public boolean isNetAvailable() {
	// return true;
	// }
}

        下载器类Downloader,负责下载数据,将下载状态进行持久化。需要特别说明一下的是,这里的下载器在启动后会进入一个下载循环,即使当前任务断开服务器连接,进入暂停状态,本下载器不会停止,只是进入了wait状态。除此之外,下载器中还包含自定义接口MyDownloadListener,用于向Activity反馈当前的下载状态,以更新界面。

package com.wly.filedownloader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;

import com.wly.filedownloader.dao.BeanHelper;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;

/**
 * 下载器
 * @author wly
 *
 */
public class Downloader extends Thread {

	private Bean mBean;
	private MyDownloadListener mListener;
	
	//用于表征当前下载器是否处于空闲状态,如果是的话,可以为其分配新的下载任务,进入空闲状态只有两种方法:"下载完成"和"取消下载"
	private boolean isIdle; 
	
	private boolean isAlive = false; //表示当前下载器是否已经激活,默认未激活
	
	private Handler handler;
	
	private Context mContext;
	
	private int beanPercent;	
	
	private int state;
	/**
	 * 创建对象
	 * @param context
	 */
	public Downloader(Context context) {
		isIdle = true;
		this.mContext = context;
		state = Conf.State_WAITTASK;
	}

	public boolean isPause() {
		return (state == Conf.State_PAUSE);
	}
	
	public boolean isRun() {
		return (state == Conf.State_DOWNLOAD);
	}
	
	/**
	 * 返回当前下载器的激活状态
	 * @return
	 */
	public boolean isActive() {
		return isAlive;
	}

	/**
	 * 等到当前下载器状态
	 * @return
	 */
	public int getstate() {
		return state;
	}
	
	/**
	 * 分配任务
	 * @param id
	 * @param bean
	 * @param listener
	 * @param handler
	 */
	public void assignTask(Bean bean,MyDownloadListener listener,Handler handler) {

		this.mBean = bean;
		this.mListener = listener;
		this.handler = handler;
		isIdle = false;
	}
	
	public Bean getBean() {
		return this.mBean;
	}

	/**
	 * 取消下载任务
	 */
	public void cancel() {
		
			//先暂停当前任务
			if(!isPause()) { 
				pause();
			}
			AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
			dialog.setTitle("提示")
				.setMessage("确定要取消当前选中的任务吗?")
				.setPositiveButton("是的", new DialogInterface.OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						if(state == Conf.State_WAITTASK || state == Conf.State_PAUSE) {
							isIdle = true;
							BeanHelper.delete(mContext, mBean);
							mListener.canceled(mBean.getGuid());
							mBean.setPercent(0);
							state = Conf.State_WAITTASK;
						} else {
							state = Conf.State_CANCELING;
						}
					}
				}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
					
					@Override
					public void onClick(DialogInterface dialog, int which) {
						recovery(); //恢复下载
					}
				}).show();
	}
	
	/**
	 * 暂停任务
	 */
	public void pause() {
		state = Conf.State_PAUSE;
		mListener.paused();
	}
	
	public void recovery() {
		state = Conf.State_DOWNLOAD;
		mListener.resumed();
		interrupt();
	}
	
	public boolean isPaused() {
		return state == Conf.State_PAUSE;
	}
	
	/**
	 * 取消当前任务
	 */
	public void kill() {
		System.out.println("Kill the downloader with guid:" + mBean.getGuid());
		state = Conf.State_WAITTASK;
		isAlive = false;
	}
	
	public boolean isIdle() {
		return isIdle;
	}
	
	@Override
	public void run() {
		synchronized (this){
			long fileSize = 0;
			RandomAccessFile randomAccessFile = null;
			long completedSize = 0;
			isAlive = true;
			isIdle = false;
			state = Conf.State_DOWNLOAD;
			InputStream is = null;
			HttpURLConnection conn = null;
			File file = null;
			mBean.setIsEnabled(Conf.TRUE);
			while(isAlive) {
				//如果有可下载任务,就进行下载
				if(state == Conf.State_DOWNLOAD) {
					//1.读取当前下载任务信息,如果是断点继续下载,则进行文件连续性判断				
					beanPercent = mBean.getPercent();
					completedSize = mBean.getCompleteSize();
					//检查本地文件是否存在
					File pathFile = new File(Conf.getSaveDir(mContext));
					if (!pathFile.exists()) {
						pathFile.mkdirs();
					}
					file = new File(Conf.getSaveDir(mContext) 
							+ File.separator  + mBean.getTitle());
					System.out.println("新任务:" + mBean.getTitle());
					if(!file.exists()) {
						if(mBean.getCompleteSize() == 0) {//新任务
							try {
								file.createNewFile();
							} catch (IOException e) {
								e.printStackTrace();
							}
						} else { //暂停任务,但是文件不存在,则重置下载任务
							mBean = new Bean(mBean.getGuid(), mBean.getUrl(), mBean.getTitle(), Conf.State_WAITTASK, 0);
							BeanHelper.update(mContext, mBean);
							beanPercent = mBean.getPercent();
							completedSize = mBean.getCompleteSize();
							
							Message msg = new Message();
							msg.what = Conf.State_FILEERROR;
							handler.sendMessage(msg);
						}
					}
					
					
					//2.与服务器检验文件的统一性
					//verifyFileWithServer();
					
					//从当前进度点开始数据下载
					URL url;
					try {
						url = new URL(mBean.getUrl());
						conn = (HttpURLConnection) url.openConnection();
						conn.setDoInput(true);
						conn.setDoOutput(true);
						conn.setConnectTimeout(Conf.NET_TIMEOUT_CONNECT);
						conn.setReadTimeout(Conf.NET_TIMEOUT_READ);
						conn.setRequestMethod("GET");
						
						fileSize = conn.getContentLength(); //得到文件尺寸
						
						try {
							randomAccessFile = new RandomAccessFile(file, "rwd");
							randomAccessFile.setLength(mBean.getCompleteSize());
							beanPercent = (int)((double)(completedSize) / fileSize * 100);
							randomAccessFile.seek((long)beanPercent);
						} catch (FileNotFoundException e) {
							e.printStackTrace();
							//重置下载任务
							
						} catch (IOException e) {
							e.printStackTrace();
							//重置下载任务
						}
						
						//准备就绪
						mListener.started();
						mBean.setIsEnabled(Conf.TRUE);
						mListener.ready(mBean.getGuid());	
					} catch (MalformedURLException e1) {
						e1.printStackTrace();
					} catch (ProtocolException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
					
					//开始下载
					try {
						is = conn.getInputStream();
						int length = -1;
						byte[] buffer = new byte[2048];
						while(state == Conf.State_DOWNLOAD &&
									(length=is.read(buffer)) != -1) {
							randomAccessFile.write(buffer, 0, length);
							completedSize += length;
							beanPercent = (int)((double)(completedSize) / fileSize * 100);
							mBean.setPercent(beanPercent);
							
							Message msg = new Message();
							handler.sendMessage(msg);
							
							System.out.println("percent:" + beanPercent);
							//当前任务已完成,进入"空闲"状态
							if(beanPercent == 100) {
								System.out.println("--一个下载任务完成--");

								mBean.setIsFinished(Conf.TRUE);
								mBean.setIsEnabled(Conf.FALSE);
								//1.数据持久化动作
								mBean.setPercent(beanPercent);
								mBean.setCompleteSize(completedSize);
								BeanHelper.update(mContext, mBean);
								System.out.println("---更新状态完毕---");
							
								//2.下载完毕,发送通知,刷新界面,等待新任务
								//告知下载队列,请求新任务
								isIdle = true;
								state = Conf.State_WAITTASK;
								mListener.finished(mBean);
								beanPercent = 0;
								completedSize = 0;
//								try {
//									sleep(1000);
//									System.out.println("--等待分配新的任务--");
//								} catch (InterruptedException e) {
//									e.printStackTrace();
//								}
								break;
							}
						}
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				}
				
				if(state == Conf.State_PAUSE) {
					//1.断开连接
					if(conn != null) {
						try {
							is.close();
							conn.disconnect();
						} catch (IOException e) {
							e.printStackTrace();
						}
						System.out.println("---断开连接----");
						//2.进行数据持久化
						mBean.setIsEnabled(Conf.TRUE);
						mBean.setPercent(beanPercent);
						mBean.setCompleteSize(completedSize);
						BeanHelper.update(mContext, mBean);
						System.out.println("---更新状态---");
					}
				}
				
				if(state == Conf.State_CANCELING) {
					//1.删除数据库数据
					BeanHelper.delete(mContext, mBean);
					//2.刷新界面
					isIdle = true;
					mListener.canceled(mBean.getGuid());
					state = Conf.State_WAITTASK;
				}
//				try {
//					System.out.println("下载器" + mBean.getGuid() + "正处于暂停/空闲状态");
//					sleep(1000);
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("This Downloader" + mBean.getGuid() + " has been killed!!!");
		}
		

	}
	
	/**
	 * 从文件/网路路径中解析出文件名
	 * @param filePath
	 * @return
	 */
	public String getFileNameFromPath(String filePath) {
		String[] ss = filePath.split("//");
		return ss[ss.length-1];
	}
	
	/**
	 * 下载过程的生命周期回调接口,用于刷新界面。注意:界面刷新是在数据更改之后的
	 * @author wly
	 *
	 */
	public interface MyDownloadListener {
		/**
		 * 下载完成,发送通知Helper,Helper再通过Handler通知Activity
		 * @param downloaderID 下载器在下载器数组中的位置索引
		 */
		public void finished(Bean bean);
		/**
		 * 已经开始下载,下载中,本方法的调用在ready()之后
		 * @param id
		 */
		public void started();
		/**
		 * 已经暂停下载
		 * @param id
		 */
		public void paused();
		/**
		 * 已经恢复下载
		 * @param id
		 */
		public void resumed();
		/**
		 * 已经取消下载
		 * @param guid
		 */
		public void canceled(String guid);
		/**
		 * 已经准备就绪(文件校验,断点读取等),可以开始下载
		 * @param guid
		 */
		public void ready(String guid);
	}
}

        自定义动态数组类DynamicArray,是下载任务容器,集合了队列和数组的特性。这是由模块的需求决定的,下载任务应该用优先队列(FIFO特性)来做,但是由于下载队列又需要支持用户的删除某个指定任务的功能(数组访问指定索引特性)。

package com.wly.filedownloader;

/**
 * 动态数组,结合队列(FIFO)和数组(根据索引进行删除元素)的特性
 * 
 * @author wly
 * 
 */
public class DynamicArray<T> {

	private T[] elems;
	private int mRight; // 右侧有内容索引值,即队列尾

	private int mLeft; // 左侧有内容索引值,即队列首

	private int INCREATE_STEP = 12;

	// public static void main(String[] args) {
	// DynamicArray<Student> array = new DynamicArray<Student>();
	// array.insert(new Student("A"));
	// array.insert(new Student("B"));
	// array.insert(new Student("C"));
	// array.insert(new Student("D"));
	// array.insert(new Student("E"));
	// array.insert(new Student("F"));
	//
	// array.poll();
	// array.peek();
	// array.delete(2);
	// array.getObjectAt(2);
	// System.out.println(array.size());
	//
	// }

	static class Student {
		String name;

		public Student(String name) {
			this.name = name;
		}
	}

	public DynamicArray() {
		elems = (T[]) new Object[INCREATE_STEP];
		mLeft = 0;
		mRight = 0;
	}

	/**
	 * 插入一个元素到数组
	 * 
	 * @param t
	 */
	public void insert(T t) {
		// 扩展数组
		if (mRight >= elems.length) {
			T[] temp = (T[]) new Object[elems.length + INCREATE_STEP];
			for (int i = 0; i < elems.length; i++) {
				temp[i] = elems[i];
			}
			elems = temp;
			temp = null;
		}

		if (elems[mRight] == null) {
			elems[mRight++] = t;
		} else {
			elems[mRight++] = t;
		}
	}

	public T peek() {
		if (!isEmpty()) {
			return elems[mLeft];
		}
		return null;
	}

	/**
	 * 弹出一个元素,将数组起点到p之间的元素都往右移动一位
	 * 
	 * @return
	 */
	public T poll() {
		if (mLeft == mRight) {
			System.out.println("数组为空,无法移除");
			return null;
		} else {
			T t = elems[mLeft];
			elems[mLeft++] = null;
			return t;
		}
	}

	/**
	 * 删除mLeft和mRight之间的元素,从0开始
	 * 
	 * @param p
	 */
	public void delete(int p) {
		p = p + mLeft;
		if (p >= mRight) {
			System.out.println("无效的索引值,无法进行删除");
		} else {
			for (int i = p; i > mLeft; i--) {
				elems[i] = elems[i - 1];
			}
			elems[mLeft] = null;
		}
		mLeft++;
	}

	/**
	 * 返回数组实际保存的有效个数
	 * 
	 * @return
	 */
	public int size() {

		return (mRight - mLeft);
	}

	/**
	 * 得到mLeft和mRight之间第p个元素,从0开始
	 * 
	 * @param p
	 * @return
	 */
	public T getObjectAt(int p) {
		p = p + mLeft;
		if (p >= mRight) {
			System.out.println("无效的索引值,无法进行查找");
			return null;
		} else {
			return elems[p];
		}
	}

	/**
	 * 数组是否为空
	 * 
	 * @return
	 */
	public boolean isEmpty() {
		return (mRight <= mLeft);
	}
}

        实体Bean类

package com.wly.filedownloader;

import net.tsz.afinal.annotation.sqlite.Id;
import net.tsz.afinal.annotation.sqlite.Table;

/**
 * 下载任务实体类
 * @author wly
 *
 */
@Table(name="Bean")
public class Bean {
	@Id
	private int id; //数据表查询主键
	private String url;
	private String title;
	
	private int isEnabled; //对应percent=(0,100),0表示false,1表示true
	private int isFinished; //对应percent=100,0表示false,1表示true
	
	private int percent; //表示当前下载任务的进度,需要持久化
	
	private long completeSize;
	
	private String guid; //下载实体类唯一性标识id
	
	/**
	 * 默认构造函数,必须有
	 */
	public Bean() {
		
	}
	
	/**
	 * 新建任务,没有状态和进度,默认isEnabled、isFinished都为false
	 */
	public Bean(String url,String title) {
		this.url = url;
		this.title = title;
		this.percent = 0;
		this.completeSize = 0;
		this.isEnabled = Conf.FALSE;
		this.isFinished = Conf.FALSE;
	}

	/**
	 * 从本地持久化处新建对象,包含状态和进度
	 * @param url
	 * @param title
	 * @param state 0初始状态,1下载中,2暂停中
	 * @param percent
	 */
	public Bean(String guid,String url, String title, int state, int percent) {
		super();
		this.guid = guid;
		this.url = url;
		this.title = title;
		this.percent = percent;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public int getPercent() {
		return percent;
	}

	public void setPercent(int percent) {
		this.percent = percent;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public long getCompleteSize() {
		return completeSize;
	}

	public void setCompleteSize(long completeSize) {
		this.completeSize = completeSize;
	}

	public String getGuid() {
		return guid;
	}

	public void setGuid(String guid) {
		this.guid = guid;
	}

	public int getIsEnabled() {
		return isEnabled;
	}

	public void setIsEnabled(int isEnabled) {
		this.isEnabled = isEnabled;
	}

	public int getIsFinished() {
		return isFinished;
	}

	public void setIsFinished(int isFinished) {
		this.isFinished = isFinished;
	}
}

      最后,配置参数类Conf

package com.wly.filedownloader;

import java.io.File;

import android.content.Context;
import android.os.Environment;

/**
 * 看出配置类
 * @author wly
 *
 */
public class Conf {

	public final static int NET_TIMEOUT_READ  = 5000; //读取超时
	public final static int NET_TIMEOUT_CONNECT = 20000; //连接超时
	
	public final static int State_DOWNLOAD = 1; //下载中状态
	public final static int State_PAUSE = 2; //暂停状态
	public final static int State_FINISH = 3;//完成状态
	public final static int State_CANCEL = 4; //DownloadBean被取消
	public final static int State_WAITTASK = 5; //等待下载任务状态,可能刚下载完一个任务,可能还没还是下载任务
	
	public final static int State_CANCELING = 7; //任务取消中
	
	public final static int State_FILEERROR = 6; //文件异常,文件版本不连续
	
	public final static int MSG_STATECHANGED = 9;//表示列表状态发生概念,通知Adapter刷新界面
	
	public final static int RETRY_TIMES = 3; //网络请求重试次数
	
	public final static int FALSE = 0;
	public final static int TRUE = 1;
	
	/**
	 * 得到本地文件保存路径
	 * @return
	 */
	public static String getSaveDir(Context context) {
		
		String fileDir;
		
		if (android.os.Environment.getExternalStorageState().equals(
				android.os.Environment.MEDIA_MOUNTED)) { //判断SD卡是否存在
			File f = context.getExternalCacheDir();
			if (null == f) {
				fileDir = Environment.getExternalStorageDirectory().getPath()
						+ File.separator + context.getPackageName()
						+ File.separator + "cache";
			} else {
				fileDir = f.getPath();
			}
		} else {
			File f = context.getCacheDir();
			fileDir = f.getPath();
		}
		return fileDir;
	}
}

       几个实现过程中需要注意的地方

       1、列表的进度反馈,因为列表需要支持拖动的同时刷新进度,但是如果简单的使用BaseAdapter.notifyDataSetChanged()来刷新的话,会出现两个问题: 一、列表拖动过程中有卡顿现象;二、列表上的按钮将变得难以响应单击事件。那么怎么解决这个问题呢?可以这么思考,如果不使用notifyDataSetChanged来刷新整张列表,而是直接取得列表项上的组件的引用,然后直接修改组件属性的话,就不会出现上述两个问题了。

       解决:Google了很多,讲的多少使用ListView.getFirstVisiablePosition和getChildAt()配合使用,我也试了下,发现在拖动列表时会出现显示错乱的情况(主要问题是getChildAt得到的View并不是期望的View)。于是只能再自己想办法了,还好咱够聪明,难不倒咱。这里在getView中使用当前的为每个contentView分配一个id:

	public View getView(final int position, View convertView, ViewGroup parent) {
			.......
                     .......		
			convertView.setId(position);
			return convertView;
		}

       然后在需要刷新时根据id得到对应的列表项convertView对象,在取得其中的ProgressBar并为其设置进度即可。

//刷新可见区域列表进度值
				int firstV = lv.getFirstVisiblePosition();
				int lastV = lv.getLastVisiblePosition();
				for (int i = firstV; i <= lastV; i++)  {
					
					View cell = lv.findViewById(i);
					cell.getId();
					if(cell != null) {
						Bean bean = workArray.getObjectAt(i);
						if(bean != null) {
							((ProgressBar)cell
									.findViewById(R.id.download_progress))
									.setProgress(bean.getPercent());
						}
					}
				}

       2、下载器队列的安排,因为有这样特定的需求,所以需要手动管理"等待中"队列和"下载中"队列。

       3、下载模块的实现,可能不是很难,不过还是有一些需要注意的地方的。

 

       工程下载地址: http://download.csdn.net/detail/u011638883/6803369

       O啦~~~

       虽然东西不是很难,但还是花了笔者不少时间,希望能给有需要的朋友一点参考。

       谢谢!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值