看效果图

android 观察者 观察者apk下载_下载文件

项目结构图

android 观察者 观察者apk下载_android 观察者_02

这个Demo 主要是通过线程池管理ThreadPoolManager 控制 下载任务线程 DownLoadManager.DownLoadTask , 下载任务线程通过HttpUrlConnection 去网络加载数据 ,下载状态更基于下载观察者 DownLoadManager 去刷新 对应的 UI控件

每一个下载任务是有唯一性的,可以通过 判断下载Url或者 下载id是否一样即可认为是同一个下载任务
我这里是通过下载id ,下载JavaBean 如下:

public class DownloadInfo implements Serializable {
    /*****数据库自增 id****/
    public int _ID;
    /***下载任务id 下载任务唯一标识  , 也可以直接用下载地址****/
    public int id;
    /**** 下载状态*****/
    public int downloadState = DownLoadManager.STATE_NONE;// 下载的状态
    /*****下载连接****/
    public String downloadUrl;
    /****下载文件名****/
    public String fileName;
    /*****下载文件保存路径****/
    public String fileSavePath;
    /****当前下载状态***/
    public int progress;
    /****文件的长度****/
    public int fileLength;
    /***文件的icon*****/
    public String icon;
    /***文件后缀名  .apk 或者 .mp4*****/
    public String houzui;
     @Override
    public boolean equals(Object obj) {
        if (obj!=null && obj instanceof DownloadInfo){
            DownloadInfo newObj = (DownloadInfo) obj;
            return newObj.id==id;
        }
        return super.equals(obj);
    }
    }

一般情况下,多线程下载包括多个下载状态, 每个下载状态对应不同的UI 表现

下载状态如下:

/**
 * 下载管理类
 * Created by YZJ on 2017/6/8: 19.
 */
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_FINISH      = 4;
    /*** 下载失败*/
    public static final int                        STATE_ERROR       = 5;
    /*** 已经安装 ,针对于apk*/
    public static final int                        STATE_INSTALLED   = 6;
    }

下载线程池 ThreadPoolManager:

/**
 *
 * 下载线程池
 * Created by YZJ on 2017/6/8: 19.
 */
public class ThreadPoolManager {
    private final static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());
    /*** 添加一个任务**/
    public static void execute(BaseLoadTask task) {
        if (task != null && !task.isCancle() && !executor.isShutdown()) {
            executor.execute(task);
        }
    }
    /*** 取消下载任务*/
    public static void cancel(BaseLoadTask task) {
        if (task != null && !executor.isShutdown()){
            task.cancle();
            executor.getQueue().remove(task);
        }
    }

下载任务线程线程:

/**
 * Created by YZJ on 2017/6/8: 21.
 *   下载任务基类
 */
public abstract class BaseLoadTask implements Runnable {
    /***是否任务已经取消****/
    private boolean cancle = false;
    /***下载出错任务重复次数 (主要是网络异常导致的,并且没有取消)****/
    private int retryTimes = 0;
    public boolean isCancle(){
        return cancle;
    }
    /****取消下载任务****/
    public void cancle(){
        cancle = true;
    }
    public void reduceRetryTimes(){
        if (retryTimes==0){
            return;
        }
        retryTimes--;
    }
    public int getRetryTimes() {
        return retryTimes;
    }
}

    /*****下载管理类的 内部类 DownLoadManager.DownLoadTask下载任务工作线程*****/
    public class DownLoadTask extends BaseLoadTask {
        private DownloadInfo info;

        public DownLoadTask(DownloadInfo info) {
            /******克隆一个新的对象,最好不要在线程里持有原有的对象,导致其无法回收******/
            this.info = info.clone();
        }

        @Override
        public void run() {
            info.downloadState = STATE_DOWNLOADING;
            notifyDownloadProgressed(info);
            downLoad();
        }

        /*******下载********/
        void downLoad() {
            if (isCancle()) {
                /****任务已经被取消*****/
                info.downloadState = STATE_PAUSED;
                notifyDownloadProgressed(info);
                return;
            }
            /******下载文件大小****/
            int fileSize = 0;
            /******已经下载的文件大小*****/
            int hasLoadSize = 0;
            FileInputStream fis = null;
            HttpURLConnection connection = null;
            try {
                /******下载文件根目录****/
                String loadpathRoot = DownloadUtil.getLoadPathRoot();
                /*****临时下载文件后缀名为 .tmp  ,下载完成后,要记得 把 .tmp去掉*****/
                File tmpFile = new File(loadpathRoot, info.fileName + info.houzui + DownloadUtil.loadTmpName);
                if (tmpFile.exists()) {
                    /********读取之前下载的文件*******/
                    fis = new FileInputStream(tmpFile);
                    int hasDownSize = fis.available();
                    hasLoadSize += hasDownSize;
                }
                URL url = new URL(info.downloadUrl);
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                /*****连接超时时间 1分钟*****/
                connection.setConnectTimeout(60 * 1000);
                /*****读取超时时间 1分钟*****/
                connection.setReadTimeout(60 * 1000);
                // connection.setRequestProperty("Range", "bytes=" + startIndex+ "-" + fileSize);
                /*****开启断点续传*****/
                connection.setRequestProperty("Range", "bytes=" + hasLoadSize + "-");
                connection.connect();
                int responseCode = connection.getResponseCode();
                /****服务器请求全部资源 200   服务器请求部分资源 206 ****/
                if (responseCode == 206 || 200 == responseCode) {
                    InputStream is = connection.getInputStream();
                    /*********** 文件大小 = 未下载完的大小 + 已经下载完的大小  ****************/
                    fileSize = connection.getContentLength() + hasLoadSize;
                    info.fileLength = fileSize;
                    RandomAccessFile randomAccessFile = new RandomAccessFile(tmpFile, "rw");
                    randomAccessFile.seek(hasLoadSize);
                    int tmpLen = -1;
                    /*****已经下载的文件大小*****/
                    int currentSize = hasLoadSize;
                    /*******记录上一次刷新时下载的大小******/
                    int preCurrentSize = 0;
                    byte[] buffer = new byte[8192];
                    /********* 有数据 ,并且没有被取消就是写文件数据**************/
                    while ((tmpLen = is.read(buffer)) != -1 && !isCancle()) {
                        currentSize += tmpLen;

                        randomAccessFile.write(buffer, 0, tmpLen);

                        info.progress = currentSize;
                        /*******通知所有下载观察者*******/
                        notifyDownloadProgressed(info);
                        preCurrentSize = currentSize;
                        float persent = (float) ((currentSize + 0.0) / fileSize * 100);
                       // Log.i("yzjTag", "当前大小: " + currentSize + " , 总大小: " + //fileSize + " , 百分比: " + persent);


                    }
                    is.close();
                    randomAccessFile.close();
                    /****标记任务已经取消***/
                    cancle();
                    /*****下载完成 ,当前下载字节刚好当于 文件大小,算是下载成功 ,否则算是下载失败******/
                    if (currentSize == fileSize) {
                        info.downloadState = STATE_FINISH;
                        // 下载完成,修改后缀名,把后缀名去掉
                        DownloadUtil.removeTmpName(tmpFile);
                        stopDownload(info);
                    } else if (currentSize > fileSize) {
                        /***下载错误 ,下载字节大小已经 大于文件大小 ,把文件删除,要重新下载***/
                        info.downloadState = STATE_ERROR;
                        tmpFile.delete();
                        info.progress = 0;
                        stopDownload(info);
                    } else {
                        /******暂停*******/
                        info.downloadState = STATE_PAUSED;
                        stopDownload(info);
                    }

                }
            } catch (Exception e) {
                /***下载异常 没有被取消且重试次数要大于一 则开启重试, **/
                if (!isCancle() && getRetryTimes() >= 1) {
                    /****重试次数减一***/
                    reduceRetryTimes();
                    if (connection != null) {
                        connection.disconnect();
                        connection = null;
                    }
                    DownloadUtil.closeIo(fis);
                    downLoad();
                } else {
                    /****网络异常取消任务****/
                    cancle();
                    info.downloadState = STATE_ERROR;
                    /******,同时从下载队列移除*********/
                    stopDownload(info);
                }
            } finally {
                cancle();
                if (connection != null) {
                    connection.disconnect();
                    connection = null;
                }
                DownloadUtil.closeIo(fis);
            }
        }
    }

在 下载任务线程中 DownLoadManager.DownLoadTask,状态以及进度的刷新 均通过观察者去 通知 注册了 观察的UI 控件,比如 (ListView GridView)BaseAdapter ,(RecycleView) RecyclerView.Adapter

public class DownLoadTask extends BaseLoadTask {
 ...
     randomAccessFile.write(buffer, 0, tmpLen);
    info.progress = currentSize;
     /*******通知所有下载观察者*******/
     notifyDownloadProgressed(info);
     preCurrentSize = currentSize;
     float persent = (float) ((currentSize + 0.0) / fileSize * 100);
     .....
}

下载观察者管理 DownLoadManager

