亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android版多線程下載 仿下載助手(最新)

2020-04-11 11:27:24
字體:
來源:轉載
供稿:網友

首先聲明一點: 這里的多線程下載并不是指多個線程下載一個 文件,而是每個線程負責一個文件,今天給大家分享一個多線程下載的 例子。先看一下效果,點擊下載開始下載,同時顯示下載進度,下載完成,變成程安裝,點擊安裝提示安裝應用。

界面效果圖:

線程池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版多線程下載 仿下載助手(最新)有所啟發。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
2019精品视频| 日韩免费在线电影| 欧美电影免费观看电视剧大全| 久久久久女教师免费一区| 国产一区二区美女视频| 日韩中文字幕第一页| 久久综合亚洲社区| 欧美精品久久久久久久久久| 久久久中精品2020中文| 亚洲欧美精品一区| 97人洗澡人人免费公开视频碰碰碰| 456国产精品| 国产精品永久在线| 欧美成年人网站| 亚洲视频在线免费观看| 国产精品第一区| 亚洲视频在线播放| 久久精品视频免费播放| 成人自拍性视频| 成人精品网站在线观看| 国产亚洲精品高潮| www.亚洲天堂| 欧美亚洲伦理www| 欧美一级淫片播放口| 亚洲国产精品久久久久久| 亚洲精品电影在线观看| 日本sm极度另类视频| 国产在线播放不卡| 国产视频欧美视频| 亚洲国产精品一区二区三区| 国产精品三级美女白浆呻吟| 麻豆国产va免费精品高清在线| 亚洲人成电影网站色…| 国产精品黄视频| 青草青草久热精品视频在线网站| 国产区精品在线观看| 久久高清视频免费| 九九热99久久久国产盗摄| 国产午夜精品免费一区二区三区| 性欧美亚洲xxxx乳在线观看| 成人精品一区二区三区电影免费| 欧美精品久久久久久久久| 亚洲直播在线一区| 国产精品自产拍在线观看| 不卡毛片在线看| 欧美床上激情在线观看| 91精品国产自产在线观看永久| 久久91亚洲精品中文字幕| 欧美成人合集magnet| 91av免费观看91av精品在线| 黄色一区二区三区| 日韩av在线影院| 亚洲欧美日韩国产中文专区| 精品亚洲va在线va天堂资源站| 2019中文在线观看| www.欧美视频| 精品国偷自产在线视频| 亚洲成色777777女色窝| 日韩有码片在线观看| 在线精品视频视频中文字幕| 欧美最猛性xxxxx亚洲精品| 91在线免费观看网站| 91av在线免费观看视频| 欧美极品欧美精品欧美视频| 亚洲a在线观看| 91九色蝌蚪国产| 亚洲一级黄色av| 久久亚洲综合国产精品99麻豆精品福利| 亚洲第一精品夜夜躁人人躁| 日韩一级裸体免费视频| 欧美激情一区二区三区在线视频观看| 亚洲高清在线观看| 91在线高清免费观看| 色综合91久久精品中文字幕| 欧美孕妇孕交黑巨大网站| 国产高清视频一区三区| 国产精品中文在线| 国产不卡精品视男人的天堂| 国产精品r级在线| 久久91精品国产91久久久| 国产精品旅馆在线| 日韩在线观看免费高清完整版| 亚洲欧美另类人妖| 亚洲精品成人久久电影| 日韩中文字在线| 亚洲男人7777| 日韩福利在线播放| 一区二区欧美在线| 欧美有码在线视频| 亚洲一区二区三区视频| 亚洲一区中文字幕在线观看| 久久久久久尹人网香蕉| 亚洲一区二区三区xxx视频| 久久精品精品电影网| 欧美日韩国产中文字幕| 欧美在线精品免播放器视频| 欧美在线欧美在线| 国产精彩精品视频| 国产在线播放91| 美日韩精品免费视频| 国产91成人在在线播放| 国产一区欧美二区三区| 性欧美亚洲xxxx乳在线观看| 国产成人拍精品视频午夜网站| 欧美日韩国产丝袜美女| 在线观看91久久久久久| 亚洲精品国精品久久99热一| 国产精品一区二区三区久久久| 懂色aⅴ精品一区二区三区蜜月| 国产精品久久久久久久久久久不卡| 久久91精品国产91久久跳| 成人xvideos免费视频| 久久精品一偷一偷国产| 日韩免费在线电影| 5252色成人免费视频| 亚洲欧美国内爽妇网| 欧美大片欧美激情性色a∨久久| 久久成人精品视频| 久久久视频在线| 国产精品自产拍高潮在线观看| 亚洲国产高潮在线观看| 国产91在线播放精品91| 欧美成人精品在线视频| 亚洲最新av在线| 精品福利在线视频| 91精品国产高清久久久久久| 国产成人精品优优av| 欧美在线www| 69影院欧美专区视频| 91九色单男在线观看| 亚洲аv电影天堂网| 精品国产一区久久久| 国产亚洲免费的视频看| 中文字幕日韩精品有码视频| 国产亚洲精品美女| 久久国产精彩视频| 日韩欧美aaa| 中文字幕亚洲欧美一区二区三区| 欧美亚洲一区在线| 91国产美女在线观看| 欧美激情第99页| 在线午夜精品自拍| 国产午夜精品理论片a级探花| 中文日韩在线观看| 成人做爽爽免费视频| 成人h猎奇视频网站| 91色p视频在线| 亚洲天堂av电影| 欧美成人免费观看| 2021久久精品国产99国产精品| www高清在线视频日韩欧美| 精品久久久国产| 欧美交受高潮1| 欧美日韩中文字幕综合视频| 欧美亚洲成人精品| 国产精品久久久久久久久| 精品一区二区三区四区在线| 永久555www成人免费| 亚洲一区二区三区四区视频| 免费91麻豆精品国产自产在线观看| 欧美中文字幕在线播放| 97精品视频在线播放| 国产精品一区二区久久久久| 97**国产露脸精品国产|