Android版多线程下载 仿下载助手(最新)_Android

首先声明一点: 这里的多线程下载并不是指多个线程下载一个 文件,而是每个线程负责一个文件,今天给大家分享一个多线程下载的 例子。先看一下效果,点击下载开始下载,同时显示下载进度,下载完成,变成程安装,点击安装提示安装应用。

界面效果图:

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

/**
    * Parameters:
     corePoolSize
       the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
     maximumPoolSize
       the maximum number of threads to allow in the pool
     keepAliveTime
       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.
     unit
       the time unit for the keepAliveTime argument
     workQueue
       the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted          by the execute method.
     handler
       the handler to use when execution is blocked because the thread bounds and queue capacities are reached
    Throws:
     IllegalArgumentException - if one of the following holds:
     corePoolSize < 0
     keepAliveTime < 0
     maximumPoolSize <= 0
     maximumPoolSize < corePoolSize
     NullPointerException - if workQueue or handler is null
    */
   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

public class ThreadManager {
  public static final String DEFAULT_SINGLE_POOL_NAME = "DEFAULT_SINGLE_POOL_NAME";
  private static ThreadPoolProxy mLongPool = null;
  private static Object mLongLock = new Object();
  private static ThreadPoolProxy mShortPool = null;
  private static Object mShortLock = new Object();
  private static ThreadPoolProxy mDownloadPool = null;
  private static Object mDownloadLock = new Object();
  private static Map<String, ThreadPoolProxy> mMap = new HashMap<String, ThreadPoolProxy>();
  private static Object mSingleLock = new Object();
  /** 获取下载线程 */
  public static ThreadPoolProxy getDownloadPool() {
    synchronized (mDownloadLock) {
      if (mDownloadPool == null) {
        mDownloadPool = new ThreadPoolProxy(3, 3, 5L);
      }
      return mDownloadPool;
    }
  }
  /** 获取一个用于执行长耗时任务的线程池,避免和短耗时任务处在同一个队列而阻塞了重要的短耗时任务,通常用来联网操作 */
  public static ThreadPoolProxy getLongPool() {
    synchronized (mLongLock) {
      if (mLongPool == null) {
        mLongPool = new ThreadPoolProxy(5, 5, 5L);
      }
      return mLongPool;
    }
  }
  /** 获取一个用于执行短耗时任务的线程池,避免因为和耗时长的任务处在同一个队列而长时间得不到执行,通常用来执行本地的IO/SQL */
  public static ThreadPoolProxy getShortPool() {
    synchronized (mShortLock) {
      if (mShortPool == null) {
        mShortPool = new ThreadPoolProxy(2, 2, 5L);
      }
      return mShortPool;
    }
  }
  /** 获取一个单线程池,所有任务将会被按照加入的顺序执行,免除了同步开销的问题 */
  public static ThreadPoolProxy getSinglePool() {
    return getSinglePool(DEFAULT_SINGLE_POOL_NAME);
  }
  /** 获取一个单线程池,所有任务将会被按照加入的顺序执行,免除了同步开销的问题 */
  public static ThreadPoolProxy getSinglePool(String name) {
    synchronized (mSingleLock) {
      ThreadPoolProxy singlePool = mMap.get(name);
      if (singlePool == null) {
        singlePool = new ThreadPoolProxy(1, 1, 5L);
        mMap.put(name, singlePool);
      }
      return singlePool;
    }
  }
  public static class ThreadPoolProxy {
    private ThreadPoolExecutor mPool;
    private int mCorePoolSize;
    private int mMaximumPoolSize;
    private long mKeepAliveTime;
    private ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
      mCorePoolSize = corePoolSize;
      mMaximumPoolSize = maximumPoolSize;
      mKeepAliveTime = keepAliveTime;
    }
    /** 执行任务,当线程池处于关闭,将会重新创建新的线程池 */
    public synchronized void execute(Runnable run) {
      if (run == null) {
        return;
      }
      if (mPool == null || mPool.isShutdown()) {
        mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new AbortPolicy());
      }
      mPool.execute(run);
    }
    /** 取消线程池中某个还未执行的任务 */
    public synchronized void cancel(Runnable run) {
      if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
        mPool.getQueue().remove(run);
      }
    }
    /** 取消线程池中某个还未执行的任务 */
    public synchronized boolean contains(Runnable run) {
      if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
        return mPool.getQueue().contains(run);
      } else {
        return false;
      }
    }
    /** 立刻关闭线程池,并且正在执行的任务也将会被中断 */
    public void stop() {
      if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
        mPool.shutdownNow();
      }
    }
    /** 平缓关闭单任务线程池,但是会确保所有已经加入的任务都将会被执行完毕才关闭 */
    public synchronized void shutdown() {
      if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {
        mPool.shutdownNow();
      }
    }
  }
}

这个线程池工具类 主要就是 生成一个线程池, 以及 取消线程池中的任务,查询线程池中是否包含某一任务。
下载任务 DownloadTask
我们的现在线程 DownloadTask 就 通过 ThreadManager .getDownloadPool().execute() 方法 交给线程池去管理。
有了线程池管理我们的线程, 那我们下一步 就是 DownloadTask 这个类去下载了。