public class DownLoadManager {
...
  private static      List<DownloadInfo>         downloadInfos     = new ArrayList<>();
    /*** 存储所有观察者 为什么用CopyOnWriteArrayList ,而不是 ArrayList ,可以去看看源码,ArrayList观察者频繁刷新
     * 会抛出 ConcurrentModificationException  多线程问题,用线程安全的 集合
     * ConcurrentHashMap  替代 HashMap原理一样
     * */
    private             List<DownloadObserver>     downloadObservers = new CopyOnWriteArrayList<>();
    /**
     * 存储下载中的任务
     **/
    private             Map<Integer, DownLoadTask> downLoadTasksMap  = new ConcurrentHashMap<Integer, DownLoadTask>();

    /*******下载观察者******/
    public interface DownloadObserver {
        public void onDownloadStateProgressed(DownloadInfo updateInfo);
    }

    /******注册一个下载任务 观察者*******/
    public void registerObserver(DownloadObserver observer) {
        if (observer != null) {
            synchronized (downloadObservers) {
                if (!downloadObservers.contains(observer)) {
                    downloadObservers.add(observer);
                }
            }
        }
    }

    /******注销一个下载任务 观察者*******/
    public void unregisterObserver(DownloadObserver observer) {
        if (observer != null) {
            synchronized (downloadObservers) {
                int index = downloadObservers.indexOf(observer);
                if (index != -1) {
                    downloadObservers.remove(index);
                }
            }
        }
    }

    /******注销所有观察者*******/
    public void unregisterAll() {
        synchronized (downloadObservers) {
            downloadObservers.clear();
        }
    }

    /**
     * 当下载进度 和状态发送改变的时候回调
     */
    public void notifyDownloadProgressed(final DownloadInfo item) {
        /**
         * 用ArrayyList的话 java.util.ConcurrentModificationException
         */
        for (final DownloadObserver observer : downloadObservers) {

            MyApplication.getIntance().getHandler().post(new Runnable() {
                @Override
                public void run() {
                    observer.onDownloadStateProgressed(item);
                }
            });

        }

    }
....
 /*****下载任务工作线程*****/
 public class DownLoadTask extends BaseLoadTask{...}

///接下来就是 下载线程任务的 控制 ,暂停 ,继续 的操作了


    /*****取消所有下载任务*******/
    public synchronized void cancleAllTask() {
        for (DownLoadTask loadTask : downLoadTasksMap.values()) {
            if (loadTask != null) {
                /******取消对应的任务*******/
                ThreadPoolManager.cancel(loadTask);
                unregisterAll();
            }
        }
    }

    /*****开始下载一个任务*******/
    public synchronized void startDownload(DownloadInfo item) {
        if (item != null) {
            /*******先检查任务是否已经存在*******/
            if (downLoadTasksMap.containsKey(item.id)) {
                DownLoadTask downLoadTask = downLoadTasksMap.get(item.id);
                if (downLoadTask != null && !downLoadTask.isCancle()) {
                    /****,并且没有被取消,则 return******/
                    return;
                } else {
                    /****,任务取消,则先移除这个任务******/
                    ThreadPoolManager.cancel(downLoadTask);
                }
            }

            /*******开启新的下载任务*********/
            DownLoadTask downLoadTask = new DownLoadTask(item);
            /*****放入任务集合*****/
            downLoadTasksMap.put(item.id, downLoadTask);
            ThreadPoolManager.execute(downLoadTask);
            item.downloadState = STATE_WAITING;
            notifyDownloadProgressed(item);
        }
    }

    /*** 暂停或者取消任务*/
    public synchronized void stopDownload(DownloadInfo item) {
        if (item != null) {// 修改下载状态
            notifyDownloadProgressed(item);
            DownLoadTask task = downLoadTasksMap.remove(item.id);// 先从集合中找出下载任务
            if (task != null) {
                ThreadPoolManager.cancel(task);// 然后从线程池中移除
            }
        }

    }
}

上面的观察者,接下来需要 被观察者,也就是 UI控件需要注册观察者. 主要是这些控件(ListView GridView)BaseAdapter ,(RecycleView) RecyclerView.Adapter

观察者注册

@Override
    protected void onCreate(Bundle savedInstanceState) {
        /**** 注册观察者******/
        adapter = new     LoadBaseAdapter(getActivity(),MyApplication.getIntance().downloadInfos,listView);
        listView.setAdapter(adapter);
        /******注册下载通知观察者*****/
        adapter.registerDownObserver();
  }
  @Override
    protected void onDestroy() {
        super.onDestroy();
         /****注销下载通知观察者***/
        if (adapter!=null){
            adapter.unRegisterDownObserver();
        }
    }

