android开发步步为营之108:下载断点续传

标签: android 开发 步步为营 | 发表时间:2016-07-30 17:31 | 作者:figo0423
出处:http://blog.csdn.net

        android开发过程中,下载是必备的功能,下载安装包,或者下载图片,假设用户下载过程中人为中断网络,或者网络不稳定中断下载任务,好的用户体验是从断开的地方继续下载,而不是又从头开始下载,因为比方说用户是拿4g来下载的,你一个游戏安装包100多M,用户下载了90M,突然手机没电了,充好电,又从头下载,那岂不是浪费用户的流量。所以断点续传是非常必要的一个功能。其实断点续传也可以使用多线程来实现的,本篇先不写的这么麻烦了,就单线程去下载一个任务了,如果中断了,下次再点击下载的时候,从断点继续下载。好,开始我们的实验。本实验是下载一个安装包。比如我们下载360手机卫士。给出Demo代码。

        1、activity_resume_download.xml 下载页面

<span style="font-size:18px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="断点续传测试" />

    <Button
        android:id="@+id/btn_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始下载" />

    <Button
        android:id="@+id/btn_cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="取消下载" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="20dp"
        android:layout_gravity="center_horizontal" />

</LinearLayout></span>
          

            2、ResumeDownloadActivity.java

  
package com.figo.study.activity;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.figo.study.R;
import com.figo.study.mgr.DownloadMgr;
import com.figo.study.utils.FileUtils;

import java.io.File;

public class ResumeDownloadActivity extends Activity implements View.OnClickListener {
    String tag = "ResumeDownloadActivity";
    ProgressBar mProgressBar;
    String downloadUrl = "http://msoftdl.360.cn/mobilesafe/shouji360/360safe/500192/360MobileSafe.apk";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_resume_download);

        findViewById(R.id.btn_download).setOnClickListener(this);
        findViewById(R.id.btn_cancel).setOnClickListener(this);

        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
    }

    private final Handler msgHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    Toast.makeText(getApplicationContext(), msg.getData().get("msg").toString(), Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_download:
                String directory = FileUtils.getStorageDirectory();
                String fileName = directory + File.separator + getFileName(downloadUrl);
                DownloadMgr.getInstance().addTask(downloadUrl, fileName, new DownloadMgr.Callback() {
                    @Override
                    public void onProgress(long progress, long total) {
                        super.onProgress(progress, total);
                        mProgressBar.setProgress((int) (100 * progress / total));
                    }

                    @Override
                    public void onStart() {
                        super.onStart();
                        sendMsg("start");

                    }

                    @Override
                    public void onSuccess() {
                        super.onSuccess();
                        Log.i(tag, "success");
                        sendMsg("success");

                    }

                    @Override
                    public void onFailed(boolean cancelled, String msg) {
                        super.onFailed(cancelled, msg);
                        Log.e(tag, msg);
                        //Looper.getMainLooper().prepare();//这么干虽然可以在子线程,弹出toast,但是子线程执行到这里,后面的代码将不再执行
//                        Toast.makeText(ResumeDownloadActivity.this, "download start", Toast.LENGTH_SHORT).show();
                        //Looper.getMainLooper().loop();
                        //进程间通信还是用Handler比较靠谱
                        sendMsg(msg);
                    }
                });

                break;
            case R.id.btn_cancel:
                DownloadMgr.getInstance().cancelTask(downloadUrl);
                break;
        }
    }

    private String getFileName(String downloadUrl) {
        return downloadUrl.substring(downloadUrl.lastIndexOf("/"));
    }

    private void sendMsg(String msg) {
        Message msgNew = new Message();
        msgNew.what = 0;
        Bundle bundle = new Bundle();
        bundle.putString("msg", msg);
        msgNew.setData(bundle);
        msgHandler.sendMessage(msgNew);
    }
}

3、下载工具类DownloadMgr.java
  