/** 下载任务 */
 public class DownloadTask implements Runnable {
   private DownloadInfo info;
   public DownloadTask(DownloadInfo info) {
     this.info = info;
   }
   @Override
   public void run() {
     info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
     notifyDownloadStateChanged(info);
     File file = new File(info.getPath());// 获取下载文件
     HttpResult httpResult = null;
     InputStream stream = null;
     if (info.getCurrentSize() == 0 || !file.exists()
         || file.length() != info.getCurrentSize()) {
       // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
       info.setCurrentSize(0);
       file.delete();
     }
     httpResult = HttpHelper.download(info.getUrl());
     // else {
     // // //文件存在且长度和进度相等,采用断点下载
     // httpResult = HttpHelper.download(info.getUrl() + "&range=" +
     // info.getCurrentSize());
     // }
     if (httpResult == null
         || (stream = httpResult.getInputStream()) == null) {
       info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态
       notifyDownloadStateChanged(info);
     } else {
       try {
         skipBytesFromStream(stream, info.getCurrentSize());
       } catch (Exception e1) {
         e1.printStackTrace();
       }
       FileOutputStream fos = null;
       try {
         fos = new FileOutputStream(file, true);
         int count = -1;
         byte[] buffer = new byte[1024];
         while (((count = stream.read(buffer)) != -1)
             && info.getDownloadState() == STATE_DOWNLOADING) {
           // 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
           fos.write(buffer, 0, count);
           fos.flush();
           info.setCurrentSize(info.getCurrentSize() + count);
           notifyDownloadProgressed(info);// 刷新进度
         }
       } catch (Exception e) {
         info.setDownloadState(STATE_ERROR);
         notifyDownloadStateChanged(info);
         info.setCurrentSize(0);
         file.delete();
       } finally {
         IOUtils.close(fos);
         if (httpResult != null) {
           httpResult.close();
         }
       }
       // 判断进度是否和app总长度相等
       if (info.getCurrentSize() == info.getAppSize()) {
         info.setDownloadState(STATE_DOWNLOADED);
         notifyDownloadStateChanged(info);
       } else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
         notifyDownloadStateChanged(info);
       } else {
         info.setDownloadState(STATE_ERROR);
         notifyDownloadStateChanged(info);
         info.setCurrentSize(0);// 错误状态需要删除文件
         file.delete();
       }
     }
     mTaskMap.remove(info.getId());
   }
 }

下载的原理 很简单,就是通过目标的URL 拿到流,然后写到本地。
因为下载在 run()里面执行,这个DownloadTask 类 我们就看run() 方法的实现,所以 关键代码 就是下面一点点

fos = new FileOutputStream(file, true);
     int count = -1;
     byte[] buffer = new byte[1024];
     while (((count = stream.read(buffer)) != -1)
         && info.getDownloadState() == STATE_DOWNLOADING) {
       // 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
       fos.write(buffer, 0, count);
       fos.flush();
       info.setCurrentSize(info.getCurrentSize() + count);
       notifyDownloadProgressed(info);// 刷新进度
     }

这个在我们刚接触Java 的时候 肯定都写过了。 这就是往本地写数据的代码。所以run()方法中的 前面 就是拿到 stream 输入流, 以及 把file 创建出来。
刷新进度,状态
关于控制 button中text 显示 暂停 ,下载,还是进度,就靠 notifyDownloadProgressed(info);和 notifyDownloadStateChanged(info)两个方法, 这两个方法 实际上调用的是两个接口,只要我们在我们需要改变界面的类里 实现这两个接口,就可以接收到 包含最新信息的info对象。而我们在哪个类里改变button 上面 显示的文字呢? 当然是在 我们的adapter 里面了,大家都知道 是在 adapter 的getView() 方法里面 加载的每一条数据的布局。
那就一起看下是不是这样子呢?

