*Android 多线程下载 仿下载助手

今天带来一个多线程下载的 样例。先看一下效果。点击 下载 開始下载,同一时候显示下载进度。完成下载,变成程 安装,点击安装 提示 安装应用。

界面效果

线程池 ThreadPoolExecutor

在以下介绍实现下载原理的时候。我想尝试倒着来说。这样是否好理解一点? 
    我们都知道。下载助手,比方360, 百度的 手机助手,下载APP 的时候 ,都能够同一时候下载多个。所以,下载肯定是多线程的。所以我们就须要一个线程工具类 来管理我们的线程,这个工具类的核心,就是 线程池。

线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用

[java] view plaincopy

  1. /**
  2. * Parameters:
  3. corePoolSize
  4. the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
  5. maximumPoolSize
  6. the maximum number of threads to allow in the pool
  7. keepAliveTime
  8. when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
  9. unit
  10. the time unit for the keepAliveTime argument
  11. workQueue
  12. the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted                   by the execute method.
  13. handler
  14. the handler to use when execution is blocked because the thread bounds and queue capacities are reached
  15. Throws:
  16. IllegalArgumentException - if one of the following holds:
  17. corePoolSize < 0
  18. keepAliveTime < 0
  19. maximumPoolSize <= 0
  20. maximumPoolSize < corePoolSize
  21. NullPointerException - if workQueue or handler is null
  22. */
  23. ThreadPoolExecutor threadpool=new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler)

上面是 ThreadPoolExecutor的參数介绍。

第一个參数 corePoolSize : 空暇时 存在的线程数目、 
    第二个參数 maximumPoolSize :同意同一时候存在的最大线程数、 
    第三个參数 keepAliveTime: 这个參数是 同意空暇线程存活的时间、 
    第四个參数 unit : 是 时间的单位 、 
    第五个參数 workQueue :这个是一个容器,它里面存放的是、 threadpool.execute(new Runnable()) 运行的线程.new Runnable()、 
    第六个參数 handler:当运行被堵塞时,该处理程序将被堵塞。由于线程的边界和队列容量达到了 。

工具类 ThreadManager

介绍完了 线程池參数,那我们就先创建一个线程管理的工具类 ThreadManager

[java] view plaincopy

  1. public class ThreadManager {
  2. public static final String DEFAULT_SINGLE_POOL_NAME = "DEFAULT_SINGLE_POOL_NAME";
  3. private static ThreadPoolProxy mLongPool = null;
  4. private static Object mLongLock = new Object();
  5. private static ThreadPoolProxy mShortPool = null;
  6. private static Object mShortLock = new Object();
  7. private static ThreadPoolProxy mDownloadPool = null;
  8. private static Object mDownloadLock = new Object();
  9. private static Map<String, ThreadPoolProxy> mMap = new HashMap<String, ThreadPoolProxy>();
  10. private static Object mSingleLock = new Object();
  11. /** 获取下载线程 */
  12. public static ThreadPoolProxy getDownloadPool() {
  13. synchronized (mDownloadLock) {
  14. if (mDownloadPool == null) {
  15. mDownloadPool = new ThreadPoolProxy(3, 3, 5L);
  16. }
  17. return mDownloadPool;
  18. }
  19. }
  20. /** 获取一个用于运行长耗时任务的线程池。避免和短耗时任务处在同一个队列而堵塞了重要的短耗时任务。通经常使用来联网操作 */
  21. public static ThreadPoolProxy getLongPool() {
  22. synchronized (mLongLock) {
  23. if (mLongPool == null) {
  24. mLongPool = new ThreadPoolProxy(5, 5, 5L);
  25. }
  26. return mLongPool;
  27. }
  28. }
  29. /** 获取一个用于运行短耗时任务的线程池。避免由于和耗时长的任务处在同一个队列而长时间得不到运行,通经常使用来运行本地的IO/SQL */
  30. public static ThreadPoolProxy getShortPool() {
  31. synchronized (mShortLock) {
  32. if (mShortPool == null) {
  33. mShortPool = new ThreadPoolProxy(2, 2, 5L);
  34. }
  35. return mShortPool;
  36. }
  37. }
  38. /** 获取一个单线程池。全部任务将会被依照加入的顺序运行,免除了同步开销的问题 */
  39. public static ThreadPoolProxy getSinglePool() {
  40. return getSinglePool(DEFAULT_SINGLE_POOL_NAME);
  41. }
  42. /** 获取一个单线程池。全部任务将会被依照加入的顺序运行,免除了同步开销的问题 */
  43. public static ThreadPoolProxy getSinglePool(String name) {
  44. synchronized (mSingleLock) {
  45. ThreadPoolProxy singlePool = mMap.get(name);
  46. if (singlePool == null) {
  47. singlePool = new ThreadPoolProxy(1, 1, 5L);
  48. mMap.put(name, singlePool);
  49. }
  50. return singlePool;
  51. }
  52. }
  53. public static class ThreadPoolProxy {
  54. private ThreadPoolExecutor mPool;
  55. private int mCorePoolSize;
  56. private int mMaximumPoolSize;
  57. private long mKeepAliveTime;
  58. private ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
  59. mCorePoolSize = corePoolSize;
  60. mMaximumPoolSize = maximumPoolSize;
  61. mKeepAliveTime = keepAliveTime;
  62. }
  63. /** 运行任务,当线程池处于关闭,将会又一次创建新的线程池 */
  64. public synchronized void execute(Runnable run) {
  65. if (run == null) {
  66. return;
  67. }
  68. if (mPool == null || mPool.isShutdown()) {
  69. mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new AbortPolicy());
  70. }
  71. mPool.execute(run);
  72. }
  73. /** 取消线程池中某个还未运行的任务 */
  74. public synchronized void cancel(Runnable run) {
  75. if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
  76. mPool.getQueue().remove(run);
  77. }
  78. }
  79. /** 取消线程池中某个还未运行的任务 */
  80. public synchronized boolean contains(Runnable run) {
  81. if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
  82. return mPool.getQueue().contains(run);
  83. } else {
  84. return false;
  85. }
  86. }
  87. /** 立马关闭线程池,而且正在运行的任务也将会被中断 */
  88. public void stop() {
  89. if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
  90. mPool.shutdownNow();
  91. }
  92. }
  93. /** 平缓关闭单任务线程池,可是会确保全部已经加入的任务都将会被运行完成才关闭 */
  94. public synchronized void shutdown() {
  95. if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
  96. mPool.shutdownNow();
  97. }
  98. }
  99. }
  100. }

