赞
踩
实现了一下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啦~~~
虽然东西不是很难,但还是花了笔者不少时间,希望能给有需要的朋友一点参考。
谢谢!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。