public class RecommendAdapter extends BaseAdapter implements
    DownloadManager.DownloadObserver {
  ArrayList<AppInfo> list;
  private List<ViewHolder> mDisplayedHolders;
  private FinalBitmap finalBitmap;
  private Context context;
  public RecommendAdapter(ArrayList<AppInfo> list, FinalBitmap finalBitmap,
      Context context) {
    this.list = list;
    this.context = context;
    this.finalBitmap = finalBitmap;
    mDisplayedHolders = new ArrayList<ViewHolder>();
  } 

  public void startObserver() {
    DownloadManager.getInstance().registerObserver(this);
  }
  public void stopObserver() {
    DownloadManager.getInstance().unRegisterObserver(this);
  }
  @Override
  public int getCount() {
    return list.size();
  }
  @Override
  public Object getItem(int position) {
    return list.get(position);
  }
  @Override
  public long getItemId(int position) {
    return position;
  }
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    final AppInfo appInfo = list.get(position);
    final ViewHolder holder;
    if (convertView == null) {
      holder = new ViewHolder(context);
    } else {
      holder = (ViewHolder) convertView.getTag();
    }
    holder.setData(appInfo);
    mDisplayedHolders.add(holder);
    return holder.getRootView();
  }
  @Override
  public void onDownloadStateChanged(DownloadInfo info) {
    refreshHolder(info);
  }
  @Override
  public void onDownloadProgressed(DownloadInfo info) {
    refreshHolder(info);
  }
  public List<ViewHolder> getDisplayedHolders() {
    synchronized (mDisplayedHolders) {
      return new ArrayList<ViewHolder>(mDisplayedHolders);
    }
  }
  public void clearAllItem() {
    if (list != null){
      list.clear();
    }
    if (mDisplayedHolders != null) {
      mDisplayedHolders.clear();
    }
  }
  public void addItems(ArrayList<AppInfo> infos) {
    list.addAll(infos);
  }
  private void refreshHolder(final DownloadInfo info) {
    List<ViewHolder> displayedHolders = getDisplayedHolders();
    for (int i = 0; i < displayedHolders.size(); i++) {
      final ViewHolder holder = displayedHolders.get(i);
      AppInfo appInfo = holder.getData();
      if (appInfo.getId() == info.getId()) {
        AppUtil.post(new Runnable() {
          @Override
          public void run() {
            holder.refreshState(info.getDownloadState(),
                info.getProgress());
          }
        });
      }
    }
  }
  public class ViewHolder {
    public TextView textView01;
    public TextView textView02;
    public TextView textView03;
    public TextView textView04;
    public ImageView imageView_icon;
    public Button button;
    public LinearLayout linearLayout;
    public AppInfo mData;
    private DownloadManager mDownloadManager;
    private int mState;
    private float mProgress;
    protected View mRootView;
    private Context context;
    private boolean hasAttached;
    public ViewHolder(Context context) {
      mRootView = initView();
      mRootView.setTag(this);
      this.context = context; 

    }
    public View getRootView() {
      return mRootView;
    }
    public View initView() {
      View view = AppUtil.inflate(R.layout.item_recommend_award);
      imageView_icon = (ImageView) view
          .findViewById(R.id.imageview_task_app_cion);
      textView01 = (TextView) view
          .findViewById(R.id.textview_task_app_name);
      textView02 = (TextView) view
          .findViewById(R.id.textview_task_app_size);
      textView03 = (TextView) view
          .findViewById(R.id.textview_task_app_desc);
      textView04 = (TextView) view
          .findViewById(R.id.textview_task_app_love);
      button = (Button) view.findViewById(R.id.button_task_download);
      linearLayout = (LinearLayout) view
          .findViewById(R.id.linearlayout_task);
      button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
          System.out.println("mState:173  "+mState);
          if (mState == DownloadManager.STATE_NONE
              || mState == DownloadManager.STATE_PAUSED
              || mState == DownloadManager.STATE_ERROR) {
            mDownloadManager.download(mData);
          } else if (mState == DownloadManager.STATE_WAITING
              || mState == DownloadManager.STATE_DOWNLOADING) {
            mDownloadManager.pause(mData);
          } else if (mState == DownloadManager.STATE_DOWNLOADED) {
//           tell2Server();
            mDownloadManager.install(mData);
          }
        }
      });
      return view;
    } 

    public void setData(AppInfo data) {
      if (mDownloadManager == null) {
        mDownloadManager = DownloadManager.getInstance();
      }
       String filepath= FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + data.getName() + ".apk";
        boolean existsFile = FileUtil.isExistsFile(filepath);
        if(existsFile){
          int fileSize = FileUtil.getFileSize(filepath);
          if(data.getSize()==fileSize){
            DownloadInfo downloadInfo = DownloadInfo.clone(data);
            downloadInfo.setCurrentSize(data.getSize());
            downloadInfo.setHasFinished(true);
            mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
          }
//         else if(fileSize>0){
//           DownloadInfo downloadInfo = DownloadInfo.clone(data);
//           downloadInfo.setCurrentSize(data.getSize());
//           downloadInfo.setHasFinished(false);
//           mDownloadManager.setDownloadInfo(data.getId(),downloadInfo );
//         }
        }
      DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(data
          .getId());
      if (downloadInfo != null) {
        mState = downloadInfo.getDownloadState();
        mProgress = downloadInfo.getProgress();
      } else {
        mState = DownloadManager.STATE_NONE;
        mProgress = 0;
      }
      this.mData = data;
      refreshView();
    }
    public AppInfo getData() {
      return mData;
    }
    public void refreshView() {
      linearLayout.removeAllViews();
      AppInfo info = getData();
      textView01.setText(info.getName());
      textView02.setText(FileUtil.FormetFileSize(info.getSize()));
      textView03.setText(info.getDes());
      textView04.setText(info.getDownloadNum() + "下载量);
      finalBitmap.display(imageView_icon, info.getIconUrl()); 

      if (info.getType().equals("0")) {
//       mState = DownloadManager.STATE_READ;
        textView02.setVisibility(View.GONE);
      }else{
        String path=FileUtil.getDownloadDir(AppUtil.getContext()) + File.separator + info.getName() + ".apk";
        hasAttached = FileUtil.isValidAttach(path, false);
        DownloadInfo downloadInfo = mDownloadManager.getDownloadInfo(info
            .getId());
        if (downloadInfo != null && hasAttached) {
          if(downloadInfo.isHasFinished()){
            mState = DownloadManager.STATE_DOWNLOADED;
          }else{
            mState = DownloadManager.STATE_PAUSED;
          }
        } else {
          mState = DownloadManager.STATE_NONE;
          if(downloadInfo !=null){
            downloadInfo.setDownloadState(mState);
          }
        }
      }
      refreshState(mState, mProgress);
    }
    public void refreshState(int state, float progress) {
      mState = state;
      mProgress = progress;
      switch (mState) {
      case DownloadManager.STATE_NONE:
        button.setText(R.string.app_state_download);
        break;
      case DownloadManager.STATE_PAUSED:
        button.setText(R.string.app_state_paused);
        break;
      case DownloadManager.STATE_ERROR:
        button.setText(R.string.app_state_error);
        break;
      case DownloadManager.STATE_WAITING:
        button.setText(R.string.app_state_waiting);
        break;
      case DownloadManager.STATE_DOWNLOADING:
        button.setText((int) (mProgress * 100) + "%");
        break;
      case DownloadManager.STATE_DOWNLOADED:
        button.setText(R.string.app_state_downloaded);
        break;
//     case DownloadManager.STATE_READ:
//       button.setText(R.string.app_state_read);
//       break;
      default:
        break;
      }
    }
  }
}