这个线程池工具类 主要就是 生成一个线程池。 以及 取消线程池中的任务,查询线程池中是否包括某一任务。

下载任务 DownloadTask

我们的如今线程 DownloadTask 就 通过 ThreadManager .getDownloadPool().execute() 方法 交给线程池去管理。

有了线程池管理我们的线程。 那我们下一步 就是 DownloadTask 这个类去下载了。

[java] view plaincopy

  1. /** 下载任务 */
  2. public class DownloadTask implements Runnable {
  3. private DownloadInfo info;
  4. public DownloadTask(DownloadInfo info) {
  5. this.info = info;
  6. }
  7. @Override
  8. public void run() {
  9. info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
  10. notifyDownloadStateChanged(info);
  11. File file = new File(info.getPath());// 获取下载文件
  12. HttpResult httpResult = null;
  13. InputStream stream = null;
  14. if (info.getCurrentSize() == 0 || !file.exists()
  15. || file.length() != info.getCurrentSize()) {
  16. // 假设文件不存在,或者进度为0,或者进度和文件长度不相符。就须要又一次下载
  17. info.setCurrentSize(0);
  18. file.delete();
  19. }
  20. httpResult = HttpHelper.download(info.getUrl());
  21. // else {
  22. // // //文件存在且长度和进度相等。採用断点下载
  23. // httpResult = HttpHelper.download(info.getUrl() + "&range=" +
  24. // info.getCurrentSize());
  25. // }
  26. if (httpResult == null
  27. || (stream = httpResult.getInputStream()) == null) {
  28. info.setDownloadState(STATE_ERROR);// 没有下载内容返回,改动为错误状态
  29. notifyDownloadStateChanged(info);
  30. } else {
  31. try {
  32. skipBytesFromStream(stream, info.getCurrentSize());
  33. } catch (Exception e1) {
  34. e1.printStackTrace();
  35. }
  36. FileOutputStream fos = null;
  37. try {
  38. fos = new FileOutputStream(file, true);
  39. int count = -1;
  40. byte[] buffer = new byte[1024];
  41. while (((count = stream.read(buffer)) != -1)
  42. && info.getDownloadState() == STATE_DOWNLOADING) {
  43. // 每次读取到数据后,都须要推断是否为下载状态。假设不是。下载须要终止,假设是,则刷新进度
  44. fos.write(buffer, 0, count);
  45. fos.flush();
  46. info.setCurrentSize(info.getCurrentSize() + count);
  47. notifyDownloadProgressed(info);// 刷新进度
  48. }
  49. } catch (Exception e) {
  50. info.setDownloadState(STATE_ERROR);
  51. notifyDownloadStateChanged(info);
  52. info.setCurrentSize(0);
  53. file.delete();
  54. } finally {
  55. IOUtils.close(fos);
  56. if (httpResult != null) {
  57. httpResult.close();
  58. }
  59. }
  60. // 推断进度是否和app总长度相等
  61. if (info.getCurrentSize() == info.getAppSize()) {
  62. info.setDownloadState(STATE_DOWNLOADED);
  63. notifyDownloadStateChanged(info);
  64. } else if (info.getDownloadState() == STATE_PAUSED) {// 推断状态
  65. notifyDownloadStateChanged(info);
  66. } else {
  67. info.setDownloadState(STATE_ERROR);
  68. notifyDownloadStateChanged(info);
  69. info.setCurrentSize(0);// 错误状态须要删除文件
  70. file.delete();
  71. }
  72. }
  73. mTaskMap.remove(info.getId());
  74. }
  75. }

下载的原理 非常easy。就是 通过 目标的URL 拿到流,然后写到本地。

由于下载在 run() 里面运行,这个DownloadTask 类 我们就看run() 方法的实现,所以 关键代码 就是以下一点点

