文件多线程下载实现
布局文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 - <RelativeLayout 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" 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=".MainActivity"> 2 - <LinearLayout android:id="@+id/ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> 3 - <EditText android:id="@+id/pathET" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:inputType="textUri" android:text="http://192.168.1.251:8080/WebServer/FeiQ.exe"> 4 <requestFocus /> 5 </EditText> 6 <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="download" android:src="@android:drawable/ic_media_play" /> 7 </LinearLayout> 8 <ProgressBar android:id="@+id/downloadPB" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/ll" android:layout_marginTop="20dp" /> 9 <TextView android:id="@+id/percentTV" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/downloadPB" android:text="0%" /> 10 <TextView android:id="@+id/progressTV" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/downloadPB" android:text="0/0" /> 11 </RelativeLayout>
MyHandler.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.itheima.download; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 8 import org.apache.http.HttpEntity; 9 10 import android.content.Context; 11 12 import com.loopj.android.http.FileAsyncHttpResponseHandler; 13 14 public abstract class MyFileHandler extends FileAsyncHttpResponseHandler { 15 private boolean isCancel; 16 17 public MyFileHandler(Context context) { 18 super(context); 19 } 20 21 public MyFileHandler(File file) { 22 super(file); 23 } 24 25 @Override 26 protected byte[] getResponseData(HttpEntity entity) throws IOException { 27 if (entity != null) { 28 InputStream instream = entity.getContent(); 29 long contentLength = entity.getContentLength(); 30 FileOutputStream buffer = new FileOutputStream(getTargetFile(), true); 31 if (instream != null) { 32 try { 33 byte[] tmp = new byte[BUFFER_SIZE]; 34 int l, count = 0; 35 // do not send messages if request has been cancelled 36 while ((l = instream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) { 37 if (isCancel) 38 break; 39 count += l; 40 buffer.write(tmp, 0, l); 41 sendProgressMessage(count, (int) contentLength); 42 } 43 } finally { 44 instream.close(); 45 buffer.flush(); 46 buffer.close(); 47 } 48 } 49 } 50 return null; 51 } 52 53 public void cancel() { 54 isCancel = true; 55 } 56 57 }
MainActivity.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.itheima.download; 2 3 import java.io.File; 4 5 import android.app.Activity; 6 import android.os.Bundle; 7 import android.os.Environment; 8 import android.view.View; 9 import android.widget.EditText; 10 import android.widget.ImageButton; 11 import android.widget.ProgressBar; 12 import android.widget.TextView; 13 14 public class MainActivity extends Activity { 15 16 // private AsyncHttpClient client; 17 18 private EditText pathET; 19 private ProgressBar downloadPB; 20 private TextView percentTV; 21 private TextView progressTV; 22 private DownloadTask task; 23 24 @Override 25 protected void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 setContentView(R.layout.activity_main); 28 29 pathET = (EditText) findViewById(R.id.pathET); 30 downloadPB = (ProgressBar) findViewById(R.id.downloadPB); 31 percentTV = (TextView) findViewById(R.id.percentTV); 32 progressTV = (TextView) findViewById(R.id.progressTV); 33 34 // client = new AsyncHttpClient(); 35 } 36 37 public void download(View v) { 38 ImageButton ib = (ImageButton) v; 39 40 if (task == null) { 41 String targetUrl = pathET.getText().toString().trim(); 42 File localFile = new File(Environment.getExternalStorageDirectory(), targetUrl.substring(targetUrl.lastIndexOf("/") + 1)); 43 task = new DownloadTask(targetUrl, localFile, 3, this, downloadPB, percentTV, progressTV); 44 task.execute(); 45 ib.setImageResource(android.R.drawable.ic_media_pause); 46 } else { 47 task.stop(); 48 task = null; 49 ib.setImageResource(android.R.drawable.ic_media_play); 50 } 51 } 52 53 /* 54 public void classicDownload(View v) { 55 String path = pathET.getText().toString().trim(); 56 File file = new File("/mnt/sdcard/", path.substring(path.lastIndexOf("/") + 1)); 57 58 client.get(path, new FileAsyncHttpResponseHandler(file) { // 处理文件传输的处理器 59 public void onSuccess(int statusCode, Header[] headers, File file) { 60 Toast.makeText(getApplicationContext(), "下载成功", Toast.LENGTH_SHORT).show(); 61 } 62 public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) { 63 Toast.makeText(getApplicationContext(), "下载失败 " + statusCode, Toast.LENGTH_SHORT).show(); 64 throwable.printStackTrace(); 65 } 66 public void onProgress(int bytesWritten, int totalSize) { // 下载或上传的进度 67 System.out.println(bytesWritten + " / " + totalSize + " (" + bytesWritten * 100 / totalSize + "%)"); 68 } 69 }); 70 } 71 */ 72 73 }
DownloadTask.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.itheima.download; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.util.ArrayList; 8 9 import org.apache.http.Header; 10 11 import android.content.Context; 12 import android.widget.ProgressBar; 13 import android.widget.TextView; 14 15 import com.loopj.android.http.AsyncHttpClient; 16 import com.loopj.android.http.AsyncHttpResponseHandler; 17 18 public class DownloadTask { 19 private String targetUrl; 20 private File localFile; 21 private int threadAmount; 22 private Context context; 23 private ProgressBar downloadPB; 24 private TextView percentTV; 25 private TextView progressTV; 26 27 private long contentLength; 28 private long threadLength; 29 private ArrayList<File> cacheList = new ArrayList<File>(); 30 private ArrayList<MyFileHandler> handlerList = new ArrayList<MyFileHandler>(); 31 32 /** 33 * 创建下载任务 34 * @param targetUrl 目标地址 35 * @param localFile 本地路径 36 * @param threadAmount 线程数量 37 * @param context 应用环境 38 * @param downloadPB 39 * @param progressTV 40 * @param percentTV 41 */ 42 public DownloadTask(String targetUrl, File localFile, int threadAmount, Context context, ProgressBar downloadPB, TextView percentTV, TextView progressTV) { 43 super(); 44 this.targetUrl = targetUrl; 45 this.localFile = localFile; 46 this.threadAmount = threadAmount; 47 this.context = context; 48 this.downloadPB = downloadPB; 49 this.percentTV = percentTV; 50 this.progressTV = progressTV; 51 } 52 53 public void execute() { 54 new AsyncHttpClient().head(targetUrl, new AsyncHttpResponseHandler() { // 根据URL向服务器发起一个请求, 获取响应头 55 public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { 56 caculateLength(headers); // 获取文件总长度, 计算每个线程负责的长度 57 beginDownload(); // 开启多个线程下载 58 } 59 public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { 60 } 61 }); 62 } 63 64 private void caculateLength(Header[] headers) { 65 for (Header header : headers) 66 if ("Content-Length".equals(header.getName())) 67 contentLength = Long.parseLong(header.getValue()); // 从相应头中获取文件总大小 68 threadLength = (contentLength + threadAmount - 1) / threadAmount; // 计算每个线程负责多少 69 downloadPB.setMax((int) contentLength); // 设置进度条的最大刻度为文件总大小 70 } 71 72 private void beginDownload() { 73 for (int i = 0; i < threadAmount; i++) { // 定义循环, 每次循环启动1个线程下载 74 File cacheFile = new File(context.getCacheDir(), localFile.getName() + ".temp" + i); // 定义临时文件的路径 75 cacheList.add(cacheFile); // 把文件装入集合 76 77 long begin = i * threadLength + cacheFile.length(); // 计算开始位置 78 long end = i * threadLength + threadLength - 1; // 计算结束位置 79 System.out.println(i + ": " + begin + " - " + end); 80 if (begin > end) 81 continue; 82 83 MyFileHandler fileHandler = new MyFileHandler(cacheFile) { // 发送请求, 下载数据, 存储到临时文件 84 private long ms; 85 public void onSuccess(int statusCode, Header[] headers, File file) { 86 System.out.println(Thread.currentThread().getName() + ": " + file + " 下载完成"); 87 checkOtherThread(); // 检查其他线程是否下载完毕, 如果都完了, 合并文件 88 } 89 public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) { 90 } 91 public void onProgress(int bytesWritten, int totalSize) { // 运行频率非常高 92 if (System.currentTimeMillis() - ms > 100) { 93 ms = System.currentTimeMillis(); 94 updateProgress(); 95 } 96 } 97 }; 98 handlerList.add(fileHandler); 99 100 AsyncHttpClient client = new AsyncHttpClient(); 101 client.addHeader("Range", "bytes=" + begin + "-" + end); // 设置请求数据的范围 102 client.get(targetUrl, fileHandler); 103 } 104 } 105 106 private void updateProgress() { 107 long finishLength = getFinishLength(); 108 if (finishLength == 0) 109 return; 110 downloadPB.setProgress((int) finishLength); 111 percentTV.setText(finishLength * 1000 / contentLength / 10f + "%"); 112 progressTV.setText(finishLength + "/" + contentLength); 113 } 114 115 private long getFinishLength() { 116 long finishLength = 0; 117 for (File cacheFile : cacheList) // 遍历所有临时文件 118 finishLength += cacheFile.length(); // 统计总长度 119 return finishLength; 120 } 121 122 private void checkOtherThread() { 123 if (getFinishLength() == contentLength) { // 如果文件长度和ContentLength相等, 代表下载完成 124 updateProgress(); 125 merge(); // 合并所有的临时文件 126 } 127 } 128 129 private void merge() { 130 try { 131 FileOutputStream out = new FileOutputStream(localFile); 132 for (File cacheFile : cacheList) { // 遍历所有临时文件 133 FileInputStream in = new FileInputStream(cacheFile); 134 byte[] buffer = new byte[8192]; 135 int length; 136 while ((length = in.read(buffer)) != -1) // 读取每个文件 137 out.write(buffer, 0, length); // 把每个文件的数据都写出到localFile 138 in.close(); 139 cacheFile.delete(); 140 } 141 out.close(); 142 } catch (IOException e) { 143 throw new RuntimeException(e); 144 } 145 } 146 147 public void stop() { 148 for (MyFileHandler fileHandler : handlerList) { 149 fileHandler.cancel(); 150 } 151 } 152 }