package com.figo.study.mgr;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

import com.figo.study.activity.MyApplication;
import com.figo.study.utils.CommonUtil;
import com.figo.study.utils.IOUtil;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.HashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Created by figo on 16/7/25.
 */
public class DownloadMgr {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    Executor mExecutor = Executors.newFixedThreadPool(MAXIMUM_POOL_SIZE);
    static DownloadMgr mDownloadMgr;
    static Object obj = new Object();
    HashMap<String, DownloadTask> mTasks = new HashMap<String, DownloadTask>();

    public static void init() {
        getInstance();
    }

    public static DownloadMgr getInstance() {
        synchronized (obj) {
            if (mDownloadMgr == null) {
                mDownloadMgr = new DownloadMgr();
            }
        }
        return mDownloadMgr;
    }

    public void addTask(String downloadUrl, String filePath, Callback callback) {
        if (!mTasks.containsKey(downloadUrl)) {
            mTasks.put(downloadUrl, new DownloadTask(downloadUrl, filePath, callback));
        }
        mTasks.get(downloadUrl).startDownload();
    }

    public void removeTask(String downloadUrl, String filePath, Callback callback) {
        if (mTasks.containsKey(downloadUrl)) {
            mTasks.get(downloadUrl).cancel();
        }
        mTasks.remove(downloadUrl);
    }

    public class DownloadTask implements Runnable {
        private String downloadUrl;
        private String filePath;
        Callback callback;

        public DownloadTask(String downloadUrl, String filePath, Callback callback) {
            this.downloadUrl = downloadUrl;
            this.filePath = filePath;
            this.callback = callback;
        }

        public void startDownload() {
            mExecutor.execute(this);
        }


        @Override
        public void run() {
            runResumable(downloadUrl, filePath, callback);
        }

        synchronized boolean cancel() {
            if (thread == null)
                return false;

            thread.interrupt();
            return true;
        }
    }

    Thread thread;