何时 注册 监听observer
里面代码有点多,那就看startObserver()方法做了什么。

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

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

public interface DownloadObserver {
  public void onDownloadStateChanged(DownloadInfo info);
  public void onDownloadProgressed(DownloadInfo info);
}

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

两个抽象方法,而我们在 adapter

@Override
 public void onDownloadStateChanged(DownloadInfo info) {
   refreshHolder(info);
 }
 @Override
 public void onDownloadProgressed(DownloadInfo info) {
   refreshHolder(info);
 }

中实现了 这两个方法 就可以轻松的控制 去 刷新 和改变 下载状态了。
细心的朋友 或许 发现问题了,对,我们还没有注册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);
}
}
}
[java] view plaincopy
/** 反注册观察者 */
public void unRegisterObserver(DownloadObserver observer) {
  synchronized (mObservers) {
    if (mObservers.contains(observer)) {
      mObservers.remove(observer);
    }
  }
}

所以最后一步,因为 adapter 方法中有 startObserver, 所以 我们在 主界面 MainActivity 的类中调用 adapter.startObser() 将 实现了 接口的adapter 对象 添加到 Observer 容器中 就可以了。
OK。大功告成!
=============================================
DownloadManager 代码
这里 贴一下DownloadManager 代码

public class DownloadManager {
  public static final int STATE_NONE = 0;
  /** 等待中 */
  public static final int STATE_WAITING = 1;
  /** 下载中 */
  public static final int STATE_DOWNLOADING = 2;
  /** 暂停 */
  public static final int STATE_PAUSED = 3;
  /** 下载完毕 */
  public static final int STATE_DOWNLOADED = 4;
  /** 下载失败 */
  public static final int STATE_ERROR = 5;
  // public static final int STATE_READ = 6;
  private static DownloadManager instance;
  private DownloadManager() {
  }
  /** 用于记录下载信息,如果是正式项目,需要持久化保存 */
  private Map<Long, DownloadInfo> mDownloadMap = new ConcurrentHashMap<Long, DownloadInfo>();
  /** 用于记录观察者,当信息发送了改变,需要通知他们 */
  private List<DownloadObserver> mObservers = new ArrayList<DownloadObserver>();
  /** 用于记录所有下载的任务,方便在取消下载时,通过id能找到该任务进行删除 */
  private Map<Long, DownloadTask> mTaskMap = new ConcurrentHashMap<Long, DownloadTask>();
  public static synchronized DownloadManager getInstance() {
    if (instance == null) {
      instance = new DownloadManager();
    }
    return instance;
  }
  /** 注册观察者 */
  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);
      }
    }
  }
  /** 当下载状态发送改变的时候回调 */
  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);
      }
    }
  }
  /** 下载,需要传入一个appInfo对象 */
  public synchronized void download(AppInfo appInfo) {
    // 先判断是否有这个app的下载信息
    DownloadInfo info = mDownloadMap.get(appInfo.getId());
    if (info == null) {// 如果没有,则根据appInfo创建一个新的下载信息
      info = DownloadInfo.clone(appInfo);
      mDownloadMap.put(appInfo.getId(), info);
    }
    // 判断状态是否为STATE_NONE、STATE_PAUSED、STATE_ERROR。只有这3种状态才能进行下载,其他状态不予处理
    if (info.getDownloadState() == STATE_NONE
        || info.getDownloadState() == STATE_PAUSED
        || info.getDownloadState() == STATE_ERROR) {
      // 下载之前,把状态设置为STATE_WAITING,因为此时并没有产开始下载,只是把任务放入了线程池中,当任务真正开始执行时,才会改为STATE_DOWNLOADING
      info.setDownloadState(STATE_WAITING);
      notifyDownloadStateChanged(info);// 每次状态发生改变,都需要回调该方法通知所有观察者
      DownloadTask task = new DownloadTask(info);// 创建一个下载任务,放入线程池
      mTaskMap.put(info.getId(), task);
      ThreadManager.getDownloadPool().execute(task);
    }
  }
  /** 暂停下载 */
  public synchronized void pause(AppInfo appInfo) {
    stopDownload(appInfo);
    DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
    if (info != null) {// 修改下载状态
      info.setDownloadState(STATE_PAUSED);
      notifyDownloadStateChanged(info);
    }
  }
  /** 取消下载,逻辑和暂停类似,只是需要删除已下载的文件 */
  public synchronized void cancel(AppInfo appInfo) {
    stopDownload(appInfo);
    DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
    if (info != null) {// 修改下载状态并删除文件
      info.setDownloadState(STATE_NONE);
      notifyDownloadStateChanged(info);
      info.setCurrentSize(0);
      File file = new File(info.getPath());
      file.delete();
    }
  }
  /** 安装应用 */
  public synchronized void install(AppInfo appInfo) {
    stopDownload(appInfo);
    DownloadInfo info = mDownloadMap.get(appInfo.getId());// 找出下载信息
    if (info != null) {// 发送安装的意图
      Intent installIntent = new Intent(Intent.ACTION_VIEW);
      installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      installIntent.setDataAndType(Uri.parse("file://" + info.getPath()),
          "application/vnd.android.package-archive");
      AppUtil.getContext().startActivity(installIntent);
    }
    notifyDownloadStateChanged(info);
  }
  /** 启动应用,启动应用是最后一个 */
  public synchronized void open(AppInfo appInfo) {
    try {
      Context context = AppUtil.getContext();
      // 获取启动Intent
      Intent intent = context.getPackageManager()
          .getLaunchIntentForPackage(appInfo.getPackageName());
      context.startActivity(intent);
    } catch (Exception e) {
    }
  }
  /** 如果该下载任务还处于线程池中,且没有执行,先从线程池中移除 */
  private void stopDownload(AppInfo appInfo) {
    DownloadTask task = mTaskMap.remove(appInfo.getId());// 先从集合中找出下载任务
    if (task != null) {
      ThreadManager.getDownloadPool().cancel(task);// 然后从线程池中移除
    }
  }
  /** 获取下载信息 */
  public synchronized DownloadInfo getDownloadInfo(long id) {
    return mDownloadMap.get(id);
  }
  public synchronized void setDownloadInfo(long id, DownloadInfo info) {
    mDownloadMap.put(id, info);
  }
  /** 下载任务 */
  public class DownloadTask implements Runnable {
    private DownloadInfo info;
    public DownloadTask(DownloadInfo info) {
      this.info = info;
    }
    @Override
    public void run() {
      info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
      notifyDownloadStateChanged(info);
      File file = new File(info.getPath());// 获取下载文件
      HttpResult httpResult = null;
      InputStream stream = null;
      if (info.getCurrentSize() == 0 || !file.exists()
          || file.length() != info.getCurrentSize()) {
        // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
        info.setCurrentSize(0);
        file.delete();
      }
      httpResult = HttpHelper.download(info.getUrl());
      // else {
      // // //文件存在且长度和进度相等,采用断点下载
      // httpResult = HttpHelper.download(info.getUrl() + "&range=" +
      // info.getCurrentSize());
      // }
      if (httpResult == null
          || (stream = httpResult.getInputStream()) == null) {
        info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态
        notifyDownloadStateChanged(info);
      } else {
        try {
          skipBytesFromStream(stream, info.getCurrentSize());
        } catch (Exception e1) {
          e1.printStackTrace();
        }
        FileOutputStream fos = null;
        try {
          fos = new FileOutputStream(file, true);
          int count = -1;
          byte[] buffer = new byte[1024];
          while (((count = stream.read(buffer)) != -1)
              && info.getDownloadState() == STATE_DOWNLOADING) {
            // 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
            fos.write(buffer, 0, count);
            fos.flush();
            info.setCurrentSize(info.getCurrentSize() + count);
            notifyDownloadProgressed(info);// 刷新进度
          }
        } catch (Exception e) {
          info.setDownloadState(STATE_ERROR);
          notifyDownloadStateChanged(info);
          info.setCurrentSize(0);
          file.delete();
        } finally {
          IOUtils.close(fos);
          if (httpResult != null) {
            httpResult.close();
          }
        }
        // 判断进度是否和app总长度相等
        if (info.getCurrentSize() == info.getAppSize()) {
          info.setDownloadState(STATE_DOWNLOADED);
          notifyDownloadStateChanged(info);
        } else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
          notifyDownloadStateChanged(info);
        } else {
          info.setDownloadState(STATE_ERROR);
          notifyDownloadStateChanged(info);
          info.setCurrentSize(0);// 错误状态需要删除文件
          file.delete();
        }
      }
      mTaskMap.remove(info.getId());
    }
  }
  public interface DownloadObserver {
    public abstract void onDownloadStateChanged(DownloadInfo info);
    public abstract void onDownloadProgressed(DownloadInfo info);
  }
  /* 重写了Inpustream 中的skip(long n) 方法,将数据流中起始的n 个字节跳过 */
  private long skipBytesFromStream(InputStream inputStream, long n) {
    long remaining = n;
    // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
    int SKIP_BUFFER_SIZE = 10000;
    // skipBuffer is initialized in skip(long), if needed.
    byte[] skipBuffer = null;
    int nr = 0;
    if (skipBuffer == null) {
      skipBuffer = new byte[SKIP_BUFFER_SIZE];
    }
    byte[] localSkipBuffer = skipBuffer;
    if (n <= 0) {
      return 0;
    }
    while (remaining > 0) {
      try {
        long skip = inputStream.skip(10000);
        nr = inputStream.read(localSkipBuffer, 0,
            (int) Math.min(SKIP_BUFFER_SIZE, remaining));
      } catch (IOException e) {
        e.printStackTrace();
      }
      if (nr < 0) {
        break;
      }
      remaining -= nr;
    }
    return n - remaining;
  }
}

