Android多线程断点下载

最近想写一个关于在线更新的文章。在线更新基本逻辑就是将当前的app的版本号发送给服务器校验,若服务器上有新版本就会返回一个url给客户端。客户端就要去这个url下载最新的apk包。这个功能的核心以及难点就是下载文件,然而!!然而!!就在今天在极客学院的网站上看到一个视频,讲的是关于多线程断点下载的。这可比我的单线程,不支持暂停的下载要高大上太多了。于是我就学习了一下,将它总结一下:视频地址极客学院-多线程断点下载。app运行效果如下:




基本逻辑:

1.通过HttpURLConnection获取url中相关资源

2.通过HttpURLConnection的setRequestProperty方法,截取资源的bytes,实现多线程下载

3.通过RandomAccessFile的seek方法可以设置从哪个位置开始写入,实现断点下载(恢复下载)


下面贴上代码,具体的讲解请看注释:

MainActivity.java

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private int mDownloadedLength = 0;//已经下载的大小
    private int mTotalLength = 0;//已经下载的大小
    private boolean isDownloading = false;//标记是否下载中,用于断电续传
    private File mDownloadFile;
    private URL mUrl;
    private List<HashMap<String, Integer>> mThreadList = new ArrayList<>();
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0x1023) {
                int progressRate = (int) msg.obj;
                mDownloadPB.setProgress(progressRate);
                if (progressRate >= mTotalLength) {
                    Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    private EditText mUrlET;
    private Button mDownloadBtn;
    private ProgressBar mDownloadPB;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mUrlET = (EditText) findViewById(R.id.et_url);
        mDownloadBtn = (Button) findViewById(R.id.btn_download);
        mDownloadPB = (ProgressBar) findViewById(R.id.pb_download);

        mDownloadBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isDownloading) {
                    isDownloading = false;
                    ((Button) v).setText("Download");
                    return;
                } else {
                    isDownloading = true;
                    ((Button) v).setText("Pause");
                }

                if (mThreadList.size() == 0) {
                    //开启新下载
                    String url = mUrlET.getText().toString();
                    executeDownload(url);
                } else {
                    //恢复下载
                    for (int i = 0; i < mThreadList.size(); i++) {
                        HashMap<String, Integer> map = mThreadList.get(i);
                        int begin = map.get("begin");
                        int end = map.get("end");
                        int finished = map.get("finished");
                        //修改下载文件的起始点
                        Thread t = new Thread(new DownloadRunnable(i, begin + finished,
                                end, mDownloadFile, mUrl));
                        t.start();
                    }
                }
            }
        });
    }

    /**
     * 执行下载
     * @param url 需要下载的资源的url
     */
    private void executeDownload(final String url) {
        if (!TextUtils.isEmpty(url)) {
            //
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mUrl = new URL(url);
                        HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
                        //设置为get请求
                        connection.setRequestMethod("GET");
                        //设置请求超时的时间
                        connection.setConnectTimeout(5 * 1000);
                        //模拟浏览器请求
                        connection.setRequestProperty("User-Agent",
                                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36");

                        int length = connection.getContentLength();
                        if (length < 0) {
                            System.out.println("url错误");
                            return;
                        }
                        mTotalLength = length;
                        mDownloadPB.setMax(length);
                        mDownloadPB.setProgress(0);

                        //创建一个文件用于保存下载的资源
                        int urlIndex = url.lastIndexOf(".");
                        //获取文件后缀
                        String fileSuffix = url.substring(urlIndex);
                        mDownloadFile = new File(Environment.getExternalStorageDirectory(),
                                String.valueOf(System.currentTimeMillis()) + fileSuffix);
                        RandomAccessFile randomAccessFile = new RandomAccessFile(mDownloadFile, "rw");
                        randomAccessFile.setLength(length);

                        //线程数量,用于开启多线程下载
                        int threadCount = 3;
                        //每个线程下载的文件的大小
                        int eachFileSize = length / threadCount;
                        for (int i = 0; i < threadCount; i++) {
                            int begin = i * eachFileSize;
                            int end = (i + 1) * eachFileSize;
                            if (i == threadCount - 1) {
                                end = length;
                            }
                            //记录该线程的下载信息
                            HashMap<String, Integer> map = new HashMap<String, Integer>();
                            map.put("begin", begin);
                            map.put("end", end);
                            map.put("finished", 0);
                            mThreadList.add(map);

                            //创建线程来下载文件
                            Thread thread = new Thread(new DownloadRunnable(i, begin, end, mDownloadFile, mUrl));
                            thread.start();

                        }


                    } catch (Exception e) {
                        e.printStackTrace();
                    }


                }
            }).start();
        }
    }

    /**
     * 执行下载Runnable
     */
    class DownloadRunnable implements Runnable {
        private int begin;
        private int end;
        private File file;
        private URL url;
        private int id;

        public DownloadRunnable(int id, int begin, int end, File file, URL url) {
            this.id = id;
            this.begin = begin;
            this.end = end;
            this.file = file;
            this.url = url;
        }

        @Override
        public void run() {
            try {
                if (begin > end) {
                    //下载结束
                    return;
                }

                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                //设置为get请求
                connection.setRequestMethod("GET");
                //设置请求超时的时间
                connection.setConnectTimeout(5 * 1000);
                //模拟浏览器请求
                connection.setRequestProperty("User-Agent",
                        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36");
                //多线程断点下载的重点
                connection.setRequestProperty("Range", "bytes=" + begin + "-" + end);

                InputStream is = connection.getInputStream();
                byte[] buf = new byte[1024 * 1024];
                //randomAccessFile对象不能在多个线程中共享
                RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
                randomAccessFile.seek(begin);
                int len = 0;
                HashMap<String, Integer> map = mThreadList.get(id);
                while ((len = is.read(buf)) != -1 && isDownloading) {
                    randomAccessFile.write(buf, 0, len);
                    updateProgress(len);
                    //修改该线程已经下载的文件大小
                    map.put("finished", map.get("finished") + len);
                    System.out.println("mDownloadedLength:" + mDownloadedLength);
                }
                is.close();
                randomAccessFile.close();


            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 更新下载进度
     * @param addLength 增加的进度
     */
    synchronized private void updateProgress(int addLength) {
        mDownloadedLength += addLength;
        mHandler.obtainMessage(0x1023, mDownloadedLength).sendToTarget();

    }
}


xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.yyl.forexpresstest.MainActivity"
    tools:showIn="@layout/activity_main">

    <EditText
        android:id="@+id/et_url"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ProgressBar
        android:id="@+id/pb_download"
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_below="@+id/et_url"
        style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"/>

    <Button
        android:id="@+id/btn_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/pb_download"
        android:text="Download" />


</RelativeLayout>



最后别忘记了在manifest中添加权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


到这里就结束了吗?no!虽然例子里面下载的资源不是app文件,但是在线更新的话需要下载的肯定是一个apk格式的文件。我们不能下载下来后不做任何操作,让用户自己去file explorer中去找自己刚刚下载的apk文件,并且手动点击安装。这样的体验太差了,所以我们还需要一步,安装apk文件,代码如下:

File apkfile = new File(apkFilePath);
if (!apkfile.exists()) {
    return;
}
Intent i = new Intent(Intent.ACTION_VIEW);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
mContext.startActivity(i);
apkFile就是刚刚下载的文件了




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值