实现了一下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啦~~~
虽然东西不是很难,但还是花了笔者不少时间,希望能给有需要的朋友一点参考。
谢谢!!