有两点需要说明,关于点击暂停后,再继续下载有两种方式可以实现
第一种 点击暂停的时候 记录下载了 多少,然后 再点击 继续下载 时,告诉服务器, 让服务器接着 上次的数据 往本地传递,
代码是我们 DownloadTask 下载时候,判断一下

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

通过 range 来区分 当前的下载size.
服务器 处理的代码 也很简单 就是一句话
String range = req.getParameter(“range”); 拿到 range 判断 range 存在不存在。
如果不存在

FileInputStream stream = new FileInputStream(file);
     int count = -1;
     byte[] buffer = new byte[1024];
     while ((count = stream.read(buffer)) != -1) {
       SystemClock.sleep(20);
       out.write(buffer, 0, count);
       out.flush();
     }
     stream.close();
     out.close(); 

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

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

另一种方式是本地处理,这个demo 中就是本地处理的, 但是有一个问题, 因为 Java api的原因 ,inputStream.skip() 方法 并不能准确的 跳过多少个字节,
而是 小于你想要跳过的字节,所以 你要去遍历 一直到 满足你要跳过的字节 在继续写, 因为 这样的方法有一个缺点,就是在下载很大的文件,
比如文件大小20M ,当已经下载了15M 此时你去暂停,在继续下载,那么要跳过前面的15M 将会话费很多时间。

此实现方式还有很多缺陷,所以在实际中要下载大的文件,还是不能用。

--------------------------------------------------------------------- 改进版-------------------------------------------------------------------------------

先来介绍下这次改进的两点:

 第一点 ,前面说过 项目 只适合学习,作为商用的话, 效率不高,是因为当时点击暂停 ,在点击下载继续下载时候,如果文件前面下载部分较大,会比较慢,因为java 的 inputstream的 skip(longsize) 跳过字节 这个方法 并不能按照你 想要跳过的字节,而是跳过的往往是比较小的,所以要不断遍历,直到返回满足条件 ,比较耗时。打个比方,文件大小30M ,你下载了20M,你点了暂停然后继续点下载,就要跳过这20M,但是你用skip 方法 可能每次跳过4096 字节,这样要跳过20M的时间 就会很长。这样应该好理解。
第二点,原来 项目中,你这一次下载没有完成,下次在下载是删除掉原来的从新 下载,这次改成继续上次的地方接着下载。
吐槽下,关于下载,我最近一周 一直在看 开源的download, 但是 无奈水平有限,收获甚微,往往是看到最后 脑袋短路。大哭
这次改的方式比较简单,只改动了 项目中 DownloadManager 这个类。在来看下 DownloadManager这个类 的run 方法,
   

@Override
    public void run() {
      info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
      notifyDownloadStateChanged(info);
      File file = new File(info.getPath());// 获取下载文件
      HttpResult httpResult = null;
      InputStream stream = null;
      if (info.getCurrentSize() == 0 || !file.exists()
          || file.length() != info.getCurrentSize()) {
        // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
<span>        </span>info.setCurrentSize(0);
        file.delete();
      }
      httpResult = HttpHelper.download(info.getUrl());
      if (httpResult == null
          || (stream = httpResult.getInputStream()) == null) {
        info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态
        notifyDownloadStateChanged(info);
      } else {
        try {
          skipBytesFromStream(stream, info.getCurrentSize());
        } catch (Exception e1) {
          e1.printStackTrace();
        }
        FileOutputStream fos = null;
        try {
          fos = new FileOutputStream(file, true);
          int count = -1;
          byte[] buffer = new byte[1024];
          while (((count = stream.read(buffer)) != -1)
              && info.getDownloadState() == STATE_DOWNLOADING) {
            // 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度
            fos.write(buffer, 0, count);
            fos.flush();
            info.setCurrentSize(info.getCurrentSize() + count);
            notifyDownloadProgressed(info);// 刷新进度
          }
        } catch (Exception e) {
          info.setDownloadState(STATE_ERROR);
          notifyDownloadStateChanged(info);
          info.setCurrentSize(0);
          file.delete();
        } finally {
          IOUtils.close(fos);
          if (httpResult != null) {
            httpResult.close();
          }
        }
<span>        </span>// 判断进度是否和app总长度相等
        if (info.getCurrentSize() == info.getAppSize()) {
          info.setDownloadState(STATE_DOWNLOADED);
          notifyDownloadStateChanged(info);
        } else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
          notifyDownloadStateChanged(info);
        } else {
          info.setDownloadState(STATE_ERROR);
          notifyDownloadStateChanged(info);
          info.setCurrentSize(0);// 错误状态需要删除文件
          file.delete();
        }
      }
      mTaskMap.remove(info.getId());
    }

