Android 多线程断点下载(非原创)
1.服务器的CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源,这里在客户端开启多个线程来从服务器下载资源
2.fragment_main.xml
<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:orientation="vertical" > <!-- 点击下载按钮 --> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="download" android:text="开始下载" /> <!-- 进度条 --> <ProgressBar android:id="@+id/pb_bar" style="@android:style/Widget.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" /> <!-- 显示进度 如: 5% --> <TextView android:id="@+id/tv_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#CC5599" android:textSize="24sp" /> </LinearLayout>
3.MainActivity.java
package com.example.phonemultithreaddownload; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends Activity { // 文件名字 String fileName = "ESurfing_V2.1.exe"; // 路径 String path = "http://192.168.1.66:8080/" + fileName; // 线程的个数,越多下载的越快 int threadCount = 3; // 线程下载完毕的个数 private static int finishThreadCount = 0; // 进度条 ProgressBar pb_bar; // 显示进度 TextView tv_progress; // 当前下载的进度 int totalProgress = 0; // 消息处理器 Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { // 刷新TextView,注意:pb_bar.getProgress返回的值会超出int范围,结果可能为负数,需要强转为long tv_progress.setText((long) pb_bar.getProgress() * 100 / pb_bar.getMax() + " %"); }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); //加载进度条 pb_bar = (ProgressBar) findViewById(R.id.pb_bar); //加载进度文本 tv_progress = (TextView) findViewById(R.id.tv_progress); } public void download(View v) {// 下载 Thread t = new Thread() { @Override public void run() { try { URL url = new URL(path); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5000); con.setReadTimeout(5000); con.setRequestMethod("GET"); if (con.getResponseCode() == 200) { // 拿到请求资源文件的大小 int length = con.getContentLength(); // 设置进度条最大值 pb_bar.setMax(length); File file = new File(fileName); // 生成临时文件 RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/" + 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 * size + size - 1; //如果是最后一个线程,开始的位置确定,但是结束的位置是不确定的,直接设为文件的长度即可 if (i == threadCount - 1) { endIndex = length; } System.out.println("ID : " + i + startIndex + "~" + endIndex + " , " + (endIndex - startIndex + 1)); //创建线程并启动 new DownLoadThread(startIndex, endIndex, i + 1, path).start(); } } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }; t.start(); } class DownLoadThread extends Thread { //开始下载的位置 private int startIndex; //结束下载的位置 private int endIndex; //线程的ID private int threadId; //下载的资源路径 private String path; public DownLoadThread(int startIndex, int endIndex, int threadId, String path) { this.startIndex = startIndex; this.endIndex = endIndex; this.threadId = threadId; this.path = path; } @Override public void run() { try { URL url = new URL(path); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5000); con.setReadTimeout(5000); con.setRequestMethod("GET"); /* * 先判断是否保存了上次下载的字节数 */ File progessFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt"); if (progessFile.exists()) { // 如果文件存在,说明不是第一次下载,修改下载的开始位置 BufferedReader br = new BufferedReader(new FileReader(progessFile)); //读取一行 int newIndex = Integer.parseInt(br.readLine()); //修改开始的位置 startIndex = startIndex + newIndex; //修改进度条的值 totalProgress += newIndex; System.out.println(threadId + " , 上次下载了 " + newIndex); br.close(); } // 设置本次http所请求的区间 con.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); // 请求部分数据的响应码是206 if (con.getResponseCode() == 206) { RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/" + fileName, "rwd"); // 把文件的写入位置移动至startIndex位置 raf.seek(startIndex); InputStream is = con.getInputStream(); int len = -1; int total = 0; byte[] buf = new byte[1024]; while ((len = is.read(buf)) != -1) { //将读取到的数据i写入文件中 raf.write(buf, 0, len); //记录该线程一共下载了多少 total += len; System.out.println("线程" + threadId + "下载了" + total); //更改进度条的值 totalProgress += len; // 设置进度 pb_bar.setProgress(totalProgress); // 显示进度,发送空消息,携带进度的值 handler.sendEmptyMessage(totalProgress); RandomAccessFile processRaf = new RandomAccessFile(progessFile, "rwd"); processRaf.write((total + "").getBytes()); processRaf.close(); } System.out.println(threadId + "下载完成了------------------------------------------"); raf.close(); // 删除缓存文件 finishThreadCount++; System.out.println("准备删除文件" + threadId + " finishThreadCount = " + finishThreadCount); synchronized (DownLoadThread.class) { if (finishThreadCount == threadCount) { for (int i = 0; i < threadCount; i++) { File tempFile = new File(Environment.getExternalStorageDirectory(), i + 1 + ".txt"); System.out.println("删除" + (i + 1) + ".txt文件"); tempFile.delete(); } finishThreadCount = 0; // 资源下载完毕,有可能出现99%的情况,手动改为100% pb_bar.setProgress(pb_bar.getMax()); } } } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
4.添加权限,网络权限和网SD卡写入的权限
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
5.效果如下: