今天带来一个多线程下载的 样例。先看一下效果。点击 下载 開始下载,同一时候显示下载进度。完成下载,变成程 安装,点击安装 提示 安装应用。
界面效果
线程池 ThreadPoolExecutor
在以下介绍实现下载原理的时候。我想尝试倒着来说。这样是否好理解一点?
我们都知道。下载助手,比方360, 百度的 手机助手,下载APP 的时候 ,都能够同一时候下载多个。所以,下载肯定是多线程的。所以我们就须要一个线程工具类 来管理我们的线程,这个工具类的核心,就是 线程池。
线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用
[java] view plaincopy
- /**
- * 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
[java] view plaincopy
- 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 这个类去下载了。
[java] view plaincopy
- /** 下载任务 */
- 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());
- }
- }
下载的原理 非常easy。就是 通过 目标的URL 拿到流,然后写到本地。
由于下载在 run() 里面运行,这个DownloadTask 类 我们就看run() 方法的实现,所以 关键代码 就是以下一点点
[java] view plaincopy
- 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() 方法里面 载入的每一条数据的布局。
那就一起看下是不是这样子呢?
[java] view plaincopy
- 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()方法做了什么。
[java] view plaincopy
- 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中实现了 这两个方法 就能够轻松的控制 去 刷新 和改变 下载状态了。
[java] view plaincopy
- @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); } } }
/** 反注冊观察者 */ public void unRegisterObserver(DownloadObserver observer) { synchronized (mObservers) { if (mObservers.contains(observer)) { mObservers.remove(observer); } } }
所以最后一步,由于 adapter 方法中有 startObserver, 所以 我们在 主界面 MainActivity 的类中调用 adapter.startObser() 将 实现了 接口的adapter 对象 加入到 Observer 容器中 就能够了。
OK。大功告成。
=============================================
DownloadManager 代码
这里 贴一下DownloadManager 代码
[java] view plaincopy
- 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 下载时候,推断一下
[java] view plaincopy
- // //文件存在且长度和进度相等,採用断点下载
- httpResult = HttpHelper.download(info.getUrl() + "&range=" + info.getCurrentSize());
通过 range 来区分 当前的下载size.
服务器 处理的代码 也非常easy 就是一句话
String range = req.getParameter(“range”); 拿到 range 推断 range 存在不存在。
假设不存在
[java] view plaincopy
- 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 个字节
[java] view plaincopy
- 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 将会话费非常多时间。
所以这个仅限于学习。实际中 假设要下载大的文件。不能用这个方案。
完美版 过几天 再发。
代码下载 :
AndroidStudio 版
Eclipse 版
-------------------------------------------各级格机格机格机--------------------------------------
-------------------------------改进版 :点击打开链接------------------------
谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。
欢迎爱学习的小伙伴加群一起进步:230274309 。