从服务器 返回的数据流  stream  最终是在 HttpHelper 这个类中 

HttpResponse response = httpClient.execute(requestBase, httpContext);//访问网络 

通过  httpclient 去联网请求的  。
我没有试过 httpclient    addHeader("Range", "bytes=" + begin + "-" + end); 可不可以进行继续下载。
而是改成了 通过 httpurlconnection 去请求数据
现在  的run()方法是这样的。
   

@Override
    public void run() {
      info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态
      notifyDownloadStateChanged(info);
      File file = new File(info.getPath());// 获取下载文件
      /**********************************************************/
//     try {
        try {
          URL url = new URL(info.getUrl());
          HttpURLConnection conn = (HttpURLConnection) url.openConnection();
          conn.setRequestMethod("GET");
          conn.setConnectTimeout(30000);
          conn.setReadTimeout(30000);
          if (!file.exists()) {
            info.setCurrentSize(0);
            file.delete();
          } else if (file.length() > info.getAppSize()) {
            info.setCurrentSize(0);
            file.delete();
          } else if (file.length() == info.getAppSize()) {
          } else if (file.length() < info.getAppSize()) {
            info.setCurrentSize(file.length());
          }
          if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) {
            // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
            info.setCurrentSize(0);
            file.delete();
          } else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) {
            conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize());
          }
          int code = conn.getResponseCode();
          RandomAccessFile raf = new RandomAccessFile(file, "rw");
          InputStream is = conn.getInputStream();
          byte[] buffer = new byte[1024 * 8];
          int len = -1;
          int total = 0;// 当前线程下载的总的数据的长度
          if (code == 200) {
          } else if (code == 206) {
            raf.seek(file.length());
          }
          while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下载数据的过程。
            raf.write(buffer, 0, len);
            total += len;// 需要记录当前的数据。
            info.setCurrentSize(info.getCurrentSize() + len);
            notifyDownloadProgressed(info);// 刷新进度
          }
          is.close();
          raf.close();
        } catch (MalformedURLException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        } catch (ProtocolException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        } catch (FileNotFoundException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
        /*************************对于各种情况,需要删除下载任务,从新下载的 请自己改动代码*****************************/
        // 判断进度是否和app总长度相等
//     } catch (Exception e) {
//       System.out.println(e.toString());
//       info.setDownloadState(STATE_ERROR);
//       info.setCurrentSize(0);
//       file.delete();
//       e.printStackTrace();
//     }
      if (info.getCurrentSize() == info.getAppSize()) {
        info.setDownloadState(STATE_DOWNLOADED);
        notifyDownloadStateChanged(info);
      } else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态
        notifyDownloadStateChanged(info);
      } else {
        info.setDownloadState(STATE_ERROR);
        notifyDownloadStateChanged(info);
        info.setCurrentSize(0);// 错误状态需要删除文件
        file.delete();
      }
      /**********************************************************/
      mTaskMap.remove(info.getId());
    }

先判断文件存不存在,以及大小是否满足条件, 在这里做判断

if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) {
  // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载
  info.setCurrentSize(0);
  file.delete();
  } else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) {
     conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize());
   }

如果 文件当前大小为0,或者文件不存在,或者长度不等于当前长度,则重新下载,否则 设置 Range
下面 判断 code  正常情况下code =200 表示成功,如果 设置了Range  那么 code 返回 206 表示正常。这个时候我们通过RandomAccessFile
RandomAccessFile  这个 类实现了 RandomAccessFile implements DataInput, DataOutput,就是一个既可以读也可以写的类。
RandomAccessFile 这个类来 处理 跳过多少字节, 前面 我说过 inpuStream.skeep() 方法 不准确,但是  RandomAccessFile  这个类是可以的。

RandomAccessFile raf = new RandomAccessFile(file, "rw");
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024 * 8];
int len = -1;
int total = 0;// 当前线程下载的总的数据的长度
if (code == 200) {
} else if (code == 206) {
raf.seek(file.length());
}

通过 seek 方法 跳过 这些字节。
然后

while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下载数据的过程。
raf.write(buffer, 0, len);
total += len;// 需要记录当前的数据。
info.setCurrentSize(info.getCurrentSize() + len);
notifyDownloadProgressed(info);// 刷新进度
}
is.close();
raf.close(); 

很普通的代码,把数据写出去。不断刷新当前进度, 最后关闭流。
这样就可以保证快速的暂停继续下载,并且 本次下载 没有完成,点了暂停,下次进应用,继续下载的时候 会接着上一次下载,但是断网,或者你自己把网关掉 ,下次在恢复网络,或者 在点下载,我并没有处理,有需要的就自己处理下吧,应该是捕获异常 seckouttimeException,然后保存数据。自己动手试下就知道了。

本次就到这里,希望对大家学习Android版多线程下载 仿下载助手(最新)有所启发。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android多线程下载
, android多线程
android多线程学习
android 多线程、android 多线程并发、android多线程编程、android 多线程下载、android 多线程锁,以便于您获取更多的相关知识。

时间: 2024-11-05 12:15:13

Android版多线程下载 仿下载助手(最新)_Android的相关文章

Android版多线程下载 仿下载助手(最新)

