实现断点就会需要用到服务器,关于服务器可以使用tomcat 、wampservser等,在该服务器上放置一个体积足够大的文件,不然局域网超快的速度下载和传输,你都没有反应过来什么事。
网络传输就会涉及到权限问题,android版本的不断更新,,对于用户的权限那可是十分的重要和棘手,自从android某版本开始以后,安卓上开发传输已经不在建议使用http协议传输,官方的理由就是明文传输不安全,建议使用https协议传输。相对于http协议,https传输会更加的安全。
HTTP协议一般指HTTP。
超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII形式给出;而消息内容可以明文发送。
HTTPS协议
(全称:Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 在HTTP 的基础下加入SSL,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。
备注:SSL是一种域名验证的加载的证书,验证身份信息的加密的证书。
下载文件[思路]
- 需要服务器以及可以提供下载的地址
- 通过URL对象获取连接,以及请求方式
- 官方为了更好的体验,必须使用子线程开启下载操作,主线程操作下载操作直接出错
- 下载文件,开启线程,需要确定下载文件的子线程的数量 每个子线程需要下载模块的大小 记录当前的线程运行的数量 文件的存储目录
- android需要接入互联网,要在本地存储操作文件,需要在mainfest文件申请权限
- 需要动态申请权限[官方规定]
- 创建子线程类,需要确定子线程id 开始的位置 结束的位置 下载文件的path
- 断点下载需要向服务器发送Range 请求头 例如:
conn.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);//告诉浏览器分段下载
- 需要使用RandomAccessFile操控文件读取指针的位置,
- 确定每一个子线程都能完成下载的模块的byte 才可以删除临时记录的文件
- 在数据中0也是数据,如果是最后一个字节必须减1个byte
- 在finlly中操作删除临时记录文件,一定要注意线程的脏数据以及线程并发操作,必须要使用加锁方式处理
具体代码实现如下:
关于mainfest文件申请权限可以查阅别人的代码,网上应该有!
package com.example.a02downloadfile;
/*
功能:实现多线程下载 实现断点下载
注意:一定要要动态获取存储权限,这里是手动开启权限
*/
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE };
private static int threadCount = 3; //设置线程的数量为3
private static long blockSize; //声明每个下载区块的大小
private static int runninbgThreadCount; //声明正在运行的线程数量
private static File extDir = Environment.getExternalStorageDirectory(); //设备存储目录
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int permission = ActivityCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE);
}
//192.168.2.15
//http://192.168.2.15/school/temp.exe
String path = "http://192.168.2.15/school/temp.exe"; //服务器下载文件的路径
new Thread(new Runnable() {
@Override
public void run() {
//实例化url对象
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //获取连接对象
conn.setRequestMethod("GET");//设置请求方式为get请求
conn.setConnectTimeout(5000); //设置超时5s
int code = conn.getResponseCode(); //获取返回的请求码
if(code ==200){ //如果返回码等于200,代表请求返回成功
long size = conn.getContentLength(); //获取返回数据长度
System.out.println("获取服务器文件大小:"+size+"KB");
blockSize = size/threadCount; //分发每一个下载模块的大小
File file = new File(extDir,"temp.exe"); //在本地创建一个与服务器同样的空白文件
System.out.println(file);
RandomAccessFile raf = new RandomAccessFile(file,"rw"); //随机读写文件
raf.setLength(size);
runninbgThreadCount = threadCount;//开启若干个子线程分别去下载对应的资源
for(int i=1; i<= runninbgThreadCount; i++){
long startIndex = (i-1)*blockSize; //开始位置 0
long endIndex = i*blockSize - 1; //结束位置 2
if(i==threadCount){ //如果是最后一个线程
endIndex = size - 1; // 0也算一个数据。因此也要减去该位
}
System.out.println("开启线程:"+i+" 下载位置:"+startIndex+"结束位置:"+endIndex+"~");
//url路径 线程id 开始下载位置 结束下载位置
new DownloadThread(path,i,startIndex,endIndex).start();
}
conn.disconnect();
raf.close();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
//创建子线程
private static class DownloadThread extends Thread{
private int threadID; //子线程ID
private long startIndex; //下载开始位置
private long endIndex; //下载结束位置
private String path; //文件路径
public DownloadThread(String path, int threadID, long startIndex, long endIndex){
this.path = path;
this.threadID = threadID;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
try {
int total = 0; //当前总下载的大小
File postionFile = new File(extDir,threadID+".txt"); //线程文件.txt
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//接着上一次下载的数据
if(postionFile.exists() && postionFile.length()>0){ //如果该文件存在并且大于0 代表有数据
FileInputStream fis = new FileInputStream(postionFile); //读取该文件
BufferedReader br = new BufferedReader(new InputStreamReader(fis)); //reader对象
String lastTotalstr = br.readLine(); //A获取当前线程上一次下载的总大小
int lastTotal = Integer.valueOf(lastTotalstr);
System.out.println("上一次线程"+threadID+"下载了总大小:"+lastTotal+"KB");
startIndex += lastTotal; //累加上一次下载的总大小
total += lastTotal; //当前累加下载的总大小
br.close();
fis.close();
}
conn.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);//告诉浏览器分段下载
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
System.out.println("code = "+code);//服务器响应206 代表url可以访问任何资源
InputStream is = conn.getInputStream();//获取服务器流
File file = new File(extDir,"temp.exe");
RandomAccessFile raf = new RandomAccessFile(file,"rw");
raf.seek(startIndex); //指定文件开始写的位置
System.out.println("第"+threadID+"个线程:写文件开始的位置"+String.valueOf(startIndex));
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer))!= -1){
raf.write(buffer,0,len);
RandomAccessFile rf = new RandomAccessFile(postionFile,"rwd");
total += len;
rf.write(String.valueOf(total).getBytes()); //转换为字节数
rf.close();
}
raf.close();
is.close();
conn.disconnect();
System.out.println("线程:"+threadID+"下载完毕");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//只有所有的线程下载玩文件之后,才可以删除文件记录
synchronized (DownloadThread.class){
System.out.println("线程"+threadID+"下载完毕了,可以删除文件了");
runninbgThreadCount--; //每当下载完一个任务就相应减去一个线程个数
if(runninbgThreadCount<1) { //代表当前没有线程
System.out.println("所有的线程都完成了工作,开始删除临时记录文件了");
for (int i = 1; i <= threadCount; i++) {
File f = new File(extDir,i + ".txt");
System.out.println(f);
System.out.println(f.delete());
}
}
}
}
}
}
}