当前位置:   article > 正文

Android中多线程下载列表实现_android 下载列表

android 下载列表

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

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

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

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

       主要类介绍

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

  1. package com.wly.filedownloader;
  2. import java.util.List;
  3. import com.wly.filedownloader.dao.BeanHelper;
  4. import net.tsz.afinal.FinalDb;
  5. import net.tsz.afinal.exception.AfinalException;
  6. import android.os.Bundle;
  7. import android.os.Handler;
  8. import android.os.Message;
  9. import android.app.Activity;
  10. import android.app.AlertDialog;
  11. import android.content.DialogInterface;
  12. import android.graphics.Bitmap.Config;
  13. import android.view.LayoutInflater;
  14. import android.view.Menu;
  15. import android.view.View;
  16. import android.view.View.OnClickListener;
  17. import android.view.ViewGroup;
  18. import android.widget.BaseAdapter;
  19. import android.widget.Button;
  20. import android.widget.ListAdapter;
  21. import android.widget.ListView;
  22. import android.widget.ProgressBar;
  23. import android.widget.RelativeLayout;
  24. import android.widget.TextView;
  25. public class MainActivity extends Activity{
  26. DownloadHelper downHelper;
  27. private LayoutInflater inflater;
  28. private ListView lv;
  29. //最大支持同时进行的线程数
  30. private int maxThread = 2;
  31. //测试下载任务数量
  32. private int taskSize = 5;
  33. private DynamicArray<Bean> waitArray; //等待列表
  34. private DynamicArray<Bean> workArray; //用来存放已完成及下载中的任务
  35. List<Bean> list;
  36. //专门负责刷新界面的Handler
  37. private Handler refreshHandler = new Handler() {
  38. @Override
  39. public void handleMessage(Message msg) {
  40. super.handleMessage(msg);
  41. switch(msg.what) {
  42. case Conf.State_FINISH:
  43. myAdapter.notifyDataSetChanged();
  44. break;
  45. case Conf.State_CANCEL:
  46. for(int i=0;i<workArray.size();i++) {
  47. if(workArray.getObjectAt(i).getGuid().equals(msg.obj)) {
  48. workArray.delete(i);
  49. }
  50. }
  51. downHelper.refreshDownloaderArray();
  52. myAdapter.notifyDataSetChanged();
  53. break;
  54. case Conf.MSG_STATECHANGED:
  55. myAdapter.notifyDataSetChanged();
  56. break;
  57. case Conf.State_DOWNLOAD:
  58. //刷新可见区域列表进度值
  59. int firstV = lv.getFirstVisiblePosition();
  60. int lastV = lv.getLastVisiblePosition();
  61. for (int i = firstV; i <= lastV; i++) {
  62. View cell = lv.findViewById(i);
  63. cell.getId();
  64. if(cell != null) {
  65. Bean bean = workArray.getObjectAt(i);
  66. if(bean != null) {
  67. ((ProgressBar)cell
  68. .findViewById(R.id.download_progress))
  69. .setProgress(bean.getPercent());
  70. }
  71. }
  72. }
  73. break;
  74. case Conf.State_FILEERROR:
  75. //文件版本异常
  76. break;
  77. }
  78. }
  79. };
  80. @Override
  81. protected void onCreate(Bundle savedInstanceState) {
  82. super.onCreate(savedInstanceState);
  83. setContentView(R.layout.activity_main);
  84. lv = (ListView) findViewById(R.id.listView1);
  85. Bean[] beanArray = new Bean[taskSize];
  86. workArray = new DynamicArray<Bean>();
  87. waitArray = new DynamicArray<Bean>();
  88. for(int i=0;i<beanArray.length;i++) {
  89. beanArray[i] = new Bean("AA_" + i,"http://10.0.2.2:8080/" + i + ".mp3", i + ".mp3"
  90. ,Conf.State_WAITTASK,0);
  91. }
  92. //1.添加测试数据到本地数据库
  93. List<Bean> list = FinalDb.create(this).findAll(Bean.class);
  94. if(list.size() == 0) {
  95. for(int i=0;i<beanArray.length;i++) {
  96. FinalDb.create(this).save(beanArray[i]);
  97. }
  98. list = FinalDb.create(this).findAll(Bean.class);
  99. }
  100. for(Bean bean:list) {
  101. System.out.println("id:" + bean.getId() + "," + "title:" + bean.getTitle()
  102. + "," + "url:" + bean.getUrl() + "," + "isEnabled:" + bean.getIsEnabled()
  103. + ",isFinished:" + bean.getIsFinished()
  104. + "," + "cZise:" + bean.getCompleteSize() + ",percent:" + bean.getPercent());
  105. }
  106. for(Bean bean:list) {
  107. waitArray.insert(bean);
  108. }
  109. downHelper = new DownloadHelper(this,maxThread,waitArray,workArray,refreshHandler);
  110. downHelper.assignTaskToDownloaders();
  111. inflater = LayoutInflater.from(this);
  112. lv.setAdapter(myAdapter);
  113. }
  114. @Override
  115. protected void onDestroy() {
  116. super.onDestroy();
  117. //停止所有下载器任务
  118. downHelper.kill();
  119. }
  120. class ViewHolder {
  121. ProgressBar progressBar;
  122. Button pauseBtn,cancelBtn,watingBtn,resumeBtn;
  123. RelativeLayout level_1,level_2,level_3;
  124. TextView title;
  125. }
  126. ViewHolder holder;
  127. BaseAdapter myAdapter = new BaseAdapter() {
  128. @Override
  129. public View getView(final int position, View convertView, ViewGroup parent) {
  130. final Bean bean;
  131. //先显示"完成列表",再显示"下载中及等待列表"
  132. if(position < workArray.size()) {
  133. bean = workArray.getObjectAt(position);
  134. } else {
  135. bean = waitArray.getObjectAt(position-workArray.size());
  136. }
  137. final Downloader downloader = downHelper.getDownloaderByGuid(bean.getGuid());
  138. convertView = inflater.inflate(R.layout.download_list_item, null);
  139. ProgressBar progressBar = (ProgressBar) convertView.findViewById(R.id.download_progress);
  140. final Button pauseBtn = (Button)convertView.findViewById(R.id.download_btn_pause);
  141. Button cancelBtn = (Button)convertView.findViewById(R.id.download_btn_cancel);
  142. Button resumeBtn = (Button)convertView.findViewById(R.id.download_btn_resume);
  143. Button waitCancelBtn = (Button)convertView.findViewById(R.id.wait_btn_cancel);
  144. RelativeLayout download_level = (RelativeLayout)convertView.findViewById(R.id.download_level);
  145. RelativeLayout waiting_level = (RelativeLayout)convertView.findViewById(R.id.waiting_level);
  146. RelativeLayout finish_level = (RelativeLayout)convertView.findViewById(R.id.finish_level);
  147. TextView title = (TextView)convertView.findViewById(R.id.download_title);
  148. //根据Bean的状态来决定暂停、继续、取消等按钮的显示状态
  149. progressBar.setProgress((int)bean.getPercent());
  150. title.setText(bean.getTitle());
  151. final String guid = bean.getGuid();
  152. if(bean.getIsEnabled() == Conf.TRUE) { //对应下载进度(0,100) => 暂停/继续
  153. download_level.setVisibility(View.VISIBLE);
  154. waiting_level.setVisibility(View.INVISIBLE);
  155. finish_level.setVisibility(View.INVISIBLE);
  156. if(downloader.getstate() == Conf.State_DOWNLOAD) { //下载中
  157. resumeBtn.setVisibility(View.INVISIBLE);
  158. pauseBtn.setVisibility(View.VISIBLE);
  159. } else { //暂停中
  160. resumeBtn.setVisibility(View.VISIBLE);
  161. pauseBtn.setVisibility(View.INVISIBLE);
  162. resumeBtn.setText("继续");
  163. }
  164. } else if(bean.getIsFinished() == Conf.TRUE) { //对应下载进度100 => 完成
  165. download_level.setVisibility(View.INVISIBLE);
  166. waiting_level.setVisibility(View.INVISIBLE);
  167. finish_level.setVisibility(View.VISIBLE);
  168. } else { //对应下载进度0 => 开始/等待
  169. //检查是否处于等待状态
  170. System.out.println("--开始/等待--");
  171. if(downloader != null) {
  172. download_level.setVisibility(View.VISIBLE);
  173. waiting_level.setVisibility(View.INVISIBLE);
  174. finish_level.setVisibility(View.INVISIBLE);
  175. resumeBtn.setText("开始");
  176. } else {
  177. download_level.setVisibility(View.INVISIBLE);
  178. waiting_level.setVisibility(View.VISIBLE);
  179. finish_level.setVisibility(View.INVISIBLE);
  180. }
  181. }
  182. pauseBtn.setOnClickListener(new View.OnClickListener() {
  183. @Override
  184. public void onClick(View v) {
  185. downloader.pause();
  186. }
  187. });
  188. resumeBtn.setOnClickListener(new View.OnClickListener() {
  189. @Override
  190. public void onClick(View v) {
  191. if(downloader != null && downloader.isActive()) {
  192. downloader.recovery();
  193. System.out.println("--recovery--");
  194. } else {
  195. downloader.start();
  196. System.out.println("--start--");
  197. }
  198. }
  199. });
  200. //下载中"取消"按钮
  201. cancelBtn.setOnClickListener(new View.OnClickListener() {
  202. @Override
  203. public void onClick(View v) {
  204. downloader.cancel();
  205. }
  206. });
  207. //等待中"取消"按钮
  208. waitCancelBtn.setOnClickListener(new View.OnClickListener() {
  209. @Override
  210. public void onClick(View v) {
  211. if(position < workArray.size()) {
  212. downloader.cancel();
  213. } else {
  214. waitArray.delete(position-workArray.size());
  215. BeanHelper.delete(MainActivity.this,bean);
  216. myAdapter.notifyDataSetChanged();
  217. }
  218. }
  219. });
  220. convertView.setId(position);
  221. return convertView;
  222. }
  223. @Override
  224. public long getItemId(int position) {
  225. return 0;
  226. }
  227. @Override
  228. public Object getItem(int position) {
  229. if(position < workArray.size()) {
  230. return workArray.getObjectAt(position);
  231. } else {
  232. return waitArray.getObjectAt(position-workArray.size());
  233. }
  234. }
  235. @Override
  236. public int getCount() {
  237. return waitArray.size() + workArray.size();
  238. }
  239. };
  240. }

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

  1. package com.wly.filedownloader;
  2. import com.wly.filedownloader.Downloader.MyDownloadListener;
  3. import android.app.AlertDialog;
  4. import android.content.Context;
  5. import android.content.DialogInterface;
  6. import android.os.Handler;
  7. import android.os.Message;
  8. /**
  9. * 本类中包含两个队列一个是等待中的实体Bean,另一个是下载中的任务队列, 当添加一个任务实体Bean时,会先检查是否有空余的下载器没有使用,如果有,就
  10. * 使用refreshDownloaderArray将该任务Bean从"等待下载队列"移动到"下载中队列"
  11. * 同样的,当一个任务下载成功后,先从"下载中队列"移除,然后得到一个空闲的下载器,最后调用插入流程,将一个新的任务Bean 添加到到"下载中队列"中去
  12. * 最后是删除(取消),如果发生在"等待"队列则删除数据即可,如果发生在"下载中"队列则复用下载完成逻辑即可。
  13. *
  14. * @author wly
  15. *
  16. */
  17. public class DownloadHelper {
  18. private DynamicArray<Bean> waitArray;
  19. private DynamicArray<Bean> workArray;
  20. private int maxThread; // 最大同时进行任务数量,默认是1
  21. private Context mContext;
  22. /**
  23. * 下载器队列
  24. */
  25. private Downloader[] downladerArray;
  26. private Handler mHandler; // Activity中的Handler
  27. private long lastRefreshTime = 0;
  28. private int refresh_time = 0; // 每次刷新进度的最少间隔时间
  29. // 做一层过滤,控制界面刷新频率
  30. private Handler handler = new Handler() {
  31. @Override
  32. public void handleMessage(Message msg) {
  33. super.handleMessage(msg);
  34. if (msg.what == Conf.State_FILEERROR) {
  35. System.out.println("--文件异常,发送重置对应下载任务界面显示信息!!!--");
  36. } else {
  37. // 每隔refresh_time时间发送一次刷新界面信息
  38. if ((System.currentTimeMillis() - lastRefreshTime) > refresh_time) {
  39. mHandler.sendEmptyMessage(Conf.State_DOWNLOAD);
  40. lastRefreshTime = System.currentTimeMillis();
  41. }
  42. }
  43. }
  44. };
  45. private MyDownloadListener myDownloadListener = new MyDownloadListener() {
  46. @Override
  47. public void finished(Bean bean) {
  48. // 1.尝试新的任务给空闲的下载器
  49. refreshDownloaderArray();
  50. // 2.刷新界面
  51. Message msg = new Message();
  52. msg.what = Conf.State_FINISH;
  53. mHandler.sendMessage(msg);
  54. System.out.println("--finished");
  55. }
  56. @Override
  57. public void started() {
  58. Message msg = new Message();
  59. msg.what = Conf.MSG_STATECHANGED;
  60. mHandler.sendMessage(msg);
  61. }
  62. @Override
  63. public void paused() {
  64. Message msg = new Message();
  65. msg.what = Conf.MSG_STATECHANGED;
  66. mHandler.sendMessage(msg);
  67. }
  68. @Override
  69. public void resumed() {
  70. Message msg = new Message();
  71. msg.what = Conf.MSG_STATECHANGED;
  72. mHandler.sendMessage(msg);
  73. }
  74. @Override
  75. public void canceled(String guid) {
  76. // 发送消息给Activity
  77. Message msg = new Message();
  78. msg.what = Conf.State_CANCEL;
  79. msg.obj = guid;
  80. mHandler.sendMessage(msg);
  81. }
  82. /**
  83. * 准备就绪(主要是文件校验过程),可以开始下载了,
  84. */
  85. @Override
  86. public void ready(String guid) {
  87. System.out.println("--准备就绪,可以开始下载了--");
  88. }
  89. };
  90. /**
  91. * 创建实体Bean的队列
  92. *
  93. * @param context
  94. * @param maxThread
  95. * @param beans
  96. * @param handler
  97. */
  98. public DownloadHelper(Context context, int maxThread,
  99. DynamicArray<Bean> dArray, DynamicArray<Bean> workArray,
  100. Handler handler) {
  101. this.maxThread = maxThread;
  102. this.mContext = context;
  103. this.waitArray = dArray;
  104. this.workArray = workArray;
  105. this.mHandler = handler;
  106. downladerArray = new Downloader[maxThread];
  107. }
  108. /**
  109. * 添加任务到队列
  110. *
  111. * @param bean
  112. */
  113. public void insert(Bean bean) {
  114. waitArray.insert(bean);
  115. refreshDownloaderArray();
  116. }
  117. /**
  118. * 初始启动Activity时,为下载器数组中的下载器分配下载任务
  119. */
  120. public void assignTaskToDownloaders() {
  121. int i = 0;
  122. while (i < maxThread && !waitArray.isEmpty()) {
  123. // 为下载器分配下载任务
  124. Bean bean = waitArray.poll();
  125. workArray.insert(bean); // 不管任务完成与否都插入到"工作队列"中去
  126. if (bean.getIsFinished() == Conf.FALSE) { // 只为未完成的任务分配下载器
  127. downladerArray[i] = new Downloader(mContext);
  128. downladerArray[i].assignTask(bean, myDownloadListener, handler);
  129. i++;
  130. }
  131. }
  132. }
  133. /**
  134. * 取消整个队列任务
  135. */
  136. public void kill() {
  137. for (Downloader d : downladerArray) {
  138. if (d != null) {
  139. d.kill();
  140. }
  141. }
  142. }
  143. /**
  144. * 检查下载器队列的状态,负责刷新下载器中的任务。通常在新增、更新、删除任务后调用
  145. */
  146. public void refreshDownloaderArray() {
  147. for (int i = 0; i < maxThread; i++) {
  148. if (downladerArray[i] != null && downladerArray[i].isIdle()) {
  149. if (!waitArray.isEmpty()) {
  150. Bean bean = waitArray.poll();
  151. workArray.insert(bean);
  152. // 为下载器分配下载任务
  153. downladerArray[i].assignTask(bean, myDownloadListener,
  154. handler);
  155. // 开始下载
  156. downladerArray[i].recovery();
  157. } else {
  158. downladerArray[i].kill();
  159. }
  160. }
  161. }
  162. }
  163. /**
  164. * 根据guid查询得到其对应的实体bean对象
  165. *
  166. * @param guid
  167. * @return
  168. */
  169. public Downloader getDownloaderByGuid(String guid) {
  170. for (Downloader d : downladerArray) {
  171. if (d != null && d.getBean().getGuid().equals(guid)) {
  172. return d;
  173. }
  174. }
  175. return null;
  176. }
  177. // /**
  178. // * 检查网络环境
  179. // */
  180. // public boolean isNetAvailable() {
  181. // return true;
  182. // }
  183. }

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

  1. package com.wly.filedownloader;
  2. import java.io.File;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.RandomAccessFile;
  7. import java.net.HttpURLConnection;
  8. import java.net.MalformedURLException;
  9. import java.net.ProtocolException;
  10. import java.net.URL;
  11. import com.wly.filedownloader.dao.BeanHelper;
  12. import android.app.AlertDialog;
  13. import android.content.Context;
  14. import android.content.DialogInterface;
  15. import android.os.Handler;
  16. import android.os.Message;
  17. /**
  18. * 下载器
  19. * @author wly
  20. *
  21. */
  22. public class Downloader extends Thread {
  23. private Bean mBean;
  24. private MyDownloadListener mListener;
  25. //用于表征当前下载器是否处于空闲状态,如果是的话,可以为其分配新的下载任务,进入空闲状态只有两种方法:"下载完成"和"取消下载"
  26. private boolean isIdle;
  27. private boolean isAlive = false; //表示当前下载器是否已经激活,默认未激活
  28. private Handler handler;
  29. private Context mContext;
  30. private int beanPercent;
  31. private int state;
  32. /**
  33. * 创建对象
  34. * @param context
  35. */
  36. public Downloader(Context context) {
  37. isIdle = true;
  38. this.mContext = context;
  39. state = Conf.State_WAITTASK;
  40. }
  41. public boolean isPause() {
  42. return (state == Conf.State_PAUSE);
  43. }
  44. public boolean isRun() {
  45. return (state == Conf.State_DOWNLOAD);
  46. }
  47. /**
  48. * 返回当前下载器的激活状态
  49. * @return
  50. */
  51. public boolean isActive() {
  52. return isAlive;
  53. }
  54. /**
  55. * 等到当前下载器状态
  56. * @return
  57. */
  58. public int getstate() {
  59. return state;
  60. }
  61. /**
  62. * 分配任务
  63. * @param id
  64. * @param bean
  65. * @param listener
  66. * @param handler
  67. */
  68. public void assignTask(Bean bean,MyDownloadListener listener,Handler handler) {
  69. this.mBean = bean;
  70. this.mListener = listener;
  71. this.handler = handler;
  72. isIdle = false;
  73. }
  74. public Bean getBean() {
  75. return this.mBean;
  76. }
  77. /**
  78. * 取消下载任务
  79. */
  80. public void cancel() {
  81. //先暂停当前任务
  82. if(!isPause()) {
  83. pause();
  84. }
  85. AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
  86. dialog.setTitle("提示")
  87. .setMessage("确定要取消当前选中的任务吗?")
  88. .setPositiveButton("是的", new DialogInterface.OnClickListener() {
  89. @Override
  90. public void onClick(DialogInterface dialog, int which) {
  91. if(state == Conf.State_WAITTASK || state == Conf.State_PAUSE) {
  92. isIdle = true;
  93. BeanHelper.delete(mContext, mBean);
  94. mListener.canceled(mBean.getGuid());
  95. mBean.setPercent(0);
  96. state = Conf.State_WAITTASK;
  97. } else {
  98. state = Conf.State_CANCELING;
  99. }
  100. }
  101. }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
  102. @Override
  103. public void onClick(DialogInterface dialog, int which) {
  104. recovery(); //恢复下载
  105. }
  106. }).show();
  107. }
  108. /**
  109. * 暂停任务
  110. */
  111. public void pause() {
  112. state = Conf.State_PAUSE;
  113. mListener.paused();
  114. }
  115. public void recovery() {
  116. state = Conf.State_DOWNLOAD;
  117. mListener.resumed();
  118. interrupt();
  119. }
  120. public boolean isPaused() {
  121. return state == Conf.State_PAUSE;
  122. }
  123. /**
  124. * 取消当前任务
  125. */
  126. public void kill() {
  127. System.out.println("Kill the downloader with guid:" + mBean.getGuid());
  128. state = Conf.State_WAITTASK;
  129. isAlive = false;
  130. }
  131. public boolean isIdle() {
  132. return isIdle;
  133. }
  134. @Override
  135. public void run() {
  136. synchronized (this){
  137. long fileSize = 0;
  138. RandomAccessFile randomAccessFile = null;
  139. long completedSize = 0;
  140. isAlive = true;
  141. isIdle = false;
  142. state = Conf.State_DOWNLOAD;
  143. InputStream is = null;
  144. HttpURLConnection conn = null;
  145. File file = null;
  146. mBean.setIsEnabled(Conf.TRUE);
  147. while(isAlive) {
  148. //如果有可下载任务,就进行下载
  149. if(state == Conf.State_DOWNLOAD) {
  150. //1.读取当前下载任务信息,如果是断点继续下载,则进行文件连续性判断
  151. beanPercent = mBean.getPercent();
  152. completedSize = mBean.getCompleteSize();
  153. //检查本地文件是否存在
  154. File pathFile = new File(Conf.getSaveDir(mContext));
  155. if (!pathFile.exists()) {
  156. pathFile.mkdirs();
  157. }
  158. file = new File(Conf.getSaveDir(mContext)
  159. + File.separator + mBean.getTitle());
  160. System.out.println("新任务:" + mBean.getTitle());
  161. if(!file.exists()) {
  162. if(mBean.getCompleteSize() == 0) {//新任务
  163. try {
  164. file.createNewFile();
  165. } catch (IOException e) {
  166. e.printStackTrace();
  167. }
  168. } else { //暂停任务,但是文件不存在,则重置下载任务
  169. mBean = new Bean(mBean.getGuid(), mBean.getUrl(), mBean.getTitle(), Conf.State_WAITTASK, 0);
  170. BeanHelper.update(mContext, mBean);
  171. beanPercent = mBean.getPercent();
  172. completedSize = mBean.getCompleteSize();
  173. Message msg = new Message();
  174. msg.what = Conf.State_FILEERROR;
  175. handler.sendMessage(msg);
  176. }
  177. }
  178. //2.与服务器检验文件的统一性
  179. //verifyFileWithServer();
  180. //从当前进度点开始数据下载
  181. URL url;
  182. try {
  183. url = new URL(mBean.getUrl());
  184. conn = (HttpURLConnection) url.openConnection();
  185. conn.setDoInput(true);
  186. conn.setDoOutput(true);
  187. conn.setConnectTimeout(Conf.NET_TIMEOUT_CONNECT);
  188. conn.setReadTimeout(Conf.NET_TIMEOUT_READ);
  189. conn.setRequestMethod("GET");
  190. fileSize = conn.getContentLength(); //得到文件尺寸
  191. try {
  192. randomAccessFile = new RandomAccessFile(file, "rwd");
  193. randomAccessFile.setLength(mBean.getCompleteSize());
  194. beanPercent = (int)((double)(completedSize) / fileSize * 100);
  195. randomAccessFile.seek((long)beanPercent);
  196. } catch (FileNotFoundException e) {
  197. e.printStackTrace();
  198. //重置下载任务
  199. } catch (IOException e) {
  200. e.printStackTrace();
  201. //重置下载任务
  202. }
  203. //准备就绪
  204. mListener.started();
  205. mBean.setIsEnabled(Conf.TRUE);
  206. mListener.ready(mBean.getGuid());
  207. } catch (MalformedURLException e1) {
  208. e1.printStackTrace();
  209. } catch (ProtocolException e) {
  210. e.printStackTrace();
  211. } catch (IOException e) {
  212. e.printStackTrace();
  213. }
  214. //开始下载
  215. try {
  216. is = conn.getInputStream();
  217. int length = -1;
  218. byte[] buffer = new byte[2048];
  219. while(state == Conf.State_DOWNLOAD &&
  220. (length=is.read(buffer)) != -1) {
  221. randomAccessFile.write(buffer, 0, length);
  222. completedSize += length;
  223. beanPercent = (int)((double)(completedSize) / fileSize * 100);
  224. mBean.setPercent(beanPercent);
  225. Message msg = new Message();
  226. handler.sendMessage(msg);
  227. System.out.println("percent:" + beanPercent);
  228. //当前任务已完成,进入"空闲"状态
  229. if(beanPercent == 100) {
  230. System.out.println("--一个下载任务完成--");
  231. mBean.setIsFinished(Conf.TRUE);
  232. mBean.setIsEnabled(Conf.FALSE);
  233. //1.数据持久化动作
  234. mBean.setPercent(beanPercent);
  235. mBean.setCompleteSize(completedSize);
  236. BeanHelper.update(mContext, mBean);
  237. System.out.println("---更新状态完毕---");
  238. //2.下载完毕,发送通知,刷新界面,等待新任务
  239. //告知下载队列,请求新任务
  240. isIdle = true;
  241. state = Conf.State_WAITTASK;
  242. mListener.finished(mBean);
  243. beanPercent = 0;
  244. completedSize = 0;
  245. // try {
  246. // sleep(1000);
  247. // System.out.println("--等待分配新的任务--");
  248. // } catch (InterruptedException e) {
  249. // e.printStackTrace();
  250. // }
  251. break;
  252. }
  253. }
  254. } catch (IOException e1) {
  255. e1.printStackTrace();
  256. }
  257. }
  258. if(state == Conf.State_PAUSE) {
  259. //1.断开连接
  260. if(conn != null) {
  261. try {
  262. is.close();
  263. conn.disconnect();
  264. } catch (IOException e) {
  265. e.printStackTrace();
  266. }
  267. System.out.println("---断开连接----");
  268. //2.进行数据持久化
  269. mBean.setIsEnabled(Conf.TRUE);
  270. mBean.setPercent(beanPercent);
  271. mBean.setCompleteSize(completedSize);
  272. BeanHelper.update(mContext, mBean);
  273. System.out.println("---更新状态---");
  274. }
  275. }
  276. if(state == Conf.State_CANCELING) {
  277. //1.删除数据库数据
  278. BeanHelper.delete(mContext, mBean);
  279. //2.刷新界面
  280. isIdle = true;
  281. mListener.canceled(mBean.getGuid());
  282. state = Conf.State_WAITTASK;
  283. }
  284. // try {
  285. // System.out.println("下载器" + mBean.getGuid() + "正处于暂停/空闲状态");
  286. // sleep(1000);
  287. // } catch (InterruptedException e) {
  288. // e.printStackTrace();
  289. // }
  290. try {
  291. wait();
  292. } catch (InterruptedException e) {
  293. e.printStackTrace();
  294. }
  295. }
  296. System.out.println("This Downloader" + mBean.getGuid() + " has been killed!!!");
  297. }
  298. }
  299. /**
  300. * 从文件/网路路径中解析出文件名
  301. * @param filePath
  302. * @return
  303. */
  304. public String getFileNameFromPath(String filePath) {
  305. String[] ss = filePath.split("//");
  306. return ss[ss.length-1];
  307. }
  308. /**
  309. * 下载过程的生命周期回调接口,用于刷新界面。注意:界面刷新是在数据更改之后的
  310. * @author wly
  311. *
  312. */
  313. public interface MyDownloadListener {
  314. /**
  315. * 下载完成,发送通知Helper,Helper再通过Handler通知Activity
  316. * @param downloaderID 下载器在下载器数组中的位置索引
  317. */
  318. public void finished(Bean bean);
  319. /**
  320. * 已经开始下载,下载中,本方法的调用在ready()之后
  321. * @param id
  322. */
  323. public void started();
  324. /**
  325. * 已经暂停下载
  326. * @param id
  327. */
  328. public void paused();
  329. /**
  330. * 已经恢复下载
  331. * @param id
  332. */
  333. public void resumed();
  334. /**
  335. * 已经取消下载
  336. * @param guid
  337. */
  338. public void canceled(String guid);
  339. /**
  340. * 已经准备就绪(文件校验,断点读取等),可以开始下载
  341. * @param guid
  342. */
  343. public void ready(String guid);
  344. }
  345. }

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

  1. package com.wly.filedownloader;
  2. /**
  3. * 动态数组,结合队列(FIFO)和数组(根据索引进行删除元素)的特性
  4. *
  5. * @author wly
  6. *
  7. */
  8. public class DynamicArray<T> {
  9. private T[] elems;
  10. private int mRight; // 右侧有内容索引值,即队列尾
  11. private int mLeft; // 左侧有内容索引值,即队列首
  12. private int INCREATE_STEP = 12;
  13. // public static void main(String[] args) {
  14. // DynamicArray<Student> array = new DynamicArray<Student>();
  15. // array.insert(new Student("A"));
  16. // array.insert(new Student("B"));
  17. // array.insert(new Student("C"));
  18. // array.insert(new Student("D"));
  19. // array.insert(new Student("E"));
  20. // array.insert(new Student("F"));
  21. //
  22. // array.poll();
  23. // array.peek();
  24. // array.delete(2);
  25. // array.getObjectAt(2);
  26. // System.out.println(array.size());
  27. //
  28. // }
  29. static class Student {
  30. String name;
  31. public Student(String name) {
  32. this.name = name;
  33. }
  34. }
  35. public DynamicArray() {
  36. elems = (T[]) new Object[INCREATE_STEP];
  37. mLeft = 0;
  38. mRight = 0;
  39. }
  40. /**
  41. * 插入一个元素到数组
  42. *
  43. * @param t
  44. */
  45. public void insert(T t) {
  46. // 扩展数组
  47. if (mRight >= elems.length) {
  48. T[] temp = (T[]) new Object[elems.length + INCREATE_STEP];
  49. for (int i = 0; i < elems.length; i++) {
  50. temp[i] = elems[i];
  51. }
  52. elems = temp;
  53. temp = null;
  54. }
  55. if (elems[mRight] == null) {
  56. elems[mRight++] = t;
  57. } else {
  58. elems[mRight++] = t;
  59. }
  60. }
  61. public T peek() {
  62. if (!isEmpty()) {
  63. return elems[mLeft];
  64. }
  65. return null;
  66. }
  67. /**
  68. * 弹出一个元素,将数组起点到p之间的元素都往右移动一位
  69. *
  70. * @return
  71. */
  72. public T poll() {
  73. if (mLeft == mRight) {
  74. System.out.println("数组为空,无法移除");
  75. return null;
  76. } else {
  77. T t = elems[mLeft];
  78. elems[mLeft++] = null;
  79. return t;
  80. }
  81. }
  82. /**
  83. * 删除mLeft和mRight之间的元素,从0开始
  84. *
  85. * @param p
  86. */
  87. public void delete(int p) {
  88. p = p + mLeft;
  89. if (p >= mRight) {
  90. System.out.println("无效的索引值,无法进行删除");
  91. } else {
  92. for (int i = p; i > mLeft; i--) {
  93. elems[i] = elems[i - 1];
  94. }
  95. elems[mLeft] = null;
  96. }
  97. mLeft++;
  98. }
  99. /**
  100. * 返回数组实际保存的有效个数
  101. *
  102. * @return
  103. */
  104. public int size() {
  105. return (mRight - mLeft);
  106. }
  107. /**
  108. * 得到mLeft和mRight之间第p个元素,从0开始
  109. *
  110. * @param p
  111. * @return
  112. */
  113. public T getObjectAt(int p) {
  114. p = p + mLeft;
  115. if (p >= mRight) {
  116. System.out.println("无效的索引值,无法进行查找");
  117. return null;
  118. } else {
  119. return elems[p];
  120. }
  121. }
  122. /**
  123. * 数组是否为空
  124. *
  125. * @return
  126. */
  127. public boolean isEmpty() {
  128. return (mRight <= mLeft);
  129. }
  130. }

        实体Bean类

  1. package com.wly.filedownloader;
  2. import net.tsz.afinal.annotation.sqlite.Id;
  3. import net.tsz.afinal.annotation.sqlite.Table;
  4. /**
  5. * 下载任务实体类
  6. * @author wly
  7. *
  8. */
  9. @Table(name="Bean")
  10. public class Bean {
  11. @Id
  12. private int id; //数据表查询主键
  13. private String url;
  14. private String title;
  15. private int isEnabled; //对应percent=(0,100),0表示false,1表示true
  16. private int isFinished; //对应percent=100,0表示false,1表示true
  17. private int percent; //表示当前下载任务的进度,需要持久化
  18. private long completeSize;
  19. private String guid; //下载实体类唯一性标识id
  20. /**
  21. * 默认构造函数,必须有
  22. */
  23. public Bean() {
  24. }
  25. /**
  26. * 新建任务,没有状态和进度,默认isEnabled、isFinished都为false
  27. */
  28. public Bean(String url,String title) {
  29. this.url = url;
  30. this.title = title;
  31. this.percent = 0;
  32. this.completeSize = 0;
  33. this.isEnabled = Conf.FALSE;
  34. this.isFinished = Conf.FALSE;
  35. }
  36. /**
  37. * 从本地持久化处新建对象,包含状态和进度
  38. * @param url
  39. * @param title
  40. * @param state 0初始状态,1下载中,2暂停中
  41. * @param percent
  42. */
  43. public Bean(String guid,String url, String title, int state, int percent) {
  44. super();
  45. this.guid = guid;
  46. this.url = url;
  47. this.title = title;
  48. this.percent = percent;
  49. }
  50. public String getUrl() {
  51. return url;
  52. }
  53. public void setUrl(String url) {
  54. this.url = url;
  55. }
  56. public String getTitle() {
  57. return title;
  58. }
  59. public void setTitle(String title) {
  60. this.title = title;
  61. }
  62. public int getPercent() {
  63. return percent;
  64. }
  65. public void setPercent(int percent) {
  66. this.percent = percent;
  67. }
  68. public int getId() {
  69. return id;
  70. }
  71. public void setId(int id) {
  72. this.id = id;
  73. }
  74. public long getCompleteSize() {
  75. return completeSize;
  76. }
  77. public void setCompleteSize(long completeSize) {
  78. this.completeSize = completeSize;
  79. }
  80. public String getGuid() {
  81. return guid;
  82. }
  83. public void setGuid(String guid) {
  84. this.guid = guid;
  85. }
  86. public int getIsEnabled() {
  87. return isEnabled;
  88. }
  89. public void setIsEnabled(int isEnabled) {
  90. this.isEnabled = isEnabled;
  91. }
  92. public int getIsFinished() {
  93. return isFinished;
  94. }
  95. public void setIsFinished(int isFinished) {
  96. this.isFinished = isFinished;
  97. }
  98. }

      最后,配置参数类Conf

  1. package com.wly.filedownloader;
  2. import java.io.File;
  3. import android.content.Context;
  4. import android.os.Environment;
  5. /**
  6. * 看出配置类
  7. * @author wly
  8. *
  9. */
  10. public class Conf {
  11. public final static int NET_TIMEOUT_READ = 5000; //读取超时
  12. public final static int NET_TIMEOUT_CONNECT = 20000; //连接超时
  13. public final static int State_DOWNLOAD = 1; //下载中状态
  14. public final static int State_PAUSE = 2; //暂停状态
  15. public final static int State_FINISH = 3;//完成状态
  16. public final static int State_CANCEL = 4; //DownloadBean被取消
  17. public final static int State_WAITTASK = 5; //等待下载任务状态,可能刚下载完一个任务,可能还没还是下载任务
  18. public final static int State_CANCELING = 7; //任务取消中
  19. public final static int State_FILEERROR = 6; //文件异常,文件版本不连续
  20. public final static int MSG_STATECHANGED = 9;//表示列表状态发生概念,通知Adapter刷新界面
  21. public final static int RETRY_TIMES = 3; //网络请求重试次数
  22. public final static int FALSE = 0;
  23. public final static int TRUE = 1;
  24. /**
  25. * 得到本地文件保存路径
  26. * @return
  27. */
  28. public static String getSaveDir(Context context) {
  29. String fileDir;
  30. if (android.os.Environment.getExternalStorageState().equals(
  31. android.os.Environment.MEDIA_MOUNTED)) { //判断SD卡是否存在
  32. File f = context.getExternalCacheDir();
  33. if (null == f) {
  34. fileDir = Environment.getExternalStorageDirectory().getPath()
  35. + File.separator + context.getPackageName()
  36. + File.separator + "cache";
  37. } else {
  38. fileDir = f.getPath();
  39. }
  40. } else {
  41. File f = context.getCacheDir();
  42. fileDir = f.getPath();
  43. }
  44. return fileDir;
  45. }
  46. }

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

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

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

  1. public View getView(final int position, View convertView, ViewGroup parent) {
  2. .......
  3. .......
  4. convertView.setId(position);
  5. return convertView;
  6. }

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

  1. //刷新可见区域列表进度值
  2. int firstV = lv.getFirstVisiblePosition();
  3. int lastV = lv.getLastVisiblePosition();
  4. for (int i = firstV; i <= lastV; i++) {
  5. View cell = lv.findViewById(i);
  6. cell.getId();
  7. if(cell != null) {
  8. Bean bean = workArray.getObjectAt(i);
  9. if(bean != null) {
  10. ((ProgressBar)cell
  11. .findViewById(R.id.download_progress))
  12. .setProgress(bean.getPercent());
  13. }
  14. }
  15. }

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

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

 

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

       O啦~~~

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

       谢谢!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/283753
推荐阅读
相关标签
  

闽ICP备14008679号