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

        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;
        }
    }
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值