首先声明一点: 这里的多线程下载并不是指多个线程下载一个 文件,而是每个线程负责一个文件,今天给大家分享一个多线程下载的 例子.先看一下效果,点击下载开始下载,同时显示下载进度,下载完成,变成程安装,点击安装提示安装应用. 界面效果图: 线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用 /** * Parameters: corePoolSize the number of threads to keep in the pool, even if they are id

Android版谷歌地图新增“下载图区”功能

新浪科技讯 北京时间7月8日上午消息,谷歌周四在Android版谷歌地图(Google Maps For Android)中增加了"下载图区"(Download map area)功能.谷歌官方博客当天撰文,除了详细介绍这项新功能外,还提到了另外两项谷歌地图已有的功能--"比例尺"(Scale bar)和"测量"(Measure). 下载图区 当用户来到某个陌生地方时,移动版谷歌地图会为他们提供诸多导航消息,如距离目的地有多远,街道和地标相互之间

Android使用多线程实现断点下载_Android

多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务..可以使任务的执行速度变快..多线程的任务下载时常都会使用得到..比如说我们手机内部应用宝的下载机制..一定是通过使用了多线程创建的下载器..并且这个下载器可以实现断点下载..在任务被强行终止之后..下次可以通过触发按钮来完成断点下载...那么如何实现断点下载这就是一个问题了..   首先我们需要明确一点就是多线程下载器通过使用多个线程对同一个任务进行下载..但是这个多线程并不是线程的数目越多,下载的速度就越快..当线程增加的很

Android使用多线程实现断点下载

多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务..可以使任务的执行速度变快..多线程的任务下载时常都会使用得到..比如说我们手机内部应用宝的下载机制..一定是通过使用了多线程创建的下载器..并且这个下载器可以实现断点下载..在任务被强行终止之后..下次可以通过触发按钮来完成断点下载...那么如何实现断点下载这就是一个问题了.. 首先我们需要明确一点就是多线程下载器通过使用多个线程对同一个任务进行下载..但是这个多线程并不是线程的数目越多,下载的速度就越快..当线程增加的很多的

免费不限流量!Android 版 Opera VPN 开放下载

Opera今年5月在iOS平台上推出的免费.不限流量VPN即日起已可在Google Play商店下载(点我下载),支持Android设备使用,可让用户提升手机上网隐私.阻挡广告追踪Cookies.存取更多的网络内容.增加App的跨区域性,以及测试Wi-Fi网络的联机安全性.Android专用的Opera VPN支持英文.阿拉伯文.法文.德文.印度尼西亚文.日文.葡萄牙文.俄文.波兰文.西班牙文等语系,目前尚未提供中文语系. Opera VPN服务主要是由Opera的子公司SurfEasy来提供,

Android+Html5混合开发仿微信朋友圈_Android

开发之前大约从去年开始吧, 也可能是前年 Html5好像火得不得了, 不得了...总能从网上听说到 XXX混合开发, 为了紧跟潮流(虽然有点儿晚了), 咱们也看看Android+Html5混合开发是怎样的! 今天带来的案例是微信的朋友圈, 因为我觉得是微信把H5给"捧红了". 不过丑话说在前头, 咱们的仿朋友圈可是"低仿", 只是把混合开发的大致流程说说, 界面可能不堪入目...见谅.. 开发环境Android Studio 2.2.2 JDK1.7 API 24

谷歌升级iOS和Android版谷歌地图

5月7日,谷歌对iOS和Android版谷歌地图进行升级,其中iOS版版本号是3.0,而Android版版本号是8.0.最新版本中对离线地图的控制进行优化,同时提供了一系列有用的导航功能. 现在iOS版和Android版谷歌地图的功能已经差不多了.在最新版本中,用户可以选择某一区域,调整视图,随后自主命名这一区域.应用将在用户个人档案中列出所有离线地图,而用户可以对这些离线地图进行编辑或删除. 在最新版应用中,方向导航功能仍需要网络连接,但得到了大幅改进.新版谷歌地图提供了车道助手,帮助用户避免

Android开发仿下载助手多线程下载及原理

仿下载助手界面效果 开发仿下载助手多线程下载及原理-android 双开助手原理"> 线程池 ThreadPoolExecutor 在下面介绍实现下载原理的时候,我想尝试倒着来说,这样是否好理解一点? 我们都知道,下载助手,比如360, 百度的 手机助手,下载APP 的时候 ,都可以同时下载多个,所以,下载肯定是多线程的,所以我们就需要一个线程工具类 来管理我们的线程,这个工具类的核心,就是 线程池. 线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用  /**  

PP助手安卓版亮相 全新应用下载平台

中介交易 SEO诊断 淘宝客 云主机 技术大厅 据IDC(互联网数据中心)公布的最新数据显示,截至2013年第二季度,Android占据智能手机市场79.3%的比率,Android操作系统第二季度依旧是智能手机市场的巨头.每10部手机就有8部是安卓系统,新机入手,免不了要进行调试体验,应用的运行是检验手机性能的重要方法之一,这时只需要一个手机助手就能轻松搞定! PP助手安卓版,安卓平台一位全新的手机助手,它将凭借海量资源.清新界面.飞速下载.贴心管理等优点,为广大安卓用户带来更加安全便捷的应用下