    public void runResumable(String downloadUrl, String filePath, Callback callback) {
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        thread = Thread.currentThread();
        final Context ctx = MyApplication.getInstance();
        String msg = "";
        boolean interrupted = false;

        HttpURLConnection conn = null;
        long resumePosition = 0;
        final File file = new File(filePath);

        try {
            //20160720 add
            final File parent = file.getParentFile();
            if (!parent.exists()) {
                parent.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            callback.onStart();

//简单一点就用md5来校验
//            if (file.exists() && StringUtil.equalsIgnoreCase(md5, Md5.md5(file))) {
//                suc = true;
//                return;
//            }
            //非wifi环境不下载
            if (!isWifiActive(ctx)) {
                msg = "请在wifi环境下下载";
                callback.onFailed(true, msg);
                return;
            }

            resumePosition = file.exists() ? file.length() : 0;
            // Create connection object
            conn = (HttpURLConnection) new URL(downloadUrl).openConnection();
            conn.setConnectTimeout(60000);
            conn.setReadTimeout(60000);

            conn.setDoInput(true);
            conn.setUseCaches(false);

            // Make the request
            conn.setRequestMethod("GET");
            conn.setRequestProperty("User-Agent", "Java/Android");
            conn.setRequestProperty("Connection", "close");
            conn.setRequestProperty("Http-version", "HTTP/1.1");
            conn.setRequestProperty("Cache-Control", "no-transform");
            if (resumePosition > 0) {
                //断点续传的关键设置Range
                conn.setRequestProperty("Range", "bytes=" + resumePosition + "-");
            }

            conn.connect();

            final int responseCode = conn.getResponseCode();
            if (responseCode == 416) {
                msg = "已经下载!";
                callback.onFailed(true, msg);
                return;
            }
            if (responseCode != 200 && responseCode != 206) {
                msg = "网络繁忙,请稍后再试!";
                callback.onFailed(true, msg);

                return;
            }

            long fileLength = conn.getContentLength();
            InputStream is = new BufferedInputStream(conn.getInputStream());
            FileOutputStream fos = new FileOutputStream(file, resumePosition > 0);
            try {
                int read = 0;
                long progress = resumePosition;
                byte[] buffer = new byte[4096 * 2];
                while ((read = is.read(buffer)) > 0 && !(interrupted = Thread.interrupted())) {
                    try {
                        fos.write(buffer, 0, read);
                    } catch (Exception e) {
                        msg = "磁盘空间已满,无法下载";
                        throw e;
                    }

                    // progress
                    progress += read;
                    callback.onProgress(progress, fileLength);

                }
            } finally {
                IOUtil.closeQuietly(fos);
                IOUtil.closeQuietly(is);
            }
            //20160720 resumable download
            if (file.exists()) {
                //也可以通过md5来校验
//                if (StringUtil.equalsIgnoreCase(md5, Md5.md5(file))) {
//                    suc = true;
//                    return;
//                }
                //检验数据是否完整
                if (file.length() == fileLength + resumePosition) {
                    callback.onSuccess();
                    return;
                }
            }

        } catch (Exception e) {
            interrupted = interrupted || Thread.interrupted() || (e instanceof InterruptedIOException && !(e instanceof SocketTimeoutException));
            msg = "网络异常,下载失败";

            if (interrupted) {
                msg = "下载被中断!";
            }
            callback.onFailed(true, msg);

        } finally {
            disconnect(conn);
        }
    }

    static void disconnect(HttpURLConnection conn) {
        try {
            if (conn == null)
                return;

            conn.disconnect();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }


    public static abstract class Callback {


        public void onStart() {
        }

        public void onProgress(long progress, long total) {
        }

        public void onSuccess() {
        }

        public void onFailed(boolean cancelled, String msg) {
        }
    }

    public boolean isWifiActive(Context ctx) {
        try {
            ConnectivityManager mgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo info = mgr.getActiveNetworkInfo();
            return (info != null) ? info.getType() == ConnectivityManager.TYPE_WIFI : false;
        } catch (Exception e) {
            return false;
        }
    }

    public void cancelAllTask() {
        try {
            if (mTasks != null) {
                for (String taskKey : mTasks.keySet()) {
                    mTasks.get(taskKey).cancel();
                }
            }
        } catch (Exception e) {
            CommonUtil.printStackTrace(e);
        }

    }

    public void cancelTask(String key) {
        try {
            if (mTasks != null) {
                mTasks.get(key).cancel();
            }
        } catch (Exception e) {
            CommonUtil.printStackTrace(e);
        }
    }

    public static boolean checkNetAvailable(Context ctx) {
        try {
            ConnectivityManager mgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo info = mgr.getActiveNetworkInfo();
            return (info != null) ? true : false;
        } catch (Exception e) {
            return true;
        }
    }
}


作者:figo0423 发表于2016/7/30 17:31:51 原文链接
阅读:0 评论:0 查看评论

相关 [android 开发 步步为营] 推荐:

android开发步步为营之108:下载断点续传

- - CSDN博客推荐文章
        android开发过程中,下载是必备的功能,下载安装包,或者下载图片,假设用户下载过程中人为中断网络,或者网络不稳定中断下载任务,好的用户体验是从断开的地方继续下载,而不是又从头开始下载,因为比方说用户是拿4g来下载的,你一个游戏安装包100多M,用户下载了90M,突然手机没电了,充好电,又从头下载,那岂不是浪费用户的流量.

Android开发Tips

- - CSDN博客推荐文章
欢迎Follow我的 GitHub, 关注我的 CSDN.. 介绍一些, 在Android开发中, 会经常使用的小知识点.. submodule与git可以保持实时同步. 导入, 路径多于一个, 前面不添加冒号(:).. 使用PackageManager.. // 检查App是否安装 private boolean appInstalledOrNot(String uri) {.

Android 开发者调查

- - 爱范儿 · Beats of Bits
Startup 是为 Android 开发者提供盈利模式的一个公司. Android 开发者只要在应用上推广 Startup 服务,并且为网站带去流量,就可以得到网站给予的补贴. 今年 3 月的时候,Startup 网站对 Android 开发者进行了一次调查. 现在,他们将调查的结果制成了信息图,并 发布在网站之上.

Android敏捷开发指南

- - 互联网的那点事
本文紧密结合移动开发方法与技术,围绕Android平台的开发探讨提供更高质量移动产品的解决方案. 作者中分析了移动开发中常见的问题,从两方面阐述了ThoughtWorks使用的测试开发方案和相应的架构方法与常用工具应用,并进一步阐述了为移动开发流程所提供的持续发布方案. 随着云计算、移动互联等一系列新技术概念的崛起,新一轮的IT经济正在不断扩大发展.

Android应用开发资源

- - InfoQ cn
Android应用设计和开发人员现在可以参考由Android用户体验(UX)团队官方发布的 Android设计指南. 该指南提供了开发者应该遵循的基本原则,并列出了很多细节指导,涉及 设备与显示、 主题、 触控交互、 度量与栅格、 排版、 色彩、 图标设计,以及如何 编写用户交互界面的提示语.

Android 开发视频推荐

- - 大猫の意淫筆記
作为 Android 死忠粉,总是想自己来两手,无奈没摸过后台开发,面向的唯一对象就是马总. 花了个把星期入个门,发现还是挺好玩的. 市面上的 Android 入门书有2个主要问题,一个是都要求有 Java 基础,另一个是跟不上 Android 版本更新.所以并不推荐买书学习. 斯坦福大学公开课:编程方法学.

深入解析Kafka高可用设计如何步步为营

- - IT瘾-bigdata
Kafka在0.8以前的版本中,并不提供High Availablity机制,一旦一个或多个Broker宕机,则宕机期间其上所有Partition都无法继续提供服务. 若该Broker永远不能再恢复,亦或磁盘故障,则其上数据将丢失. 而Kafka的设计目标之一即是提供数据持久化,同时对于分布式系统来说,尤其当集群规模上升到一定程度后,一台或者多台机器宕机的可能性大大提高,对于Failover机制的需求非常高.

Android 开发者在 Android Market 上兜售应用

- SotongDJ - 谷安——谷奥Android专题站
无论你是 Android 用户还是开发者,我们相信你会同意我们所说的一个事实:Android Market 还远未完善,我们不是在谈论它的用户界面或者是如何“开放”,而是另外一个话题. CatNinjaFly,或者是 SuperNinjaCat,又或者是 NinjaCatFly. 叫什么都不要紧,要紧的是我们关心的这个问题,要紧的是它们都是来自同一开发者的完全是相同的游戏,很显然,开发者(商)NANJAANDROID 为了让他们的应用能够在 Android Market 多一些曝光率于是用不同的名称来在 Android Market 中兜售.

Eclipse开发Android应用程序入门

- Bingnan - 酷壳 - CoolShell.cn
原文出处:http://www.smashingmagazine.com/2010/10/25/get-started-developing-for-android-with-eclipse/. 如今的移动设备应用程序开发充满着让人振奋的东西. 功能强大的硬件支持,平板电脑,多样的软件平台(塞班 OS,iOS,WebOS,Windows Phone 7…),移动设备开发者前景充满了机会和挑战.

android开发书籍emule下载链接

- jing77 - biAji HeRe
本来放在Verycd的,出于避免某些难以预料的问题的考虑(就像Verycd的诸多电影资源一样),我不得不觉得应该将Verycd作为一个备选方案. ed2k: [android.开发书籍].Beginning.Android.2.(Apress,.2010,.1430226293).pdf. ed2k: [android.开发书籍].Hello.Android.3rd.Edition.pdf.