然后就是 UI控件下载状态的更新了.
对于RecycleView 刷新item比较简单,RecycleView直接支持

public class LoadRecycleViewAdapter extends RecyclerView.Adapter<LoadRecycleViewAdapter.LoadHolder> implements DownLoadManager.DownloadObserver {

    /*******注册观察者*******/
    public void registerDownObserver() {
        DownLoadManager.getDownInstance().registerObserver(this);
    }

    /*******注销观察者*******/
    public void unRegisterDownObserver() {
        DownLoadManager.getDownInstance().unregisterObserver(this);
    }

    @Override
    public void onDownloadStateProgressed(final DownloadInfo updateInfo) {
        final int index = datas.indexOf(updateInfo);
        if (index >= 0) {
            DownloadInfo item = datas.get(index);
            item.updateDownloadInfo(updateInfo);
            notifyItemChanged(index);

        }
}

而对于ListView GridView 单个item刷新是比较麻烦点的 .网上也有很多种刷新的方法. 这里刷新方法是 ,在
BaseAdapter 中getView 方法时,把Holder 存在集合list里 ,但是也不能无限的往集合里面堆,Holder 也是需要回收的 .我们可以在ListView回收 View (View 绑定了Holder)的回调方法中,把 对应的Holder 从集合移除掉. 同时Holder 也绑定 唯一的DownloadInfo

接着就是从下载观察者回调中 得到 下载DownloadInfo, 遍历集合List (Holder> ,当Holder 绑定DownloadInfo 和当前的 载观察者回调中DownloadInfo时,刷新Holder

public class LoadBaseAdapter extends AbsAdapter<DownloadInfo> implements DownLoadManager.DownloadObserver, AbsListView.RecyclerListener {
    public LoadBaseAdapter(Context context, List<DownloadInfo> data, ListView listView) {
        super(context, data);
        if (listView != null) {
            //ListView itemView 回收监听
            listView.setRecyclerListener(this);
        }
        holders = new CopyOnWriteArrayList<>();
    }
   /*****ListView 触发回收View的时候 ,同时回收我们的Holder****/
    @Override
    public void onMovedToScrapHeap(View view) {
        // recycleViewBin
        if (view != null && view.getTag() instanceof Holder) {
            Holder holder = (Holder) view.getTag();
            synchronized (holders) {
                holders.remove(holder);
            }
        }
    }
  @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        .....
        holder.setData(item);
        /**** 添加 Holder*****/
        if (!holders.contains(holder)) {
            holders.add(holder);
        }
        ....
        return convertView;
    }
 @Override
    public void onDownloadStateProgressed(final DownloadInfo updateInfo) {
        List<Holder> holders = getHolders();
        for (final Holder holder : holders) {
            final DownloadInfo item = holder.item;
            if (item != null && item.equals(updateInfo)) {
                 /**** 更新item *****/
                 item.updateDownloadInfo(updateInfo);
                 /**** Holdr 更新*****/
                 holder.update();

            }
        }
    }
}
/*******注册观察者*******/
    public void registerDownObserver() {
        DownLoadManager.getDownInstance().registerObserver(this);
    }

    /*******注销观察者*******/
    public void unRegisterDownObserver() {
        DownLoadManager.getDownInstance().unregisterObserver(this);
    }

Holder

class Holder implements View.OnClickListener {
        private DownloadInfo item;
public void setData(DownloadInfo item) {
....
            this.item = item;
            .....
}
   /********更新数据*********/
        public void update() {
            switch (item.downloadState) {
                case DownLoadManager.STATE_NONE:
                    pauseOrStart.setText(R.string.download);
                    break;
                case DownLoadManager.STATE_WAITING:
                    pauseOrStart.setText(R.string.wait);
                    break;
                case DownLoadManager.STATE_DOWNLOADING:
                    pauseOrStart.setText(R.string.stop);
                    progress.setProgress((int) (item.progress));
                    progress.setMax((int) (item.fileLength));
                    break;
                case DownLoadManager.STATE_PAUSED:
                    pauseOrStart.setText(R.string.resume);
                    break;
                case DownLoadManager.STATE_FINISH:
                    pauseOrStart.setVisibility(View.GONE);
                    install.setVisibility(View.VISIBLE);
                    progress.setVisibility(View.GONE);
                    break;
                case DownLoadManager.STATE_ERROR:
                    pauseOrStart.setText(R.string.retry);
                    break;
                case DownLoadManager.STATE_INSTALLED:
                    break;
                default:
                    break;
            }
        }
}

对于ListView Adapter,我同时弄了个基于 万能适配的回收监听