前端时间公司发新的版本,可是版本升级时候下载apk的时候速度特别的慢,不知道是不是服务器的原因
领导问有没有什么解决办法,于是我就写了个多线程断点续传的功能。(其实多线程对于提速帮助不大)
第一次没有下载完,用户没有耐心了,退出应用,甚至把整个应用都干掉了,在下次打开应用的时候可以继续上一次的下载
注释都在代码中,拿下去可以直接当作工具类使用,关于异常的处理都有注释
import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Message; import com.easipass.tool.weather.NetUtils; /*** * 实现多线程断点下载 * @author 徐初龙 */ public class BreakpointDownloader { private static final String DIR_PATH = Environment.getExternalStorageDirectory() + "/"; // 下载目录 private static final int THREAD_AMOUNT = 3; // 总线程数 private URL url; // 目标下载地址 private File dataFile; // 本地文件 private File tempFile; // 用来存储每个线程下载的进度的临时文件 private long threadLen; // 每个线程要下载的长度 private long totalFinish; // 总共完成了多少 private long totalLen; // 服务端文件总长度 public static long backtotalLen; private long begin; // 用来记录开始下载时的时间 private Handler handler; private Context context; private boolean allthreadisrun = true; public BreakpointDownloader(int newCode, String address, Handler handler,Context context) throws IOException { url = new URL(address); // 记住下载地址 dataFile = new File(DIR_PATH, Config.UPDATE_SAVENAME.replace(".apk", newCode + ".apk")); // 截取地址中的文件名, 创建本地文件 tempFile = new File(dataFile.getAbsolutePath() + ".temp"); // 在本地文件所在文件夹中创建临时文件 this.context = context; File oldDataFile = new File(DIR_PATH, Config.UPDATE_SAVENAME.replace(".apk", newCode-1 + ".apk")); File oldTempFile = new File(oldDataFile.getAbsolutePath() + ".temp"); if(oldDataFile.exists()) oldDataFile.delete(); if(oldTempFile.exists()) oldTempFile.delete(); this.handler = handler; } public void download() throws IOException { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000); totalLen = conn.getContentLength(); // 获取服务端发送过来的文件长度 threadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT; // 计算每个线程要下载的长度 Message msg = new Message(); if(totalLen<0){ msg.getData().putLong("totalLen", backtotalLen); }else{ backtotalLen = totalLen; msg.getData().putLong("totalLen", totalLen); } msg.what = 1; handler.sendMessage(msg); // 发送文件总长度 if (!dataFile.exists()) { // 如果本地文件不存在 try{ RandomAccessFile raf = new RandomAccessFile(dataFile, "rws"); // 在本地创建文件 raf.setLength(totalLen); // 设置文件的大小和服务端相同 raf.close(); }catch(Exception e){ e.printStackTrace(); } } if (!tempFile.exists()) { // 如果临时文件不存在 RandomAccessFile raf = new RandomAccessFile(tempFile, "rws"); // 创建临时文件, 用来记录每个线程已下载多少 for (int i = 0; i < THREAD_AMOUNT; i++) // 按照线程数循环 raf.writeLong(0); // 写入每个线程的开始位置(都是从0开始) raf.close(); } for (int i = 0; i < THREAD_AMOUNT; i++) // 按照线程数循环 new DownloadThread(i).start(); // 开启线程, 每个线程将会下载一部分数据到本地文件中 begin = System.currentTimeMillis(); // 记录开始时间 } private class DownloadThread extends Thread { private int id; // 用来标记当前线程是下载任务中的第几个线程 public DownloadThread(int id) { this.id = id; } boolean flag = true; public void run() { boolean isrun = false; //每个线程都有自己的标识位,用来控制线程的重启 while(allthreadisrun && !isrun){ //判断网络连接是否可用,不可用直接退出 if (!NetUtils.getNetworkIsAvailable(context)) { handler.sendEmptyMessage(3); return; } try { isrun = true; if(tempFile.exists()&&dataFile.exists()){ RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rws"); // 用来记录下载进度的临时文件 tempRaf.seek(id * 8); // 将指针移动到当前线程的位置(每个线程写1个long值, 占8字节) long threadFinish = tempRaf.readLong(); // 读取当前线程已完成了多少 //判断 如果当前线程已经完成了下载,则退出该线程 if(threadFinish == threadLen){ // System.out.println("线程"+id+"已经退出"); tempRaf.close(); totalFinish +=threadFinish; if (totalFinish >= totalLen && totalFinish == (totalLen + THREAD_AMOUNT - 1)) { // 如果已完成长度等于服务端文件长度(代表下载完成) System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin)); tempFile.delete(); // 删除临时文件 } return; } synchronized(BreakpointDownloader.this) { // 多个下载线程之间同步 totalFinish += threadFinish; // 统计所有线程总共完成了多少 } // System.out.println("totalFinish="+totalFinish); Message msg = new Message(); msg.getData().putLong("totalFinish", totalFinish); msg.what = 2; handler.sendMessage(msg); long start = id * threadLen + threadFinish; // 计算当前线程的起始位置 long end = id * threadLen + threadLen - 1; // 计算当前线程的结束位置 System.out.println("线程" + id + ": " + start + "-" + end); // System.out.println("------->url"+url); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000); conn.setRequestProperty("Range", "bytes=" + start + "-" + end); // 设置当前线程下载的范围 InputStream in = conn.getInputStream(); // 获取连接的输入流 RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rws"); // 装载数据的本地文件(可以理解为输出流) dataRaf.seek(start); // 设置当前线程保存数据的位置 byte[] buffer = new byte[1024 * 100]; // 每次拷贝100KB int len; while (totalFinish<totalLen && (len = in.read(buffer)) != -1) { dataRaf.write(buffer, 0, len); // 从服务端读取数据, 写到本地文件 threadFinish += len; // 每次写入数据之后, 统计当前线程完成了多少 tempRaf.seek(id * 8); // 将临时文件的指针指向当前线程的位置 tempRaf.writeLong(threadFinish); // 将当前线程完成了多少写入到临时文件 synchronized(BreakpointDownloader.this) { // 多个下载线程之间同步 totalFinish += len; // 统计所有线程总共完成了多少 Message msg3 = new Message(); msg3.getData().putLong("totalFinish", totalFinish); msg3.what = 2; handler.sendMessage(msg3); // 发送当前进度 // System.out.println("下载进度-----》"+totalFinish+"/"+totalLen); } } dataRaf.close(); tempRaf.close(); } } catch (IOException e) { e.printStackTrace(); // System.out.println("1 ---》这里异常了"); if (!NetUtils.getNetworkIsAvailable(context)) { // System.out.println("已经发送消息"); handler.sendEmptyMessage(3); return; } try { isrun = true; if(tempFile.exists()&&dataFile.exists()){ RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rws"); // 用来记录下载进度的临时文件 tempRaf.seek(id * 8); // 将指针移动到当前线程的位置(每个线程写1个long值, 占8字节) long threadFinish = tempRaf.readLong(); // 读取当前线程已完成了多少 //判断 如果当前线程已经完成了下载,则退出该线程 if(threadFinish == threadLen){ System.out.println("线程"+id+"已经退出"); tempRaf.close(); totalFinish +=threadFinish; if (totalFinish >= totalLen && totalFinish == (totalLen + THREAD_AMOUNT - 1)) { // 如果已完成长度等于服务端文件长度(代表下载完成) System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin)); tempFile.delete(); // 删除临时文件 } return; } synchronized(BreakpointDownloader.this) { // 多个下载线程之间同步 totalFinish += threadFinish; // 统计所有线程总共完成了多少 } long start = id * threadLen + threadFinish; // 计算当前线程的起始位置 long end = id * threadLen + threadLen - 1; // 计算当前线程的结束位置 System.out.println("线程" + id + ": " + start + "-" + end); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(10000); conn.setRequestProperty("Range", "bytes=" + start + "-" + end); // 设置当前线程下载的范围 InputStream in = conn.getInputStream(); // 获取连接的输入流 RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rws"); // 装载数据的本地文件(可以理解为输出流) dataRaf.seek(start); // 设置当前线程保存数据的位置 byte[] buffer = new byte[1024 * 100]; // 每次拷贝100KB int len; while (totalFinish<totalLen && (len = in.read(buffer)) != -1) { dataRaf.write(buffer, 0, len); // 从服务端读取数据, 写到本地文件 threadFinish += len; // 每次写入数据之后, 统计当前线程完成了多少 tempRaf.seek(id * 8); // 将临时文件的指针指向当前线程的位置 tempRaf.writeLong(threadFinish); // 将当前线程完成了多少写入到临时文件 synchronized(BreakpointDownloader.this) { // 多个下载线程之间同步 totalFinish += len; // 统计所有线程总共完成了多少 Message msg4 = new Message(); msg4.getData().putLong("totalFinish", totalFinish); msg4.what = 2; handler.sendMessage(msg4); // 发送当前进度 // System.out.println("下载进度-----》"+totalFinish+"/"+totalLen); } } dataRaf.close(); tempRaf.close(); } }catch(Exception e2){ e2.printStackTrace(); isrun = false; System.out.println("2 ---》这里异常了"); if(flag) { flag = false; handler.sendEmptyMessage(3); allthreadisrun = false; } } } } if (totalFinish == totalLen || totalFinish == (totalLen + THREAD_AMOUNT - 1)) { // 如果已完成长度等于服务端文件长度(代表下载完成) System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin)); tempFile.delete(); // 删除临时文件 }else if(dataFile.length()<totalLen){ //未下载完成 System.out.println("下载未完成"); allthreadisrun = false; handler.sendEmptyMessage(3); } } } }