Android多线程断点下载功能(一)
2019-07-03 本文已影响0人
见哥哥长高了
安卓的断点下载功能和Java SE的断点下载功能逻辑代码是可以共享的,在这里我们以任意一个服务端的文件为例,看一下断点下载的功能如何实现。其大致思路如何?
由于下载文件需要联网、文件存储等操作,所以需要用到一下权限:
//链接网络
<uses-permission-sdk-23 android:name="android.permission.INTERNET"/>
//要往SD卡里面写数据 需要权限WRITE_EXTERNAL_STORAGE
<uses-permission-sdk-23 android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
结下来我们搞一个简易的线性布局xml文件,其页面内容为:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入要下载的文件路径"
android:id="@+id/et_path"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="downloadAction"/>
<ProgressBar
android:id="@+id/progressBar0"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressBar2"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
接着在MainActivity文件中定义:
public static String path = "http://weige.teacheredu.cn//attached/20181114/20181114175439_91.docx";
private EditText et_path;
private static ProgressBar pb0;
private static ProgressBar pb1;
private static ProgressBar pb2;
//开启几个线程从服务器下载数据
public static int threadCount = 3;
public static int runingThreadCount = 3;
在此文件中找到各view并实现方法downloadAction方法
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_path = (EditText)findViewById(R.id.et_path);
et_path.setText(path);
pb0 = (ProgressBar)findViewById(R.id.progressBar0);
pb1 = (ProgressBar)findViewById(R.id.progressBar1);
pb2 = (ProgressBar)findViewById(R.id.progressBar2);
}
public void downloadAction(View view){
//得到下载路径
final String downPath = et_path.getText().toString().trim();
if (TextUtils.isEmpty(downPath) && downPath.startsWith("http://")){
Toast.makeText(this,"对不起,路径不合法",0).show();
return;
}
// 访问网络的操作不能在主线程执行
new Thread(){
@Override
public void run() {
super.run();
try {
URL url = new URL(downPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if (code == 200){
int length = conn.getContentLength();
System.out.println("服务器文件的长度:"+ length);
RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ getFileName(downPath),"rw");
raf.setLength(length);
raf.close();
int blockSize = length / threadCount;
runingThreadCount = threadCount;
for (int threadId = 0; threadId < threadCount;threadId++){
int startIndex = threadId * blockSize;
int endIndex = (threadId + 1) * blockSize - 1;
if (threadId == (threadCount - 1)){
endIndex = length - 1;
}
new DownloadThread(threadId,startIndex,endIndex).start();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}
由于我们要演示的功能是多个线程同时下载的情况,需要创建多个线程同时协作完成下载任务,所以我们在上面的代码中创建了多个线程。下面我们声明一个类继承自Thread,实现具体的下载细节。(本操作以保存在SD卡为例)
private static class DownloadThread extends Thread{
private int threadId;
private int startIndex;
private int endIndex;
private int currentPosition;
public DownloadThread(int threadId,int startIndex,int endIndex){
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
System.out.println(threadId+"号线程下载的范围"+startIndex+"----------"+endIndex);
currentPosition = startIndex;
}
@Override
public void run() {
super.run();
try{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//检查当前线程是否已经下载过一部分的数据了
File info = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ threadId + ".position");
RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" + getFileName(path),"rw");
if (info.exists() && info.length() > 0){
FileInputStream fis = new FileInputStream(info);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
currentPosition = Integer.valueOf(br.readLine());
conn.setRequestProperty("Range","bytes="+currentPosition+"-"+endIndex);
System.out.println("原来有下载进度,从上一次终止的位置处开始下载"+"bytes="+currentPosition+"-"+endIndex);
fis.close();
raf.seek(currentPosition);//每个线程写文件的开始位置都是你不一样的
}else {
//告诉服务器只想下载资源中的一部分
conn.setRequestProperty("Range","bytes="+startIndex+"-" + endIndex);
System.out.println("原来没有下载进度,新的下载"+"bytes="+startIndex+"-"+endIndex);
raf.seek(startIndex);//每个线程写文件的开始位置都是你不一样的
}
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024*1024*10];
int len = -1;
while ((len=is.read(buffer))!= -1){
//把每个线程下载的数据放在自己的控件里面
raf.write(buffer,0,len);
currentPosition += len;
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" + threadId +".position");
RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rwd");
randomAccessFile.write(String.valueOf(currentPosition).getBytes());
randomAccessFile.close();
//fileOutputSteam数据是不一定被写道底层里面的 有可能存储在缓存里面
//raf是rwd的模式,数据是直接被存储到底层硬盘里
int max = (endIndex - startIndex);
int progress = (currentPosition - startIndex);
if (threadId == 0){
pb0.setMax(max);
pb0.setProgress(progress);
}else if (threadId == 1){
pb1.setMax(max);
pb1.setProgress(progress);
}else if (threadId == 2){
pb2.setMax(max);
pb2.setProgress(progress);
}
}
raf.close();
is.close();
System.out.println("线程"+ threadId+"下载完毕了");
File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ threadId +".position");
f.renameTo(new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ threadId +".position.finish"));
synchronized (MainActivity.class){
runingThreadCount--;
if (runingThreadCount <= 0){
for (int i = 0;i < threadCount;i++){
File fi = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/"+ i +".position.finish");
fi.delete();
}
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
下面这个方法根据服务端文件路径得到文件名:
public static String getFileName(String path){
int start = path.lastIndexOf("/") + 1;
return path.substring(start);
}
以上工作完成之后,可以愉快的下载了。如果是多个文件同时下载的话大致功能类似,大家可以改装,自由发挥了。