在上一篇博客中介绍了在Java中使用多线程结合断点续传实现一个简单的文件下载器,这篇博客将介绍将介绍在android中实现多线程下载和断点续传
首先看一下实现的效果
当下载完成后进度条的进度会变满,并且TextView控件中显示的下载进度变为100%
下载完成后,可以看到在SD卡目录下多了一个Python-2.7.5.amd64.msi
实现方式:
第一步:使用Android Studio创建一个Android工程,并且修改activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 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" tools:context="com.fyt.mobilemultidownload.MainActivity" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始下载" android:onClick="download"/> <ProgressBar android:id="@+id/pb" android:layout_width="match_parent" android:layout_height="wrap_content" style="@android:style/Widget.ProgressBar.Horizontal"/> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
package com.fyt.mobilemultidownload; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends Activity { //下载文件时开启线程的个数 static int ThreadCount = 3; //下载结束的线程的个数 static int finishedThread = 0; //用于记录下载进度 int currentProgress; //下载文件的文件名 String fileName = "python-2.7.5.amd64.msi"; //确定下载地址 String path = "http://192.168.0.101:8080/app/" + fileName; //pb对象用于在进度条中设置下载进度 private ProgressBar pb; //tv对象用于在TextView中显示下载进度 TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pb = (ProgressBar) findViewById(R.id.pb); tv = (TextView) findViewById(R.id.tv); } //创建一个消息处理器对象 Handler handler = new Handler() { //在主线程中处理从子线程中发送过来的消息 public void handleMessage(android.os.Message msg) { //刷新TextView中显示的下载进度 tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%"); } }; //下载文件按钮响应函数 public void download(View v){ //创建一个子线程,用于下载文件 Thread t = new Thread() { //执行子线程(下载文件) @Override public void run() { try { //将下载地址封装成URL对象 URL url = new URL(path); //创建连接对象,此时未建立连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //设置请求方式为get请求 conn.setRequestMethod("GET"); //设置连接超时 conn.setConnectTimeout(5000); //设置读取超时 conn.setReadTimeout(5000); //如果请求成功 if(conn.getResponseCode() == 200) { //获得需要下载的文件的长度 int length = conn.getContentLength(); //设置进度条的最大值就是原文件的总长度 pb.setMax(length); //创建File对象 File file = new File(Environment.getExternalStorageDirectory(), fileName); //创建临时文件 RandomAccessFile raf = new RandomAccessFile(file, "rwd"); //设置临时文件的大小 raf.setLength(length); //关闭临时文件 raf.close(); //计算出每个线程应该下载多少字节 int size = length / ThreadCount; //遍历下载线程 for (int i = 0; i < ThreadCount; i++) { //计算线程下载的开始位置 int startIndex = i * size; //计算线程下载的结束位置 int endIndex = (i + 1) * size - 1; //如果是最后一个线程,那么结束位置写死 if(i == ThreadCount - 1) { endIndex = length - 1; } //创建下载线程 DownLoadThread thread = new DownLoadThread(startIndex, endIndex, i); //启动下载线程 thread.start(); } } } catch (Exception e) { e.printStackTrace(); } } }; t.start(); } //创建一个继承自线程类的下载线程类(其实是一个内部类) class DownLoadThread extends Thread { //下载开始的位置 int startIndex; //下载结束的位置 int endIndex; //下载线程的id int threadId; //下载线程类的构造方法 public DownLoadThread(int startIndex, int endIndex, int threadId) { super(); this.startIndex = startIndex; this.endIndex = endIndex; this.threadId = threadId; } //执行下载线程 @Override public void run() { try { //创建进度临时文件 File progressFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt"); //如果SD卡中存在进度临时文件 if(progressFile.exists()) { //创建文件输出流 FileInputStream fis = new FileInputStream(progressFile); //InputStreamReader:创建输输入流缓冲区 //BufferedReader:创建读取缓冲区 BufferedReader br = new BufferedReader(new InputStreamReader(fis)); //从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置 int lastProgress = Integer.parseInt(br.readLine()); //设置初始位置 startIndex += lastProgress; //把上次下载的进度显示至进度条 currentProgress += lastProgress; pb.setProgress(currentProgress); //发送消息,让主线程刷新文本进度 handler.sendEmptyMessage(1); //关闭文件输入流 fis.close(); } System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---" + endIndex); //将下载地址封装成URL对象 URL url = new URL(path); //创建连接对象,此时未建立连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //设置请求方式为get请求 conn.setRequestMethod("GET"); //设置连接超时 conn.setConnectTimeout(5000); //设置读取超时 conn.setReadTimeout(5000); //设置本次http请求所请求的数据的区间 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); //请求部分数据,相应码是206 if(conn.getResponseCode() == 206) { //流里此时只有1/3原文件的数据 InputStream is = conn.getInputStream(); byte[] b = new byte[1024]; int len = 0; int total = 0; //拿到临时文件的输出流 File file = new File(Environment.getExternalStorageDirectory(), fileName); //使用临时文件输出流创建临时文件 RandomAccessFile raf = new RandomAccessFile(file, "rwd"); //把文件的写入位置移动至startIndex raf.seek(startIndex); while((len = is.read(b)) != -1) { //每次读取流里数据之后,同步把数据写入临时文件 raf.write(b, 0, len); total += len; System.out.println("线程" + threadId + "下载了" + total); //每次读取流里数据之后,把本次读取的数据的长度显示至进度条 currentProgress += len; pb.setProgress(currentProgress); //发送消息,让主线程刷新文本进度 handler.sendEmptyMessage(1); //生成一个专门用来记录下载进度的临时文件 RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd"); //每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中 progressRaf.write((total + "").getBytes()); //关闭临时文件 progressRaf.close(); } System.out.println("线程" + threadId + "下载完毕-------------------小志参上!"); //关闭临时文件 raf.close(); //下载结束的进程个数加1 finishedThread++; synchronized (path) { //如果所有的下载进程都下载结束 if(finishedThread == ThreadCount) { //遍历下载进度 for (int i = 0; i < ThreadCount; i++) { //获取下载进度所在的文件 File f = new File(Environment.getExternalStorageDirectory(), i + ".txt"); //删除保存下载进度的临时文件 f.delete(); } //下载结束的下载线程的个数设置为0 finishedThread = 0; } } } } catch (Exception e) { e.printStackTrace(); } } } }
最后一步:在配置文件中添加访问网络的权限和对SD卡的写权限
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>