[java] view plaincopy

  1. fos = new FileOutputStream(file, true);
  2. int count = -1;
  3. byte[] buffer = new byte[1024];
  4. while (((count = stream.read(buffer)) != -1)
  5. && info.getDownloadState() == STATE_DOWNLOADING) {
  6. // 每次读取到数据后。都须要推断是否为下载状态,假设不是,下载须要终止,假设是。则刷新进度
  7. fos.write(buffer, 0, count);
  8. fos.flush();
  9. info.setCurrentSize(info.getCurrentSize() + count);
  10. notifyDownloadProgressed(info);// 刷新进度
  11. }

这个在我们刚接触Java 的时候 肯定都写过了。 这就是往本地写数据的代码。所以run()方法中的 前面 就是拿到 stream 输入流, 以及 把file 创建出来。

刷新进度。状态

关于控制 button中text 显示 暂停 ,下载。还是进度,就靠 notifyDownloadProgressed(info);和 notifyDownloadStateChanged(info)两个方法, 这两个方法 实际上调用的是两个接口,仅仅要我们在我们须要改变界面的类里 实现这两个接口,就能够接收到 包括最新信息的info对象。而我们在哪个类里改变button 上面 显示的文字呢? 当然是在 我们的adapter 里面了,大家都知道 是在 adapter 的getView() 方法里面 载入的每一条数据的布局。

那就一起看下是不是这样子呢?

