转载请声明:http://blog.csdn.net/yoyo_newbie/article/details/49834419
如图,一个文件可以分n块,分别用一个线程去下载。只要知道某一个开始下载点,和某一点的下载结束点,就可以下载某一段下来。那么把所有下载好的段拼接后,就是完整的文件。这就是多线程下载文件的思路。为什要使用多线程系下载呢?在理想网络充足和硬件理想好的情况下载,如果开了n线程去下载对比单线程下载,显然多线程的下载速度是单线程的 n倍快。
以下是本人封装好的demo ,本人用了线程池控制了线程并发,若想无限制在线活动线程数,自行修改。当然建议不改为好,毕竟设备支撑度始终有限
下载地址:https://github.com/Sam474850601/FileManagerDemo
视图:
activity_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:padding="10dp" android:orientation="vertical" > <TextView android:id="@+id/tv_show" android:text="点击下载" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <SeekBar android:id="@+id/seekBar" android:layout_marginTop="20dp" android:layout_gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"/> <ScrollView android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/state" android:layout_width="match_parent" android:layout_height="wrap_content" /> </ScrollView> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="下载" android:onClick="download" /> </LinearLayout>
使用方式:
public class MainActivity extends Activity { //表述 下载前后情况 private TextView tv_show; //下载文件进度条 private SeekBar seekBar; //表述当前下载进度过程字节量 private TextView state; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_show = (TextView) findViewById(R.id.tv_show); state = (TextView) findViewById(R.id.state); seekBar = (SeekBar) findViewById(R.id.seekBar); } /** * 下载点击按钮触发事件 * @param view 下载按钮 */ public void download(View view) { tv_show.setText("准备开始下载..."); NetManager netManager = new NetManager(this); final File file = new File(Environment.getExternalStorageDirectory(), "/sam/mobileqq_android.apk"); //Q安卓安装包下载地址 String url = "http://sqdd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk"; //多线程下载文件,默认开4个线程。设置进程数可调用download( String url, filePath, threadNumber, callback) netManager.download( url, file, new NetManager.DownloadCallback() { @Override public void downloading(long progress) { seekBar.setProgress((int) progress); //设置进度条当前进度 state.append("当前下载量:"+progress+"\n"); } @Override public void onSuccess() { tv_show.setText("文件下载成功!"); } @Override public void onPrepare(long filesize) { state.setText("文件总大小:"+filesize+"\n"); tv_show.setText("开始下载..."); seekBar.setMax((int) filesize);//根据文件大小设置进度条的总大小 } @Override public void onFailure(int errorState, long fileSize, List<NetManager.FileMark> fileMarks) { //如果有下载出现了下载量,且出现错误后可以保存fileMarks下来, 通过调用download(fileMarks, url, file,fiesiz, callback)继续下载。 //记录下载出错时候的原因 String error = null; switch (errorState) { case NetManager.DownloadCallback.ERROR_STATE_NOT_FOUN: { error = "下载地址不存在"; }break; case NetManager.DownloadCallback.ERROR_STATE_SERVER_EXCEPTION: { error = "服务器配置异常无法访问"; }break; case NetManager.DownloadCallback.ERROR_STATE_NETWORK_EXCEPTION: { error = "网络异常"; }break; case NetManager.DownloadCallback.EROOR_STATE_FILE_MISS: { error = "文件丢失或无法创建文件"; }break; case NetManager.DownloadCallback.ERROR_STATE_PROTOCOL_EXCEPTION: { error = "文件传输超出受限"; }break; case NetManager.DownloadCallback.ERROR_STATE_OTHER: { error = "其他异常"; }break; } tv_show.setText("下载失败!原因:"+error); } }); } }
/** * 访问网络管理器 * @author Sam */ public final class NetManager { private Handler handler; public NetManager( Context context) { handler = new Handler(context.getMainLooper()); } /** * 多线程下载文件,默认开4个线程。 * @param url 文件下载地址 * @param filePath 文件保存的路径 * @param callback 下载回调 */ public void download(String url, String filePath, DownloadCallback callback) { download(url, filePath,4, callback); } /** * 多线程下载文件,默认开4个线程。 * @param url 文件下载地址 * @param filePath 文件保存的路径 * @param callback 下载过程或结束回调 */ public void download(String url, File filePath, DownloadCallback callback) { download(url, filePath.getAbsolutePath(), callback); } /** * 多线程下载文件 * @param url 文件下载地址 * @param filePath 预设文件下载后的路径 * @param threadNumber 设置的执行的线程数目 * @param callback 下载过程或结束回调 */ public synchronized void download(final String url, final String filePath, final int threadNumber, final DownloadCallback callback) { new Thread() { @Override public void run() { InputStream inputStream = null; RandomAccessFile randomAccessFile = null; Integer errorState = -1; long fileSize = 0; try { HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.setConnectTimeout(10 * 1000); urlConnection.setDoInput(true); File file = new File(filePath); if (!file.getParentFile().exists()) file.getParentFile().mkdir(); if (file.exists()) file.delete(); file.createNewFile(); int responseCode = urlConnection.getResponseCode(); if (responseCode >= 200 && responseCode < 300) { fileSize = urlConnection.getContentLength();//获取文件大小 randomAccessFile = new RandomAccessFile(file, "rwd"); randomAccessFile.setLength(fileSize);//设置文件大小 randomAccessFile.close();//设置文件大小后关闭 long value = fileSize % threadNumber; boolean isDivision = 0 == value;//是否整除 long unit = isDivision ? fileSize / threadNumber : (fileSize - value) / threadNumber; int block = isDivision ? threadNumber : threadNumber + 1; //如果不整除,分多一块处理 List<FileMark> fileMarks = new ArrayList<FileMark>(); for (int i = 0; i < block; i++) { FileMark fileMark = new FileMark(); fileMark.startMark = i * unit;//开始下载位置 long enValue = (i + 1) * unit - 1; fileMark.endMark = i != block - 1?enValue:fileSize; fileMarks.add(fileMark); } download(fileMarks, url, file, fileSize, callback); } else { errorState = DownloadCallback.ERROR_STATE_SERVER_EXCEPTION; } } catch (MalformedURLException e) { errorState = DownloadCallback.ERROR_STATE_NOT_FOUN; } catch (FileNotFoundException e) { errorState = DownloadCallback.EROOR_STATE_FILE_MISS; } catch (ProtocolException e) { errorState = DownloadCallback.ERROR_STATE_PROTOCOL_EXCEPTION; } catch (IOException e) { errorState = DownloadCallback.ERROR_STATE_NETWORK_EXCEPTION; } catch (Exception ex) { errorState = DownloadCallback.ERROR_STATE_OTHER; } finally { if (null != randomAccessFile) { try { randomAccessFile.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != inputStream) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (-1 != errorState) { final int es = errorState; final long size = fileSize; handler.post(new Runnable() { @Override public void run() { callback.onFailure(es, size, null); } }); } } } }.start(); } private class CloseThread { int threadDistroyNum=1; long downloadQuantity; int errorState = -1; boolean hasException; } /** * 多线程下载文件, 可以通过传入FileMark下载。 * @param fileMarks 需要下载文件段落集合 * @param url 下载路径 * @param file 下载文件 * @param fileSize 文件总长度 * @param callback 下载过程或结束回调 */ public synchronized void download(List<FileMark> fileMarks, String url, File file, final long fileSize, final DownloadCallback callback) { handler.post(new Runnable() { @Override public void run() { callback.onPrepare(fileSize); } }); CloseThread ct = new CloseThread(); List<FileMark> remindFileMarks = new ArrayList<FileMark>(); long remindQuantity = 0; for (FileMark fileMark : fileMarks) { long value = fileMark.endMark - fileMark.startMark+1; remindQuantity += value; } ct.downloadQuantity = fileSize - remindQuantity+1; for (FileMark fileMark : fileMarks) { DownloadFileThread thread = new DownloadFileThread(url, file, fileMark, fileMarks.size(), ct, remindFileMarks, fileSize, callback); ThreadManager.getPool().execute(thread); } } /** * 线程池管理器 */ public static class ThreadManager { /** * 最大队列长度 */ private static final int MAX_QUEUE_LENGTH = 128; /** * 常驻内在线程数 */ private static final int ALIVE_THREAD_SIZE = 5; /** * 最大活动线程数 */ private static final int MAX_THREAD_SIZE = 15; /** * 线程空置多长时间销毁 */ private static final int THREAD_ALIVE_SECONDS = 60; /** * 线程池 */ private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(ALIVE_THREAD_SIZE, MAX_THREAD_SIZE, THREAD_ALIVE_SECONDS, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(MAX_QUEUE_LENGTH), new ThreadPoolExecutor.DiscardOldestPolicy()); /** * 取得线程池实例 * * @return */ public static final ThreadPoolExecutor getPool() { return threadPool; } } /** * 下载执行回调 */ public interface DownloadCallback { /** * 下载地址不存在 */ public static final int ERROR_STATE_NOT_FOUN = 0x01; /** * 服务器配置异常无法访问 */ public static final int ERROR_STATE_SERVER_EXCEPTION = 0x02; /** * 网络异常 */ public static final int ERROR_STATE_NETWORK_EXCEPTION = 0x03; /** * 文件丢失或无法创建文件 */ public static final int EROOR_STATE_FILE_MISS = 0x04; /** * 文件传输超出受限 */ public static final int ERROR_STATE_PROTOCOL_EXCEPTION = 0x05; /** * 其他异常 */ public static final int ERROR_STATE_OTHER = 0x06; /** * 下载时候,进度情况 * * @param progress 当前下载量 * */ void downloading(long progress); /** * 下载成功 */ void onSuccess(); /** * 下载之前回调 * @param filesize 文件长度 */ void onPrepare(long filesize); /** * 下载过程出现错误 * * @param errorState 下载错误情况 * @param fileMarks 剩余为下载的部分 * @param fileSize 总文件大小 */ void onFailure(int errorState, long fileSize, List<FileMark> fileMarks); } /** * 文件下载的位置信息 */ public static class FileMark { /** * 开始下载的长度 */ public long startMark; /** * 终止下载的长度 */ public long endMark; @Override public String toString() { return "FileMark{" + "startMark=" + startMark + ", endMark=" + endMark + '}'; } } /** * 下载文件块线程 */ private class DownloadFileThread extends Thread { private String url;//下载地址 private File file;//文件路径 private FileMark fileMark;//文件下载块信息 private DownloadCallback callback; private CloseThread ct;//线程结束数目 private int totalThread;//总线程数目 private long fileSize; private List<FileMark> remindFileMarks; public DownloadFileThread(String url, File file, FileMark fileMark, int totalThread, CloseThread ct, List<FileMark> remindFileMarks, long fileSize, DownloadCallback callback) { this.url = url; this.file = file; this.fileMark = fileMark; this.totalThread = totalThread; this.ct = ct; this.callback = callback; this.fileSize = fileSize; this.remindFileMarks = remindFileMarks; } @Override public void run() { InputStream inputStream = null; RandomAccessFile randomAccessFile = null; long currentDownQuantity = 0; try { HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.setConnectTimeout(10 * 1000); urlConnection.setReadTimeout(10*1000); urlConnection.setRequestProperty("Range", "bytes=" + fileMark.startMark + "-" + fileMark.endMark); int responseCode = urlConnection.getResponseCode(); if (responseCode >= 200 && responseCode < 300) { inputStream = urlConnection.getInputStream(); int len = 0; byte[] data = new byte[1024 * 8]; randomAccessFile = new RandomAccessFile(file, "rwd"); randomAccessFile.seek(fileMark.startMark);//设置往文件写入部分 while (-1 != (len = inputStream.read(data))) { currentDownQuantity += len; randomAccessFile.write(data, 0, len); final long value =(ct.downloadQuantity = ct.downloadQuantity + len); handler.post(new Runnable() { @Override public void run() { callback.downloading(value); } }); } } else { ct.errorState = DownloadCallback.ERROR_STATE_SERVER_EXCEPTION; } } catch (MalformedURLException e) { ct.errorState = DownloadCallback.ERROR_STATE_NOT_FOUN; } catch (FileNotFoundException e) { ct.errorState = DownloadCallback.EROOR_STATE_FILE_MISS; } catch (ProtocolException e) { ct.errorState = DownloadCallback.ERROR_STATE_PROTOCOL_EXCEPTION; } catch (IOException e) { ct.errorState = DownloadCallback.ERROR_STATE_NETWORK_EXCEPTION; } catch (Exception ex) { ct.errorState = DownloadCallback.ERROR_STATE_OTHER; } finally { if (null != randomAccessFile) { try { randomAccessFile.close(); } catch (IOException e) { } } if (null != inputStream) { try { inputStream.close(); } catch (IOException e) { } } if (-1 != ct.errorState ) ct.hasException =true; synchronized (ct) { if( ct.threadDistroyNum++ == totalThread) { fileMark.startMark += currentDownQuantity; if(ct.hasException) { remindFileMarks.add(fileMark); } handler.post(new Runnable() { @Override public void run() { if (remindFileMarks.isEmpty()) { callback.onSuccess(); } else { callback.onFailure( ct.errorState , fileSize, remindFileMarks); } } }); } } } } } }