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;//专门负责刷新界面的Handlerprivate Handler refreshHandler = new Handler() {@Overridepublic 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;}}};@Overrideprotected 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);}@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic void onClick(View v) {downloader.pause();}});resumeBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic void onClick(View v) {downloader.cancel();}});//等待中"取消"按钮waitCancelBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic 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;}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic Object getItem(int position) {if(position < workArray.size()) {return workArray.getObjectAt(position);} else {return waitArray.getObjectAt(position-workArray.size());}}@Overridepublic 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; // 最大同时进行任务数量,默认是1private Context mContext;/** * 下载器队列 */private Downloader[] downladerArray;private Handler mHandler; // Activity中的Handlerprivate long lastRefreshTime = 0;private int refresh_time = 0; // 每次刷新进度的最少间隔时间// 做一层过滤,控制界面刷新频率private Handler handler = new Handler() {@Overridepublic 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() {@Overridepublic void finished(Bean bean) {// 1.尝试新的任务给空闲的下载器refreshDownloaderArray();// 2.刷新界面Message msg = new Message();msg.what = Conf.State_FINISH;mHandler.sendMessage(msg);System.out.println("--finished");}@Overridepublic void started() {Message msg = new Message();msg.what = Conf.MSG_STATECHANGED;mHandler.sendMessage(msg);}@Overridepublic void paused() {Message msg = new Message();msg.what = Conf.MSG_STATECHANGED;mHandler.sendMessage(msg);}@Overridepublic void resumed() {Message msg = new Message();msg.what = Conf.MSG_STATECHANGED;mHandler.sendMessage(msg);}@Overridepublic void canceled(String guid) {// 发送消息给ActivityMessage msg = new Message();msg.what = Conf.State_CANCEL;msg.obj = guid;mHandler.sendMessage(msg);}/** * 准备就绪(主要是文件校验过程),可以开始下载了, */@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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;}@Overridepublic 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 {@Idprivate int id; //数据表查询主键private String url;private String title;private int isEnabled; //对应percent=(0,100),0表示false,1表示trueprivate int isFinished; //对应percent=100,0表示false,1表示trueprivate 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啦~~~
虽然东西不是很难,但还是花了笔者不少时间,希望能给有需要的朋友一点参考。
谢谢!!
更多相关文章
- Android状态栏微技巧,动态控制状态栏显示和隐藏
- android官方最新以及2.2、2.3.5源代码(完整)下载以及用source insi
- Android样式的开发:selector篇
- 我的Android进阶之旅------>Android基于HTTP协议的多线程断点下
- Android(安卓)网络状态监听那些事
- Android(安卓)启动服务配合AsyncTask 使用OKHttp 实现断点下载大
- Android关于Activity知识点总结(一)生命周期与状态及状态保存
- android线程相关2
- Android用户界面之notifaction(状态栏通知)