实现断点就会需要用到服务器,关于服务器可以使用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;
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) {
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE);
}
String path = "http://192.168.2.15/school/temp.exe";
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if(code ==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;
long endIndex = i*blockSize - 1;
if(i==threadCount){
endIndex = size - 1;
}
System.out.println("开启线程:"+i+" 下载位置:"+startIndex+"结束位置:"+endIndex+"~");
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;
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");
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
if(postionFile.exists() && postionFile.length()>0){
FileInputStream fis = new FileInputStream(postionFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String lastTotalstr = br.readLine();
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);
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());
}
}
}
}
}
}
}
操作测试时,一定要即使删除下载的临时文件,这里没判断读取累加的数据与原数据比较解析,会出现下载文件打不开或者数据损毁等。
|