[java] view plaincopy

  1. public class RecommendAdapter extends BaseAdapter implements
  2. DownloadManager.DownloadObserver {
  3. ArrayList<AppInfo> list;
  4. private List<ViewHolder> mDisplayedHolders;
  5. private FinalBitmap finalBitmap;
  6. private Context context;
  7. public RecommendAdapter(ArrayList<AppInfo> list, FinalBitmap finalBitmap,
  8. Context context) {
  9. this.list = list;
  10. this.context = context;
  11. this.finalBitmap = finalBitmap;
  12. mDisplayedHolders = new ArrayList<ViewHolder>();
  13. }
  14. public void startObserver() {
  15. DownloadManager.getInstance().registerObserver(this);
  16. }
  17. public void stopObserver() {
  18. DownloadManager.getInstance().unRegisterObserver(this);
  19. }
  20. @Override
  21. public int getCount() {
  22. return list.size();
  23. }
  24. @Override
  25. public Object getItem(int position) {
  26. return list.get(position);
  27. }
  28. @Override
  29. public long getItemId(int position) {
  30. return position;
  31. }
  32. @Override
  33. public View getView(int position, View convertView, ViewGroup parent) {
  34. final AppInfo appInfo = list.get(position);
  35. final ViewHolder holder;
  36. if (convertView == null) {
  37. holder = new ViewHolder(context);
  38. } else {
  39. holder = (ViewHolder) convertView.getTag();
  40. }
  41. holder.setData(appInfo);
  42. mDisplayedHolders.add(holder);
  43. return holder.getRootView();
  44. }
  45. @Override
  46. public void onDownloadStateChanged(DownloadInfo info) {
  47. refreshHolder(info);
  48. }
  49. @Override
  50. public void onDownloadProgressed(DownloadInfo info) {
  51. refreshHolder(info);
  52. }
  53. public List<ViewHolder> getDisplayedHolders() {
  54. synchronized (mDisplayedHolders) {
  55. return new ArrayList<ViewHolder>(mDisplayedHolders);
  56. }
  57. }
  58. public void clearAllItem() {
  59. if (list != null){
  60. list.clear();
  61. }
  62. if (mDisplayedHolders != null) {
  63. mDisplayedHolders.clear();
  64. }
  65. }
  66. public void addItems(ArrayList<AppInfo> infos) {
  67. list.addAll(infos);
  68. }
  69. private void refreshHolder(final DownloadInfo info) {
  70. List<ViewHolder> displayedHolders = getDisplayedHolders();
  71. for (int i = 0; i < displayedHolders.size(); i++) {
  72. final ViewHolder holder = displayedHolders.get(i);
  73. AppInfo appInfo = holder.getData();
  74. if (appInfo.getId() == info.getId()) {
  75. AppUtil.post(new Runnable() {
  76. @Override
  77. public void run() {
  78. holder.refreshState(info.getDownloadState(),
  79. info.getProgress());
  80. }
  81. });
  82. }
  83. }
  84. }
  85. public class ViewHolder {
  86. public TextView textView01;
  87. public TextView textView02;
  88. public TextView textView03;
  89. public TextView textView04;
  90. public ImageView imageView_icon;
  91. public Button button;
  92. public LinearLayout linearLayout;
  93. public AppInfo mData;
  94. private DownloadManager mDownloadManager;
  95. private int mState;
  96. private float mProgress;
  97. protected View mRootView;
  98. private Context context;
  99. private boolean hasAttached;
  100. public ViewHolder(Context context) {
  101. mRootView = initView();
  102. mRootView.setTag(this);
  103. this.context = context;
  104. }
  105. public View getRootView() {
  106. return mRootView;
  107. }
  108. public View initView() {
  109. View view = AppUtil.inflate(R.layout.item_recommend_award);
  110. imageView_icon = (ImageView) view
  111. .findViewById(R.id.imageview_task_app_cion);
  112. textView01 = (TextView) view
  113. .findViewById(R.id.textview_task_app_name);
  114. textView02 = (TextView) view
  115. .findViewById(R.id.textview_task_app_size);
  116. textView03 = (TextView) view
  117. .findViewById(R.id.textview_task_app_desc);
  118. textView04 = (TextView) view
  119. .findViewById(R.id.textview_task_app_love);
  120. button = (Button) view.findViewById(R.id.button_task_download);
  121. linearLayout = (LinearLayout) view
  122. .findViewById(R.id.linearlayout_task);
  123. button.setOnClickListener(new OnClickListener() {
  124. @Override
  125. public void onClick(View v) {
  126. System.out.println("mState:173    "+mState);
  127. if (mState == DownloadManager.STATE_NONE
  128. || mState == DownloadManager.STATE_PAUSED
  129. || mState == DownloadManager.STATE_ERROR) {
  130. mDownloadManager.download(mData);
  131. } else if (mState == DownloadManager.STATE_WAITING
  132. || mState == DownloadManager.STATE_DOWNLOADING) {
  133. mDownloadManager.pause(mData);
  134. } else if (mState == DownloadManager.STATE_DOWNLOADED) {
  135. //                      tell2Server();
  136. mDownloadManager.install(mData);
  137. }
  138. }
  139. });
  140. return view;
  141. }
  142. public void setData(AppInfo data) {
  143. if (mDownloadManager == null) {
  144. mDownloadManager = DownloadManager.getInstance();
  145. }
  146. String filepath= FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + data.getName() + ".apk";
  147. boolean existsFile = FileUtil.isExistsFile(filepath);
  148. if(existsFile){
  149. int fileSize = FileUtil.getFileSize(filepath);
  150. if(data.getSize()==fileSize){
  151. DownloadInfo downloadInfo = DownloadInfo.clone(data);
  152. downloadInfo.setCurrentSize(data.getSize());
  153. downloadInfo.setHasFinished(true);
  154. mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
  155. }
  156. //                  else if(fileSize>0){
  157. //                      DownloadInfo downloadInfo = DownloadInfo.clone(data);
  158. //                      downloadInfo.setCurrentSize(data.getSize());
  159. //                      downloadInfo.setHasFinished(false);
  160. //                      mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
  161. //                  }
  162. }
  163. DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(data
  164. .getId());
  165. if (downloadInfo != null) {
  166. mState = downloadInfo.getDownloadState();
  167. mProgress = downloadInfo.getProgress();
  168. } else {
  169. mState = DownloadManager.STATE_NONE;
  170. mProgress = 0;
  171. }
  172. this.mData = data;
  173. refreshView();
  174. }
  175. public AppInfo getData() {
  176. return mData;
  177. }
  178. public void refreshView() {
  179. linearLayout.removeAllViews();
  180. AppInfo info = getData();
  181. textView01.setText(info.getName());
  182. textView02.setText(FileUtil.FormetFileSize(info.getSize()));
  183. textView03.setText(info.getDes());
  184. textView04.setText(info.getDownloadNum() + "下载量);
  185. finalBitmap.display(imageView_icon, info.getIconUrl());
  186. if (info.getType().equals("0")) {
  187. //              mState = DownloadManager.STATE_READ;
  188. textView02.setVisibility(View.GONE);
  189. }else{
  190. String  path=FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + info.getName() + ".apk";
  191. hasAttached = FileUtil.isValidAttach(path, false);
  192. DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(info
  193. .getId());
  194. if (downloadInfo != null && hasAttached) {
  195. if(downloadInfo.isHasFinished()){
  196. mState = DownloadManager.STATE_DOWNLOADED;
  197. }else{
  198. mState = DownloadManager.STATE_PAUSED;
  199. }
  200. } else {
  201. mState = DownloadManager.STATE_NONE;
  202. if(downloadInfo !=null){
  203. downloadInfo.setDownloadState(mState);
  204. }
  205. }
  206. }
  207. refreshState(mState, mProgress);
  208. }
  209. public void refreshState(int state, float progress) {
  210. mState = state;
  211. mProgress = progress;
  212. switch (mState) {
  213. case DownloadManager.STATE_NONE:
  214. button.setText(R.string.app_state_download);
  215. break;
  216. case DownloadManager.STATE_PAUSED:
  217. button.setText(R.string.app_state_paused);
  218. break;
  219. case DownloadManager.STATE_ERROR:
  220. button.setText(R.string.app_state_error);
  221. break;
  222. case DownloadManager.STATE_WAITING:
  223. button.setText(R.string.app_state_waiting);
  224. break;
  225. case DownloadManager.STATE_DOWNLOADING:
  226. button.setText((int) (mProgress * 100) + "%");
  227. break;
  228. case DownloadManager.STATE_DOWNLOADED:
  229. button.setText(R.string.app_state_downloaded);
  230. break;
  231. //          case DownloadManager.STATE_READ:
  232. //              button.setText(R.string.app_state_read);
  233. //              break;
  234. default:
  235. break;
  236. }
  237. }
  238. }
  239. }

何时 注冊 监听observer

里面代码有点多,那就看startObserver()方法做了什么。

[java] view plaincopy

  1. public void startObserver() {
  2. DownloadManager.getInstance().registerObserver(this);
  3. }

这里 是 注冊了observer, Observer 是什么东西?在DownloadManager 中我们定义了

public interface DownloadObserver {

    public void onDownloadStateChanged(DownloadInfo info);

    public void onDownloadProgressed(DownloadInfo info);
}

一个接口,里面有两个抽象方法 一个是 进度。还有一个是下载状态。 那回过头来,屡一下。 我们在 下载的关键代码里面调用了

DownloadObserver onDownloadProgressed()

DownloadObserver.onDownloadStateChanged()

两个抽象方法,而我们在 adapter中实现了 这两个方法 就能够轻松的控制 去 刷新 和改变 下载状态了。

[java] view plaincopy

  1. @Override
  2. public void onDownloadStateChanged(DownloadInfo info) {
  3. refreshHolder(info);
  4. }
  5. @Override
  6. public void onDownloadProgressed(DownloadInfo info) {
  7. refreshHolder(info);
  8. }

细心的朋友 也许 发现问题了,对,我们还没有注冊Observer,就在 DownloadManager 中去调用了。

这里 在看下DownloadManager 中 调用的方法

/** 当下载状态发送改变的时候回调 */
public void notifyDownloadStateChanged(DownloadInfo info) {
    synchronized (mObservers) {
        for (DownloadObserver observer : mObservers) {
            observer.onDownloadStateChanged(info);
        }
    }
}

/** 当下载进度发送改变的时候回调 */
public void notifyDownloadProgressed(DownloadInfo info) {
    synchronized (mObservers) {
        for (DownloadObserver observer : mObservers) {
            observer.onDownloadProgressed(info);
        }
    }
}

是的。这里我们遍历一个observer容器。然后去刷新 。所以我们还须要把Observer对象加入到集合mObservers中,所以肯定有这样一个方法将observer加入到集合中 。

/* 注冊观察者 /
public void registerObserver(DownloadObserver observer) {
synchronized (mObservers) {
if (!mObservers.contains(observer)) {
mObservers.add(observer);
}
}
}
/** 反注冊观察者 */
public void unRegisterObserver(DownloadObserver observer) {
    synchronized (mObservers) {
        if (mObservers.contains(observer)) {
            mObservers.remove(observer);
        }
    }
}

所以最后一步,由于 adapter 方法中有 startObserver, 所以 我们在 主界面 MainActivity 的类中调用 adapter.startObser() 将 实现了 接口的adapter 对象 加入到 Observer 容器中 就能够了。

OK。大功告成。

=============================================

DownloadManager 代码

这里 贴一下DownloadManager 代码

[java] view plaincopy

  1. public class DownloadManager {
  2. public static final int STATE_NONE = 0;
  3. /** 等待中 */
  4. public static final int STATE_WAITING = 1;
  5. /** 下载中 */
  6. public static final int STATE_DOWNLOADING = 2;
  7. /** 暂停 */
  8. public static final int STATE_PAUSED = 3;
  9. /** 完成下载 */
  10. public static final int STATE_DOWNLOADED = 4;
  11. /** 下载失败 */
  12. public static final int STATE_ERROR = 5;
  13. // public static final int STATE_READ = 6;
  14. private static DownloadManager instance;
  15. private DownloadManager() {
  16. }
  17. /** 用于记录下载信息。假设是正式项目,须要持久化保存 */
  18. private Map<Long, DownloadInfo> mDownloadMap = new ConcurrentHashMap<Long, DownloadInfo>();
  19. /** 用于记录观察者。当信息发送了改变,须要通知他们 */
  20. private List<DownloadObserver> mObservers = new ArrayList<DownloadObserver>();
  21. /** 用于记录全部下载的任务,方便在取消下载时,通过id能找到该任务进行删除 */
  22. private Map<Long, DownloadTask> mTaskMap = new ConcurrentHashMap<Long, DownloadTask>();
  23. public static synchronized DownloadManager getInstance() {
  24. if (instance == null) {
  25. instance = new DownloadManager();
  26. }
  27. return instance;
  28. }
  29. /** 注冊观察者 */
  30. public void registerObserver(DownloadObserver observer) {
  31. synchronized (mObservers) {
  32. if (!mObservers.contains(observer)) {
  33. mObservers.add(observer);
  34. }
  35. }
  36. }
  37. /** 反注冊观察者 */
  38. public void unRegisterObserver(DownloadObserver observer) {
  39. synchronized (mObservers) {
  40. if (mObservers.contains(observer)) {
  41. mObservers.remove(observer);
  42. }
  43. }
  44. }
  45. /** 当下载状态发送改变的时候回调 */
  46. public void notifyDownloadStateChanged(DownloadInfo info) {
  47. synchronized (mObservers) {
  48. for (DownloadObserver observer : mObservers) {
  49. observer.onDownloadStateChanged(info);
  50. }
  51. }
  52. }
  53. /** 当下载进度发送改变的时候回调 */
  54. public void notifyDownloadProgressed(DownloadInfo info) {
  55. synchronized (mObservers) {
  56. for (DownloadObserver observer : mObservers) {
  57. observer.onDownloadProgressed(info);
  58. }
  59. }
  60. }
  61. /** 下载。须要传入一个appInfo对象 */
  62. public synchronized void download(AppInfo appInfo) {
  63. // 先推断是否有这个app的下载信息
  64. DownloadInfo info = mDownloadMap.get(appInfo.getId());
  65. if (info == null) {// 假设没有,则依据appInfo创建一个新的下载信息
  66. info = DownloadInfo.clone(appInfo);
  67. mDownloadMap.put(appInfo.getId(), info);
  68. }
  69. // 推断状态是否为STATE_NONE、STATE_PAUSED、STATE_ERROR。仅仅有这3种状态才干进行下载,其它状态不予处理
  70. if (info.getDownloadState() == STATE_NONE
  71. || info.getDownloadState() == STATE_PAUSED
  72. || info.getDownloadState() == STATE_ERROR) {
  73. // 下载之前。把状态设置为STATE_WAITING,由于此时并没有产開始下载,仅仅是把任务放入了线程池中,当任务真正開始运行时。才会改为STATE_DOWNLOADING
  74. info.setDownloadState(STATE_WAITING);
  75. notifyDownloadStateChanged(info);// 每次状态发生改变。都须要回调该方法通知全部观察者
  76. DownloadTask task = new DownloadTask(info);// 创建一个下载任务,放入线程池
  77. mTaskMap.put(info.getId(), task);
  78. ThreadManager.getDownloadPool().execute(task);
  79. }
  80. }
  81. /** 暂停下载 */
  82. public synchronized void pause(AppInfo appInfo) {
  83. stopDownload(appInfo);
  84. DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
  85. if (info != null) {// 改动下载状态
  86. info.setDownloadState(STATE_PAUSED);
  87. notifyDownloadStateChanged(info);
  88. }
  89. }
  90. /** 取消下载。逻辑和暂停相似,仅仅是须要删除已下载的文件 */
  91. public synchronized void cancel(AppInfo appInfo) {
  92. stopDownload(appInfo);
  93. DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
  94. if (info != null) {// 改动下载状态并删除文件
  95. info.setDownloadState(STATE_NONE);
  96. notifyDownloadStateChanged(info);
  97. info.setCurrentSize(0);
  98. File file = new File(info.getPath());
  99. file.delete();
  100. }
  101. }
  102. /** 安装应用 */
  103. public synchronized void install(AppInfo appInfo) {
  104. stopDownload(appInfo);
  105. DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
  106. if (info != null) {// 发送安装的意图
  107. Intent installIntent = new Intent(Intent.ACTION_VIEW);
  108. installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  109. installIntent.setDataAndType(Uri.parse("file://" + info.getPath()),
  110. "application/vnd.android.package-archive");
  111. AppUtil.getContext().startActivity(installIntent);
  112. }
  113. notifyDownloadStateChanged(info);
  114. }
  115. /** 启动应用,启动应用是最后一个 */
  116. public synchronized void open(AppInfo appInfo) {
  117. try {
  118. Context context = AppUtil.getContext();
  119. // 获取启动Intent
  120. Intent intent = context.getPackageManager()
  121. .getLaunchIntentForPackage(appInfo.getPackageName());
  122. context.startActivity(intent);
  123. } catch (Exception e) {
  124. }
  125. }
  126. /** 假设该下载任务还处于线程池中,且没有运行,先从线程池中移除 */
  127. private void stopDownload(AppInfo appInfo) {
  128. DownloadTask task = mTaskMap.remove(appInfo.getId());// 先从集合中找出下载任务
  129. if (task != null) {
  130. ThreadManager.getDownloadPool().cancel(task);// 然后从线程池中移除
  131. }
  132. }
  133. /** 获取下载信息 */
  134. public synchronized DownloadInfo getDownloadInfo(long id) {
  135. return mDownloadMap.get(id);
  136. }
  137. public synchronized void setDownloadInfo(long id, DownloadInfo info) {
  138. mDownloadMap.put(id, info);
  139. }
  140. /** 下载任务 */
  141. public class DownloadTask implements Runnable {
  142. private DownloadInfo info;
  143. public DownloadTask(DownloadInfo info) {
  144. this.info = info;
  145. }
  146. @Override
  147. public void run() {
  148. info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
  149. notifyDownloadStateChanged(info);
  150. File file = new File(info.getPath());// 获取下载文件
  151. HttpResult httpResult = null;
  152. InputStream stream = null;
  153. if (info.getCurrentSize() == 0 || !file.exists()
  154. || file.length() != info.getCurrentSize()) {
  155. // 假设文件不存在,或者进度为0,或者进度和文件长度不相符。就须要又一次下载
  156. info.setCurrentSize(0);
  157. file.delete();
  158. }
  159. httpResult = HttpHelper.download(info.getUrl());
  160. // else {
  161. // // //文件存在且长度和进度相等。採用断点下载
  162. // httpResult = HttpHelper.download(info.getUrl() + "&range=" +
  163. // info.getCurrentSize());
  164. // }
  165. if (httpResult == null
  166. || (stream = httpResult.getInputStream()) == null) {
  167. info.setDownloadState(STATE_ERROR);// 没有下载内容返回。改动为错误状态
  168. notifyDownloadStateChanged(info);
  169. } else {
  170. try {
  171. skipBytesFromStream(stream, info.getCurrentSize());
  172. } catch (Exception e1) {
  173. e1.printStackTrace();
  174. }
  175. FileOutputStream fos = null;
  176. try {
  177. fos = new FileOutputStream(file, true);
  178. int count = -1;
  179. byte[] buffer = new byte[1024];
  180. while (((count = stream.read(buffer)) != -1)
  181. && info.getDownloadState() == STATE_DOWNLOADING) {
  182. // 每次读取到数据后,都须要推断是否为下载状态,假设不是。下载须要终止。假设是,则刷新进度
  183. fos.write(buffer, 0, count);
  184. fos.flush();
  185. info.setCurrentSize(info.getCurrentSize() + count);
  186. notifyDownloadProgressed(info);// 刷新进度
  187. }
  188. } catch (Exception e) {
  189. info.setDownloadState(STATE_ERROR);
  190. notifyDownloadStateChanged(info);
  191. info.setCurrentSize(0);
  192. file.delete();
  193. } finally {
  194. IOUtils.close(fos);
  195. if (httpResult != null) {
  196. httpResult.close();
  197. }
  198. }
  199. // 推断进度是否和app总长度相等
  200. if (info.getCurrentSize() == info.getAppSize()) {
  201. info.setDownloadState(STATE_DOWNLOADED);
  202. notifyDownloadStateChanged(info);
  203. } else if (info.getDownloadState() == STATE_PAUSED) {// 推断状态
  204. notifyDownloadStateChanged(info);
  205. } else {
  206. info.setDownloadState(STATE_ERROR);
  207. notifyDownloadStateChanged(info);
  208. info.setCurrentSize(0);// 错误状态须要删除文件
  209. file.delete();
  210. }
  211. }
  212. mTaskMap.remove(info.getId());
  213. }
  214. }
  215. public interface DownloadObserver {
  216. public abstract void onDownloadStateChanged(DownloadInfo info);
  217. public abstract void onDownloadProgressed(DownloadInfo info);
  218. }
  219. /* 重写了Inpustream 中的skip(long n) 方法,将数据流中起始的n 个字节跳过 */
  220. private long skipBytesFromStream(InputStream inputStream, long n) {
  221. long remaining = n;
  222. // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
  223. int SKIP_BUFFER_SIZE = 10000;
  224. // skipBuffer is initialized in skip(long), if needed.
  225. byte[] skipBuffer = null;
  226. int nr = 0;
  227. if (skipBuffer == null) {
  228. skipBuffer = new byte[SKIP_BUFFER_SIZE];
  229. }
  230. byte[] localSkipBuffer = skipBuffer;
  231. if (n <= 0) {
  232. return 0;
  233. }
  234. while (remaining > 0) {
  235. try {
  236. long skip = inputStream.skip(10000);
  237. nr = inputStream.read(localSkipBuffer, 0,
  238. (int) Math.min(SKIP_BUFFER_SIZE, remaining));
  239. } catch (IOException e) {
  240. e.printStackTrace();
  241. }
  242. if (nr < 0) {
  243. break;
  244. }
  245. remaining -= nr;
  246. }
  247. return n - remaining;
  248. }
  249. }

有两点 须要说明。关于 点击暂停后。再继续下载 有两种方式能够实现

第一种 点击暂停的时候 记录下载了 多少,然后 再点击 继续下载 时,告诉服务器, 让服务器接着 上次的数据 往本地传递,

代码 就是 我们 DownloadTask 下载时候,推断一下

[java] view plaincopy

  1. // //文件存在且长度和进度相等,採用断点下载
  2. httpResult = HttpHelper.download(info.getUrl() + "&range=" + info.getCurrentSize());

通过 range 来区分 当前的下载size.

服务器 处理的代码 也非常easy 就是一句话

String range = req.getParameter(“range”); 拿到 range 推断 range 存在不存在。 
    假设不存在

[java] view plaincopy

  1. FileInputStream stream = new FileInputStream(file);
  2. int count = -1;
  3. byte[] buffer = new byte[1024];
  4. while ((count = stream.read(buffer)) != -1) {
  5. SystemClock.sleep(20);
  6. out.write(buffer, 0, count);
  7. out.flush();
  8. }
  9. stream.close();
  10. out.close();

如果存在 那么跳过range 个字节

[java] view plaincopy

  1. RandomAccessFile raf = new RandomAccessFile(file, "r");
  2. raf.seek(Long.valueOf(range));
  3. int count = -1;
  4. byte[] buffer = new byte[1024];
  5. while ((count = raf.read(buffer)) != -1) {
  6. SystemClock.sleep(10);
  7. out.write(buffer, 0, count);
  8. out.flush();
  9. }
  10. raf.close();
  11. out.close();

还有一种方式是本地处理,这个demo 中就是本地处理的, 可是有一个问题。 由于 Java api的原因 。inputStream.skip() 方法 并不能准确的 跳过多少个字节。

而是 小于你想要跳过的字节,所以 你要去遍历 一直到 满足你要跳过的字节 在继续写。 由于 这个方案有一个缺点。就是在下载非常大的文件,

比方文件大小20M ,当已经下载了15M 此时你去暂停。在继续下载,那么要跳过前面的15M 将会话费非常多时间。

所以这个仅限于学习。实际中 假设要下载大的文件。不能用这个方案。

完美版 过几天 再发。

代码下载 : 
AndroidStudio 版
Eclipse 版

-------------------------------------------各级格机格机格机--------------------------------------

-------------------------------改进版 :点击打开链接------------------------

谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。

欢迎爱学习的小伙伴加群一起进步:230274309 。

时间: 2024-10-27 06:24:31

*Android 多线程下载 仿下载助手的相关文章

J哥---------Android 多线程下载 仿下载助手(改进版)

首先声明一点: 这里的多线程下载 并不是指的 多个线程下载一个 文件,而是 每个线程 负责一个文件.真正的多线程 希望后面能给大家带来.  -------------  欢迎 爱学习的小伙伴 加群  -------------  -------------android交流群:230274309------------- -------------一起分享,一起进步!  需要你们-------------- --------------  期待各位爱学习的小伙伴们 的到来------------

*Android 多线程下载 仿下载助手(改进版)

首先声明一点: 这里的多线程下载 并非指的 多个线程下载一个 文件.而是 每一个线程 负责一个文件. 真正的多线程 希望后面能给大家带来.  -------------  欢迎 爱学习的小伙伴 加群  -------------  -------------android交流群:230274309------------- -------------一起分享.一起进步! 须要你们-------------- --------------  期待各位爱学习的小伙伴们 的到来------------

Android 多线程断点续传同时下载多个大文件

最近学习在Android环境中一些网络请求方面的知识,其中有一部分是关于网络下载方面的知识.在这里解析一下自己写的demo,总结一下自己所学的知识.下图为demo的效果图,仿照一些应用下载商城在ListView中列出加载项,然后可以可以下载和停止. 1.概述 这里有几个比较重要的类DownloadManager.DownloadService.DownloadTask.ThreadDAOImpl.主要的下载流程如下. (1) DownloadManager 负责下载任务的调配,以及下载服务Dow

android 多线程断点续传下载 四 - 仿下载助手

我们先一起简单回顾下它的基本原理. http://blog.csdn.net/shimiso/article/details/6763664  android 多线程断点续传下载 一 http://blog.csdn.net/shimiso/article/details/6763986  android 多线程断点续传下载 二 http://blog.csdn.net/shimiso/article/details/8448544  android 多线程断点续传下载 三 界面效果 线程池 T

Android多线程断点续传下载

这个月接到一个项目,要写一个像360助手一样的对于软件管理的APP:其中,遇到了一个问题:多线程断点下载 这个 ,由于之前没有写过这方面的应用功能.所以,不免要自学了.然后就在各个昂站上收索并整理了一下.跟大家分享一下,也能加深我在这方面的理解. 什么是多线程下载? 多线程下载其实就是迅雷,BT一些下载原理,通过多个线程同时和服务器连接,那么你就可以榨取到较高的带宽了,大致做法是将文件切割成N块,每块交给单独一个线程去下载,各自下载完成后将文件块组合成一个文件,程序上要完成做切割和组装的小算法

Android 多线程下载原理剖析

今天带来一个多线程下载的 例子.先看一下效果,点击 下载 开始下载,同时显示下载进度,下载完成,变成程 安装,点击安装 提示 安装应用. 界面效果 这里写图片描述 线程池 ThreadPoolExecutor 在下面介绍实现下载原理的时候,我想尝试倒着来说,这样是否好理解一点? 我们都知道,下载助手,比如360, 百度的 手机助手,下载APP 的时候 ,都可以同时下载多个,所以,下载肯定是多线程的,所以我们就需要一个线程工具类 来管理我们的线程,这个工具类的核心,就是 线程池. 线程池Threa

Andoid 更好的Android多线程下载框架

概述 为什么是更好的Android多线程下载框架呢,原因你懂的,广告法嘛! 本篇我们我们就来聊聊多线程下载框架,先聊聊我们框架的特点: 多线程 多任务 断点续传 支持大文件 可以自定义下载数据库 高度可配置,像超时时间这类 业务数据和下载数据分离 下面我们在说下该框架能实现那些的应用场景: 该框架可以很方便的下载单个文件,并且显示各种状态,包括开始下载,下载中,下载失败,删除等状态. 也可以实现常见的需要下载功能应用,比如:某某手机助手,在该应用内可以说是下载是核心功能,所以对框架的稳定性,代码

无废话Android之smartimageview使用、android多线程下载、显式意图激活另外一个activity,检查网络是否可用定位到网络的位置、隐式意图激活另外一个activity、隐式意图的配置,自定义隐式意图、在不同activity之间数据传递(5)

1.smartimageview使用 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"

Android多线程分析之一:使用Thread异步下载图像

罗朝辉 (http://blog.csdn.net/kesalin) CC 许可,转载请注明出处 打算整理一下对 Android Framework 中多线程相关知识的理解,主要集中在 Framework 层的 Thread, Handler, Looper, MessageQueue, Message, AysncTask,当然不可避免地要涉及到 native 方法,因此也会分析 dalvik 中和线程以及消息处理相关的代码:如 dalvik 中的 C++ Thread 类以及 Message