Android下载文件相关--多线程下载和断点续传

1.简单一例子-Android经过HTTP协议实现多线程下载


[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
  1. import java.io.File;  
  2. import java.io.InputStream;  
  3. import java.io.RandomAccessFile;  
  4. import java.net.HttpURLConnection;  
  5. import java.net.URL;  
  6.   
  7. public class MulThreadDownload {  
  8.   
  9.     /** 
  10.      * @param args 
  11.      */  
  12.     public static void main(String[] args) {  
  13.         String path = "http://net.itcast.cn/QQWubiSetup.exe";  
  14.         try {  
  15.             new MulThreadDownload().download(path, 3);  
  16.         } catch (Exception e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20.     /** 
  21.      * 从路径中获取文件名称 
  22.      * @param path 下载路径 
  23.      * @return 
  24.      */  
  25.     public static String getFilename(String path){  
  26.         return path.substring(path.lastIndexOf('/')+1);  
  27.     }  
  28.     /** 
  29.      * 下载文件 
  30.      * @param path 下载路径 
  31.      * @param threadsize 线程数 
  32.      */  
  33.     public void download(String path, int threadsize) throws Exception{  
  34.         URL url = new URL(path);  
  35.         HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  36.         conn.setRequestMethod("GET");  
  37.         conn.setConnectTimeout(5 * 1000);  
  38.         int filelength = conn.getContentLength();//获取要下载的文件的长度  
  39.         String filename = getFilename(path);//从路径中获取文件名称  
  40.         File saveFile = new File(filename);  
  41.         RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");  
  42.         accessFile.setLength(filelength);//设置本地文件的长度和下载文件相同  
  43.         accessFile.close();  
  44.         //计算每条线程下载的数据长度  
  45.         int block = filelength%threadsize==0? filelength/threadsize : filelength/threadsize+1;  
  46.         for(int threadid=0 ; threadid < threadsize ; threadid++){  
  47.             new DownloadThread(url, saveFile, block, threadid).start();  
  48.         }  
  49.     }  
  50.       
  51.     private final class DownloadThread extends Thread{  
  52.         private URL url;  
  53.         private File saveFile;  
  54.         private int block;//每条线程下载的数据长度  
  55.         private int threadid;//线程id  
  56.   
  57.         public DownloadThread(URL url, File saveFile, int block, int threadid) {  
  58.             this.url = url;  
  59.             this.saveFile = saveFile;  
  60.             this.block = block;  
  61.             this.threadid = threadid;  
  62.         }  
  63.   
  64.         @Override  
  65.         public void run() {  
  66.             //计算开始位置公式:线程id*每条线程下载的数据长度= ?  
  67.             //计算结束位置公式:(线程id +1)*每条线程下载的数据长度-1 =?  
  68.             int startposition = threadid * block;  
  69.             int endposition = (threadid + 1 ) * block - 1;  
  70.             try {  
  71.                 RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");  
  72.                 accessFile.seek(startposition);//设置从什么位置开始写入数据  
  73.                 HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  74.                 conn.setRequestMethod("GET");  
  75.                 conn.setConnectTimeout(5 * 1000);  
  76.                 conn.setRequestProperty("Range""bytes="+ startposition+ "-"+ endposition);  
  77.                 InputStream inStream = conn.getInputStream();  
  78.                 byte[] buffer = new byte[1024];  
  79.                 int len = 0;  
  80.                 while( (len=inStream.read(buffer)) != -1 ){  
  81.                     accessFile.write(buffer, 0, len);  
  82.                 }  
  83.                 inStream.close();  
  84.                 accessFile.close();  
  85.                 System.out.println("线程id:"+ threadid+ "下载完成");  
  86.             } catch (Exception e) {  
  87.                 e.printStackTrace();  
  88.             }  
  89.         }         
  90.     }  
  91.   
  92. }  

2.Android基于HTTP协议的多线程断点下载器的实现


1、首先写这篇文章以前,要了解实现该Android多线程断点下载器的几个知识点html

 1.多线程下载的原理,以下图所示java


注意:因为Android移动设备和PC机的处理器仍是不能相比,因此开辟的子线程建议不要多于5条。固然如今某些高端机子的处理器能力比较强了,就能够多开辟几条子线程。android

二、为了实现断点下载,采用数据库方式记录下载的进度,这样当你将该应用退出后,下次点击下载的时候,程序会去查看该下载连接是否存在下载记录,若是存在下载记录就会判断下载的进度,如何从上次下载的进度继续开始下载。正则表达式

三、特别注意在主线程里不能执行一件比较耗时的工做,不然会因主线程阻塞而没法处理用户的输入事件,致使“应用无响应”错误的出现。耗时的工做应该在子线程里执行。sql

四、UI控件画面的重绘(更新)是由主线程负责处理的,不能在子线程中更新UI控件的值。能够采用Handler机制,在主线程建立Handler对象,在子线程发送消息给主线程所绑定的消息队列,从消息中获取UI控件的值,而后在主线程中进行UI控件的重绘(更新)工做。
五、了解HTTP协议各个头字段的含义
数据库


2、将该下载器的具体实现代码展示出来数组

step一、首先查看整个Android项目的结构图浏览器

                                           

step2:设计应用的UI界面   /layout/activity_main.xml缓存

[html]  view plain copy
  1. <span style="font-size:18px"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:orientation="vertical" android:layout_width="fill_parent"  
  3.     android:layout_height="fill_parent">  
  4.   
  5.     <TextView  
  6.         android:layout_width="fill_parent"  
  7.         android:layout_height="wrap_content"  
  8.         android:text="@string/path" />  
  9.   
  10.     <EditText  
  11.         android:id="@+id/path"  
  12.         android:layout_width="fill_parent"  
  13.         android:layout_height="wrap_content"  
  14.         android:text="http://192.168.1.100:8080/Hello/a.mp4" />  
  15.   
  16.     <LinearLayout  
  17.         android:layout_width="fill_parent"  
  18.         android:layout_height="wrap_content"  
  19.         android:orientation="horizontal" >  
  20.   
  21.         <Button  
  22.             android:id="@+id/downloadbutton"  
  23.             android:layout_width="wrap_content"  
  24.             android:layout_height="wrap_content"  
  25.             android:text="@string/startbutton" />  
  26.   
  27.         <Button  
  28.             android:id="@+id/stopbutton"  
  29.             android:layout_width="wrap_content"  
  30.             android:layout_height="wrap_content"  
  31.             android:enabled="false"  
  32.             android:text="@string/stopbutton" />  
  33.     </LinearLayout>  
  34.   
  35.     <ProgressBar  
  36.         android:id="@+id/progressBar"  
  37.         style="?android:attr/progressBarStyleHorizontal"  
  38.         android:layout_width="fill_parent"  
  39.         android:layout_height="18dp" />  
  40.   
  41.     <TextView  
  42.         android:id="@+id/resultView"  
  43.         android:layout_width="fill_parent"  
  44.         android:layout_height="wrap_content"  
  45.         android:gravity="center" />  
  46. </LinearLayout></span>  


/values/string.xml服务器

[html]  view plain copy
  1. <span style="font-size:18px"><?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <string name="action_settings">Settings</string>  
  4.     <string name="hello_world">Hello world!</string>  
  5.     <string name="app_name">多线程断点下载器_欧阳鹏编写</string>  
  6.     <string name="path">下载路径</string>  
  7.     <string name="startbutton">开始下载</string>  
  8.     <string name="success">下载完成</string>  
  9.     <string name="error">下载失败</string>  
  10.     <string name="stopbutton">中止下载</string>  
  11.     <string name="sdcarderror">SDCard不存在或者写保护</string>  
  12. </resources></span>  


step三、程序主应用 cn.oyp.download.MainActivity.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.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.os.Handler;  
  9. import android.os.Message;  
  10. import android.view.View;  
  11. import android.widget.Button;  
  12. import android.widget.EditText;  
  13. import android.widget.ProgressBar;  
  14. import android.widget.TextView;  
  15. import android.widget.Toast;  
  16. import cn.oyp.download.downloader.DownloadProgressListener;  
  17. import cn.oyp.download.downloader.FileDownloader;  
  18.   
  19. public class MainActivity extends Activity {  
  20.   
  21.     /** 下载路径文本框 **/  
  22.     private EditText pathText;  
  23.     /** 下载按钮 **/  
  24.     private Button downloadButton;  
  25.     /** 中止下载按钮 **/  
  26.     private Button stopbutton;  
  27.     /** 下载进度条 **/  
  28.     private ProgressBar progressBar;  
  29.     /** 下载结果文本框,显示下载的进度值 **/  
  30.     private TextView resultView;  
  31.   
  32.     /** Hanlder的做用是用于往建立Hander对象所在的线程所绑定的消息队列发送消息 **/  
  33.     private Handler handler = new UIHander();  
  34.   
  35.     @Override  
  36.     protected void onCreate(Bundle savedInstanceState) {  
  37.         super.onCreate(savedInstanceState);  
  38.         setContentView(R.layout.activity_main);  
  39.   
  40.         /** 初始化各控件 **/  
  41.         pathText = (EditText) this.findViewById(R.id.path);  
  42.         downloadButton = (Button) this.findViewById(R.id.downloadbutton);  
  43.         stopbutton = (Button) this.findViewById(R.id.stopbutton);  
  44.         progressBar = (ProgressBar) this.findViewById(R.id.progressBar);  
  45.         resultView = (TextView) this.findViewById(R.id.resultView);  
  46.         /** 设置按钮的监听 **/  
  47.         ButtonClickListener listener = new ButtonClickListener();  
  48.         downloadButton.setOnClickListener(listener);  
  49.         stopbutton.setOnClickListener(listener);  
  50.     }  
  51.   
  52.     /** 
  53.      * Hanlder的做用是用于往建立Hander对象所在的线程所绑定的消息队列发送消息 
  54.      */  
  55.     private final class UIHander extends Handler {  
  56.         public void handleMessage(Message msg) {  
  57.             switch (msg.what) {  
  58.             case 1:  
  59.                 int size = msg.getData().getInt("size"); // 获取下载的进度值  
  60.                 progressBar.setProgress(size); // 实时更新,设置下载进度值  
  61.                 /** 计算下载的进度百分比 */  
  62.                 float num = (float) progressBar.getProgress()  
  63.                         / (float) progressBar.getMax();  
  64.                 int result = (int) (num * 100);  
  65.                 resultView.setText(result + "%"); // 设置下载结果文本框显示下载的进度值  
  66.                 // 若是进度达到了进度最大值,即下载完毕  
  67.                 if (progressBar.getProgress() == progressBar.getMax()) {  
  68.                     Toast.makeText(getApplicationContext(), R.string.success, 1)  
  69.                             .show();// 下载成功  
  70.                 }  
  71.                 break;  
  72.             case -1:  
  73.                 Toast.makeText(getApplicationContext(), R.string.error, 1)  
  74.                         .show();// 下载出错  
  75.                 break;  
  76.             }  
  77.         }  
  78.     }  
  79.       
  80.     /** 
  81.      * 按钮监听类 
  82.      */  
  83.     private final class ButtonClickListener implements View.OnClickListener {  
  84.         public void onClick(View v) {  
  85.             switch (v.getId()) {  
  86.             /** 若是是下载按钮 */  
  87.             case R.id.downloadbutton:  
  88.                 String path = pathText.getText().toString();// 获取下载路径  
  89.                 // 判断SD卡是否存在而且可写  
  90.                 if (Environment.getExternalStorageState().equals(  
  91.                         Environment.MEDIA_MOUNTED)) {  
  92.                     // 获取SD卡的路径  
  93.                     File saveDir = Environment.getExternalStorageDirectory();  
  94.                     // 开始下载的相关操做  
  95.                     download(path, saveDir);  
  96.                 } else {  
  97.                     Toast.makeText(getApplicationContext(),  
  98.                             R.string.sdcarderror, 1).show();  
  99.                 }  
  100.                 downloadButton.setEnabled(false);  
  101.                 stopbutton.setEnabled(true);  
  102.                 break;  
  103.             /** 若是是中止下载按钮 */  
  104.             case R.id.stopbutton:  
  105.                 exit();// 退出下载  
  106.                 downloadButton.setEnabled(true);  
  107.                 stopbutton.setEnabled(false);  
  108.                 break;  
  109.             }  
  110.         }  
  111.   
  112.         /** 
  113.          * UI控件画面的重绘(更新)是由主线程负责处理的,若是在子线程中更新UI控件的值,更新后的值不会重绘到屏幕上 
  114.          * 必定要在主线程里更新UI控件的值,这样才能在屏幕上显示出来,不能在子线程中更新UI控件的值 
  115.          * 借用Handler来传送UI控件的值到主线程去,在主线程更新UI控件的值 
  116.          */  
  117.         private final class DownloadTask implements Runnable {  
  118.             /** 下载路径 */  
  119.             private String path;  
  120.             /** 保存路径 */  
  121.             private File saveDir;  
  122.             /** 文件下载器 */  
  123.             private FileDownloader loader;  
  124.   
  125.             /** 
  126.              * DownloadTask的构造函数 
  127.              *  
  128.              * @param path 
  129.              *            下载路径 
  130.              * @param saveDir 
  131.              *            保存路径 
  132.              */  
  133.             public DownloadTask(String path, File saveDir) {  
  134.                 this.path = path;  
  135.                 this.saveDir = saveDir;  
  136.             }  
  137.   
  138.             /** 
  139.              * 线程主方法 
  140.              */  
  141.             public void run() {  
  142.                 try {  
  143.                     /** 
  144.                      * 构建文件下载器 将下载路径,文件保存目录,下载线程数指定好 
  145.                      */  
  146.                     loader = new FileDownloader(getApplicationContext(), path,  
  147.                             saveDir, 5);  
  148.                     progressBar.setMax(loader.getFileSize());// 设置进度条的最大刻度(即文件的总长度)  
  149.                     /** 
  150.                      * DownloadProgressListener是一个接口,onDownloadSize()为未实现的方法。 
  151.                      * onDownloadSize()方法会在download方法内部被动态赋值 
  152.                      * 监听下载数量的变化,若是不须要了解实时下载的数量,能够设置为null 
  153.                      */  
  154.                     loader.download(new DownloadProgressListener() {  
  155.                         public void onDownloadSize(int size) {  
  156.                             // 借用Handler来传送UI控件的值到主线程去,在主线程更新UI控件的值  
  157.                             Message msg = new Message();  
  158.                             msg.what = 1// 对应UIHander 得到的msg.what  
  159.                             msg.getData().putInt("size", size); // 将获取的值发送给handler,用于动态更新进度  
  160.                             handler.sendMessage(msg);  
  161.                         }  
  162.                     });  
  163.                 } catch (Exception e) {  
  164.                     e.printStackTrace();  
  165.                     // 对应UIHander 得到的msg.what  
  166.                     handler.sendMessage(handler.obtainMessage(-1));   
  167.                 }  
  168.             }  
  169.   
  170.             /** 
  171.              * 退出下载 
  172.              */  
  173.             public void exit() {  
  174.                 if (loader != null)  
  175.                     loader.exit();  
  176.             }  
  177.         }  
  178.   
  179.         /** end of DownloadTask */  
  180.   
  181.         /** 
  182.          * 因为用户的输入事件(点击button, 触摸屏幕....)是由主线程负责处理的,若是主线程处于工做状态, 
  183.          * 此时用户产生的输入事件若是没能在5秒内获得处理,系统就会报“应用无响应”错误。 
  184.          * 因此在主线程里不能执行一件比较耗时的工做,不然会因主线程阻塞而没法处理用户的输入事件, 
  185.          * 致使“应用无响应”错误的出现。耗时的工做应该在子线程里执行。 
  186.          */  
  187.         private DownloadTask task;  
  188.   
  189.         /** 
  190.          * 退出下载 
  191.          */  
  192.         public void exit() {  
  193.             if (task != null)  
  194.                 task.exit();  
  195.         }  
  196.   
  197.         /** 
  198.          * 下载方法,运行在主线程,负责开辟子线程完成下载操做,这操做耗时不超过1秒 
  199.          *  
  200.          * @param path 
  201.          *            下载路径 
  202.          * @param saveDir 
  203.          *            保存路径 
  204.          */  
  205.         private void download(String path, File saveDir) {  
  206.             task = new DownloadTask(path, saveDir);  
  207.             new Thread(task).start();// 开辟子线程完成下载操做  
  208.         }  
  209.     }  
  210.     /** end of ButtonClickListener **/  
  211. }  
  212. </span>  


文件下载器cn.oyp.download.downloader.FileDownloader.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.downloader;  
  2.   
  3. import java.io.File;  
  4. import java.io.RandomAccessFile;  
  5. import java.net.HttpURLConnection;  
  6. import java.net.URL;  
  7. import java.util.LinkedHashMap;  
  8. import java.util.Map;  
  9. import java.util.UUID;  
  10. import java.util.concurrent.ConcurrentHashMap;  
  11. import java.util.regex.Matcher;  
  12. import java.util.regex.Pattern;  
  13.   
  14. import cn.oyp.download.service.FileService;  
  15.   
  16. import android.content.Context;  
  17. import android.util.Log;  
  18.   
  19. /** 
  20.  * 文件下载器 
  21.  */  
  22. public class FileDownloader {  
  23.     private static final String TAG = "FileDownloader";  
  24.     /** 上下文 */  
  25.     private Context context;  
  26.     /** 文件下载服务类 */  
  27.     private FileService fileService;  
  28.     /** 是否中止下载 */  
  29.     private boolean exit;  
  30.     /** 已下载文件长度 */  
  31.     private int downloadSize = 0;  
  32.     /** 原始文件长度 */  
  33.     private int fileSize = 0;  
  34.     /** 用于下载的线程数组 */  
  35.     private DownloadThread[] threads;  
  36.     /** 本地保存文件 */  
  37.     private File saveFile;  
  38.     /** 缓存各线程下载的长度 */  
  39.     private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();  
  40.     /** 每条线程下载的长度 */  
  41.     private int block;  
  42.     /** 下载路径 */  
  43.     private String downloadUrl;  
  44.   
  45.     /** 
  46.      * 获取线程数 
  47.      */  
  48.     public int getThreadSize() {  
  49.         return threads.length;  
  50.     }  
  51.   
  52.     /** 
  53.      * 退出下载 
  54.      */  
  55.     public void exit() {  
  56.         this.exit = true;  
  57.     }  
  58.   
  59.     /** 
  60.      * 是否退出下载 
  61.      */  
  62.     public boolean getExit() {  
  63.         return this.exit;  
  64.     }  
  65.   
  66.     /** 
  67.      * 获取文件大小 
  68.      */  
  69.     public int getFileSize() {  
  70.         return fileSize;  
  71.     }  
  72.   
  73.     /** 
  74.      * 累计已下载大小 
  75.      * 该方法在具体某个线程下载的时候会被调用 
  76.      */  
  77.     protected synchronized void append(int size) {  
  78.         downloadSize += size;  
  79.     }  
  80.   
  81.     /** 
  82.      * 更新指定线程最后下载的位置 
  83.      * 该方法在具体某个线程下载的时候会被调用 
  84.      * @param threadId 
  85.      *            线程id 
  86.      * @param pos 
  87.      *            最后下载的位置 
  88.      */  
  89.     protected synchronized void update(int threadId, int pos) {  
  90.         // 缓存各线程下载的长度  
  91.         this.data.put(threadId, pos);  
  92.         // 更新数据库中的各线程下载的长度  
  93.         this.fileService.update(this.downloadUrl, threadId, pos);  
  94.     }  
  95.   
  96.     /** 
  97.      * 构建文件下载器 
  98.      *  
  99.      * @param downloadUrl 
  100.      *            下载路径 
  101.      * @param fileSaveDir 
  102.      *            文件保存目录 
  103.      * @param threadNum 
  104.      *            下载线程数 
  105.      */  
  106.     public FileDownloader(Context context, String downloadUrl,  
  107.             File fileSaveDir, int threadNum) {  
  108.         try {  
  109.             this.context = context;  
  110.             this.downloadUrl = downloadUrl;  
  111.             fileService = new FileService(this.context);  
  112.             // 根据指定的下载路径,生成URL  
  113.             URL url = new URL(this.downloadUrl);  
  114.             if (!fileSaveDir.exists())  
  115.                 fileSaveDir.mkdirs();// 若是保存路径不存在,则新建一个目录  
  116.             // 根据指定的线程数来新建线程数组  
  117.             this.threads = new DownloadThread[threadNum];  
  118.             // 打开HttpURLConnection  
  119.             HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  120.             // 设置 HttpURLConnection的断开时间  
  121.             conn.setConnectTimeout(5 * 1000);  
  122.             // 设置 HttpURLConnection的请求方式  
  123.             conn.setRequestMethod("GET");  
  124.             // 设置 HttpURLConnection的接收的文件类型  
  125.             conn.setRequestProperty(  
  126.                     "Accept",  
  127.                     "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "  
  128.                             + "application/x-shockwave-flash, application/xaml+xml, "  
  129.                             + "application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, "  
  130.                             + "application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");  
  131.             // 设置 HttpURLConnection的接收语音  
  132.             conn.setRequestProperty("Accept-Language""zh-CN");  
  133.             // 指定请求uri的源资源地址  
  134.             conn.setRequestProperty("Referer", downloadUrl);  
  135.             // 设置 HttpURLConnection的字符编码  
  136.             conn.setRequestProperty("Charset""UTF-8");  
  137.             // 检查浏览页面的访问者在用什么操做系统(包括版本号)浏览器(包括版本号)和用户我的偏好  
  138.             conn.setRequestProperty(  
  139.                     "User-Agent",  
  140.                     "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2;"  
  141.                             + " Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; "  
  142.                             + ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152;"  
  143.                             + " .NET CLR 3.5.30729)");  
  144.             conn.setRequestProperty("Connection""Keep-Alive");  
  145.             conn.connect();  
  146.             // 打印Http协议头  
  147.             printResponseHeader(conn);  
  148.             // 若是返回的状态码为200表示正常  
  149.             if (conn.getResponseCode() == 200) {  
  150.                 this.fileSize = conn.getContentLength();// 根据响应获取文件大小  
  151.                 if (this.fileSize <= 0)  
  152.                     throw new RuntimeException("Unkown file size ");  
  153.   
  154.                 String filename = getFileName(conn);// 获取文件名称  
  155.                 this.saveFile = new File(fileSaveDir, filename);// 构建保存文件  
  156.                 Map<Integer, Integer> logdata = fileService  
  157.                         .getData(downloadUrl);// 获取下载记录  
  158.                 if (logdata.size() > 0) {// 若是存在下载记录  
  159.                     for (Map.Entry<Integer, Integer> entry : logdata.entrySet())  
  160.                         data.put(entry.getKey(), entry.getValue());// 把各条线程已经下载的数据长度放入data中  
  161.                 }  
  162.                 if (this.data.size() == this.threads.length) {// 下面计算全部线程已经下载的数据总长度  
  163.                     for (int i = 0; i < this.threads.length; i++) {  
  164.                         this.downloadSize += this.data.get(i + 1);  
  165.                     }  
  166.                     print("已经下载的长度" + this.downloadSize);  
  167.                 }  
  168.                 // 计算每条线程下载的数据长度  
  169.                 this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize  
  170.                         / this.threads.length  
  171.                         : this.fileSize / this.threads.length + 1;  
  172.             } else {  
  173.                 throw new RuntimeException("server no response ");  
  174.             }  
  175.         } catch (Exception e) {  
  176.             print(e.toString());  
  177.             throw new RuntimeException("don't connection this url");  
  178.         }  
  179.     }  
  180.   
  181.     /**  
  182.      * 获取文件名  
  183.      *   
  184.      * @param conn  
  185.      *            Http链接  
  186.      */  
  187.     private String getFileName(HttpURLConnection conn) {  
  188.         String filename = this.downloadUrl.substring(this.downloadUrl  
  189.                 .lastIndexOf('/') + 1);// 截取下载路径中的文件名  
  190.         // 若是获取不到文件名称  
  191.         if (filename == null || "".equals(filename.trim())) {  
  192.             // 经过截取Http协议头分析下载的文件名  
  193.             for (int i = 0;; i++) {  
  194.                 String mine = conn.getHeaderField(i);  
  195.                 if (mine == null)  
  196.                     break;  
  197.                 /** 
  198.                  * Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 
  199.                  * 用户代理如何显示附加的文件。 
  200.                  * Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名 
  201.                  * 协议头中的Content-Disposition格式以下: 
  202.                  * Content-Disposition","attachment;filename=FileName.txt"); 
  203.                  */  
  204.                 if ("content-disposition".equals(conn.getHeaderFieldKey(i)  
  205.                         .toLowerCase())) {  
  206.                     // 经过正则表达式匹配出文件名  
  207.                     Matcher m = Pattern.compile(".*filename=(.*)").matcher(  
  208.                             mine.toLowerCase());  
  209.                     // 若是匹配到了文件名  
  210.                     if (m.find())  
  211.                         return m.group(1);// 返回匹配到的文件名  
  212.                 }  
  213.             }  
  214.             // 若是仍是匹配不到文件名,则默认取一个随机数文件名  
  215.             filename = UUID.randomUUID() + ".tmp";  
  216.         }  
  217.         return filename;  
  218.     }  
  219.   
  220.     /** 
  221.      * 开始下载文件 
  222.      *  
  223.      * @param listener 
  224.      *            监听下载数量的变化,若是不须要了解实时下载的数量,能够设置为null 
  225.      * @return 已下载文件大小 
  226.      * @throws Exception 
  227.      */  
  228.     public int download(DownloadProgressListener listener) throws Exception {  
  229.         try {  
  230.             RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");  
  231.             if (this.fileSize > 0)  
  232.                 randOut.setLength(this.fileSize);  
  233.             randOut.close();  
  234.             URL url = new URL(this.downloadUrl);  
  235.             // 若是原先不曾下载或者原先的下载线程数与如今的线程数不一致  
  236.             if (this.data.size() != this.threads.length) {  
  237.                 this.data.clear();// 清除原来的线程数组  
  238.                 for (int i = 0; i < this.threads.length; i++) {  
  239.                     this.data.put(i + 10);// 初始化每条线程已经下载的数据长度为0  
  240.                 }  
  241.                 this.downloadSize = 0;  
  242.             }  
  243.             //循环遍历线程数组  
  244.             for (int i = 0; i < this.threads.length; i++) {  
  245.                 int downLength = this.data.get(i + 1); // 获取当前线程下载的文件长度  
  246.                 // 判断线程是否已经完成下载,不然继续下载  
  247.                 if (downLength < this.block  
  248.                         && this.downloadSize < this.fileSize) {  
  249.                     //启动线程开始下载  
  250.                     this.threads[i] = new DownloadThread(this, url,  
  251.                             this.saveFile, this.block, this.data.get(i + 1),  
  252.                             i + 1);  
  253.                     this.threads[i].setPriority(7);  
  254.                     this.threads[i].start();  
  255.                 } else {  
  256.                     this.threads[i] = null;  
  257.                 }  
  258.             }  
  259.             //若是存在下载记录,从数据库中删除它们  
  260.             fileService.delete(this.downloadUrl);  
  261.             //从新保存下载的进度到数据库  
  262.             fileService.save(this.downloadUrl, this.data);  
  263.             boolean notFinish = true;// 下载未完成  
  264.             while (notFinish) {// 循环判断全部线程是否完成下载  
  265.                 Thread.sleep(900);  
  266.                 notFinish = false;// 假定所有线程下载完成  
  267.                 for (int i = 0; i < this.threads.length; i++) {  
  268.                     if (this.threads[i] != null && !this.threads[i].isFinish()) {// 若是发现线程未完成下载  
  269.                         notFinish = true;// 设置标志为下载没有完成  
  270.                         // 若是下载失败,再从新下载  
  271.                         if (this.threads[i].getDownLength() == -1) {  
  272.                             this.threads[i] = new DownloadThread(this, url,  
  273.                                     this.saveFile, this.block,  
  274.                                     this.data.get(i + 1), i + 1);  
  275.                             this.threads[i].setPriority(7);  
  276.                             this.threads[i].start();  
  277.                         }  
  278.                     }  
  279.                 }  
  280.                 if (listener != null)  
  281.                     listener.onDownloadSize(this.downloadSize);// 通知目前已经下载完成的数据长度  
  282.             }  
  283.             // 若是下载完成  
  284.             if (downloadSize == this.fileSize)  
  285.                 fileService.delete(this.downloadUrl);// 下载完成删除记录  
  286.         } catch (Exception e) {  
  287.             print(e.toString());  
  288.             throw new Exception("file download error");  
  289.         }  
  290.         return this.downloadSize;  
  291.     }  
  292.   
  293.     /** 
  294.      * 获取Http响应头字段 
  295.      * @param http 
  296.      * @return 
  297.      */  
  298.     public static Map<String, String> getHttpResponseHeader(  
  299.             HttpURLConnection http) {  
  300.         Map<String, String> header = new LinkedHashMap<String, String>();  
  301.         for (int i = 0;; i++) {  
  302.             String mine = http.getHeaderField(i);  
  303.             if (mine == null)  
  304.                 break;  
  305.             header.put(http.getHeaderFieldKey(i), mine);  
  306.         }  
  307.         return header;  
  308.     }  
  309.   
  310.     /** 
  311.      * 打印Http头字段 
  312.      *  
  313.      * @param http 
  314.      */  
  315.     public static void printResponseHeader(HttpURLConnection http) {  
  316.         Map<String, String> header = getHttpResponseHeader(http);  
  317.         for (Map.Entry<String, String> entry : header.entrySet()) {  
  318.             String key = entry.getKey() != null ? entry.getKey() + ":" : "";  
  319.             print(key + entry.getValue());  
  320.         }  
  321.     }  
  322.     /** 
  323.      * 打印信息 
  324.      * @param msg  信息 
  325.      */  
  326.     private static void print(String msg) {  
  327.         Log.i(TAG, msg);  
  328.     }  
  329. }  
  330. </span>  

文件下载线程 cn.oyp.download.downloader.DownloadThread.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.downloader;  
  2.   
  3.   
  4. import java.io.File;  
  5. import java.io.InputStream;  
  6. import java.io.RandomAccessFile;  
  7. import java.net.HttpURLConnection;  
  8. import java.net.URL;  
  9.   
  10. import android.util.Log;  
  11.   
  12. public class DownloadThread extends Thread {  
  13.     private static final String TAG = "DownloadThread";  
  14.     /** 本地保存文件 */  
  15.     private File saveFile;  
  16.     /** 下载路径 */  
  17.     private URL downUrl;  
  18.     /** 该线程要下载的长度 */  
  19.     private int block;  
  20.     /** 线程ID */  
  21.     private int threadId = -1;  
  22.     /** 该线程已经下载的长度 */  
  23.     private int downLength;  
  24.     /** 是否下载完成*/  
  25.     private boolean finish = false;  
  26.     /** 文件下载器 */  
  27.     private FileDownloader downloader;  
  28.     /*** 
  29.      *  构造方法 
  30.      */  
  31.     public DownloadThread(FileDownloader downloader, URL downUrl,  
  32.             File saveFile, int block, int downLength, int threadId) {  
  33.         this.downUrl = downUrl;  
  34.         this.saveFile = saveFile;  
  35.         this.block = block;  
  36.         this.downloader = downloader;  
  37.         this.threadId = threadId;  
  38.         this.downLength = downLength;  
  39.     }  
  40.     /** 
  41.      * 线程主方法 
  42.      */  
  43.     @Override  
  44.     public void run() {  
  45.         if (downLength < block) {// 未下载完成  
  46.             try {  
  47.                 HttpURLConnection http = (HttpURLConnection) downUrl  
  48.                         .openConnection();  
  49.                 http.setConnectTimeout(5 * 1000);  
  50.                 http.setRequestMethod("GET");  
  51.                 http.setRequestProperty(  
  52.                         "Accept",  
  53.                         "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash,"  
  54.                                 + " application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, "  
  55.                                 + "application/x-ms-application, application/vnd.ms-excel,"  
  56.                                 + " application/vnd.ms-powerpoint, application/msword, */*");  
  57.                 http.setRequestProperty("Accept-Language""zh-CN");  
  58.                 http.setRequestProperty("Referer", downUrl.toString());  
  59.                 http.setRequestProperty("Charset""UTF-8");  
  60.                 // 该线程开始下载位置  
  61.                 int startPos = block * (threadId - 1) + downLength;  
  62.                 // 该线程下载结束位置  
  63.                 int endPos = block * threadId - 1;  
  64.                 // 设置获取实体数据的范围  
  65.                 http.setRequestProperty("Range""bytes=" + startPos + "-"  
  66.                         + endPos);  
  67.                 http.setRequestProperty(  
  68.                         "User-Agent",  
  69.                         "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0;"  
  70.                                 + " .NET CLR 1.1.4322; .NET CLR 2.0.50727; "  
  71.                                 + ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");  
  72.                 http.setRequestProperty("Connection""Keep-Alive");  
  73.                 //获取输入流  
  74.                 InputStream inStream = http.getInputStream();  
  75.                 byte[] buffer = new byte[1024];  
  76.                 int offset = 0;  
  77.                 print("Thread " + this.threadId  
  78.                         + " start download from position " + startPos);  
  79.                 /**  
  80.                  * rwd: 打开以便读取和写入,对于 "rw",还要求对文件内容的每一个更新都同步写入到基础存储设备。  
  81.                  * 对于Android移动设备必定要注意同步,不然当移动设备断电的话会丢失数据  
  82.                  */  
  83.                 RandomAccessFile threadfile = new RandomAccessFile(  
  84.                         this.saveFile, "rwd");  
  85.                 //直接移动到文件开始位置下载的  
  86.                 threadfile.seek(startPos);  
  87.                 while (!downloader.getExit()  
  88.                         && (offset = inStream.read(buffer, 01024)) != -1) {  
  89.                     threadfile.write(buffer, 0, offset);//开始写入数据到文件  
  90.                     downLength += offset;   //该线程以及下载的长度增长  
  91.                     downloader.update(this.threadId, downLength);//修改数据库中该线程已经下载的数据长度  
  92.                     downloader.append(offset);//文件下载器已经下载的总长度增长  
  93.                 }  
  94.                 threadfile.close();  
  95.                 inStream.close();  
  96.                 print("Thread " + this.threadId + " download finish");  
  97.                 this.finish = true;  
  98.             } catch (Exception e) {  
  99.                 this.downLength = -1;  
  100.                 print("Thread " + this.threadId + ":" + e);  
  101.             }  
  102.         }  
  103.     }  
  104.   
  105.     private static void print(String msg) {  
  106.         Log.i(TAG, msg);  
  107.     }  
  108.   
  109.     /** 
  110.      * 下载是否完成 
  111.      *  
  112.      * @return 
  113.      */  
  114.     public boolean isFinish() {  
  115.         return finish;  
  116.     }  
  117.   
  118.     /** 
  119.      * 已经下载的内容大小 
  120.      *  
  121.      * @return 若是返回值为-1,表明下载失败 
  122.      */  
  123.     public long getDownLength() {  
  124.         return downLength;  
  125.     }  
  126. }  
  127. </span>  

下载进度监听接口cn.oyp.download.downloader.DownloadProgressListener.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.downloader;  
  2.   
  3. /** 
  4.  * 下载进度监听接口 
  5.  */  
  6. public interface DownloadProgressListener {  
  7.     /** 
  8.      *下载的进度  
  9.      */  
  10.     public void onDownloadSize(int size);  
  11. }  
  12. </span>  

数据库操做类 cn.oyp.download.service.DBOpenHelper.java类

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.service;  
  2.   
  3. import android.content.Context;  
  4. import android.database.sqlite.SQLiteDatabase;  
  5. import android.database.sqlite.SQLiteOpenHelper;  
  6.   
  7. public class DBOpenHelper extends SQLiteOpenHelper {  
  8.     // 数据库文件的文件名  
  9.     private static final String DBNAME = "download.db";  
  10.     // 数据库的版本号  
  11.     private static final int VERSION = 1;  
  12.   
  13.     public DBOpenHelper(Context context) {  
  14.         super(context, DBNAME, null, VERSION);  
  15.     }  
  16.   
  17.     @Override  
  18.     public void onCreate(SQLiteDatabase db) {  
  19.         db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");  
  20.     }  
  21.   
  22.     @Override  
  23.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  24.         db.execSQL("DROP TABLE IF EXISTS filedownlog");  
  25.         onCreate(db);  
  26.     }  
  27. }  
  28. </span>  

文件下载服务类cn.oyp.download.service.FileService

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.service;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. import android.content.Context;  
  7. import android.database.Cursor;  
  8. import android.database.sqlite.SQLiteDatabase;  
  9.   
  10. /** 
  11.  * 文件下载服务类 
  12.  */  
  13. public class FileService {  
  14.     private DBOpenHelper openHelper;  
  15.   
  16.     public FileService(Context context) {  
  17.         openHelper = new DBOpenHelper(context);  
  18.     }  
  19.   
  20.     /** 
  21.      * 获取每条线程已经下载的文件长度 
  22.      *  
  23.      * @param path 
  24.      * @return 
  25.      */  
  26.     public Map<Integer, Integer> getData(String path) {  
  27.         SQLiteDatabase db = openHelper.getReadableDatabase();  
  28.         Cursor cursor = db  
  29.                 .rawQuery(  
  30.                         "select threadid, downlength from filedownlog where downpath=?",  
  31.                         new String[] { path });  
  32.         Map<Integer, Integer> data = new HashMap<Integer, Integer>();  
  33.         while (cursor.moveToNext()) {  
  34.             data.put(cursor.getInt(0), cursor.getInt(1));  
  35.         }  
  36.         cursor.close();  
  37.         db.close();  
  38.         return data;  
  39.     }  
  40.   
  41.     /** 
  42.      * 保存每条线程已经下载的文件长度 
  43.      *  
  44.      * @param path 
  45.      * @param map 
  46.      */  
  47.     public void save(String path, Map<Integer, Integer> map) {// int threadid,  
  48.                                                                 // int position  
  49.         SQLiteDatabase db = openHelper.getWritableDatabase();  
  50.         db.beginTransaction();  
  51.         try {  
  52.             for (Map.Entry<Integer, Integer> entry : map.entrySet()) {  
  53.                 db.execSQL(  
  54.                         "insert into filedownlog(downpath, threadid, downlength) values(?,?,?)",  
  55.                         new Object[] { path, entry.getKey(), entry.getValue() });  
  56.             }  
  57.             db.setTransactionSuccessful();  
  58.         } finally {  
  59.             db.endTransaction();  
  60.         }  
  61.         db.close();  
  62.     }  
  63.   
  64.     /** 
  65.      * 实时更新每条线程已经下载的文件长度 
  66.      *  
  67.      * @param path 
  68.      * @param map 
  69.      */  
  70.     public void update(String path, int threadId, int pos) {  
  71.         SQLiteDatabase db = openHelper.getWritableDatabase();  
  72.         db.execSQL(  
  73.                 "update filedownlog set downlength=? where downpath=? and threadid=?",  
  74.                 new Object[] { pos, path, threadId });  
  75.         db.close();  
  76.     }  
  77.   
  78.     /** 
  79.      * 当文件下载完成后,删除对应的下载记录 
  80.      *  
  81.      * @param path 
  82.      */  
  83.     public void delete(String path) {  
  84.         SQLiteDatabase db = openHelper.getWritableDatabase();  
  85.         db.execSQL("delete from filedownlog where downpath=?",  
  86.                 new Object[] { path });  
  87.         db.close();  
  88.     }  
  89. }  
  90. </span>  

step4:AndroidManifest.xml

[html]  view plain copy
  1. <span style="font-size:18px"><?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="cn.oyp.download"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="8"  
  9.         android:targetSdkVersion="17" />  
  10.     <!-- 访问Internet权限 -->  
  11.     <uses-permission android:name="android.permission.INTERNET" />  
  12.     <!-- 在SDCard中建立与删除文件权限 -->  
  13.     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />  
  14.     <!-- 往SDCard写入数据权限 -->  
  15.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
  16.   
  17.     <application  
  18.         android:allowBackup="true"  
  19.         android:icon="@drawable/icon"  
  20.         android:label="@string/app_name"  
  21.         android:theme="@style/AppTheme" >  
  22.         <activity  
  23.             android:name="cn.oyp.download.MainActivity"  
  24.             android:label="@string/app_name" >  
  25.             <intent-filter>  
  26.                 <action android:name="android.intent.action.MAIN" />  
  27.   
  28.                 <category android:name="android.intent.category.LAUNCHER" />  
  29.             </intent-filter>  
  30.         </activity>  
  31.     </application>  
  32.   
  33. </manifest></span>  


step5:因为便于本项目的展现,因此新建一个JSP项目,部署到Tomcat服务器上,以供下载。


step6:部署应用,观看运行效果

一、打开应用


二、点击“开始下载”


3.点击“中止下载”


4.点击“开始下载”   会继续上一次的下载进度继续下载


5.退出应用,再进应用


六、点击“开始下载”,会继续上一次退出应用的时候的下载进度继续下载,完成断点下载



7.当下载完成的时候



==================================================================================================

  做者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================




读者下载源码后,会发现下载速度特别慢,有如下两种缘由:

一、因为自己的网络速度的缘由,不会特别快。

二、因为使用RandomAccessFile的缘由,对IO操做太过于频繁。所以,我修改了DownloadThread类,修改代码以下,修改以后对速度有了点提高。在此特别感谢 pobi 读者的意见

[java]  view plain copy
  1. package cn.oyp.download.downloader;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStream;  
  8. import java.io.RandomAccessFile;  
  9. import java.net.HttpURLConnection;  
  10. import java.net.URL;  
  11. import java.nio.ByteBuffer;  
  12. import java.nio.channels.FileChannel;  
  13.   
  14. import android.util.Log;  
  15.   
  16. public class DownloadThread extends Thread {  
  17.     private static final String TAG = "DownloadThread";  
  18.     /** 本地保存文件 */  
  19.     private File saveFile;  
  20.     /** 下载路径 */  
  21.     private URL downUrl;  
  22.     /** 该线程要下载的长度 */  
  23.     private int block;  
  24.     /** 线程ID */  
  25.     private int threadId = -1;  
  26.     /** 该线程已经下载的长度 */  
  27.     private int downLength;  
  28.     /** 是否下载完成 */  
  29.     private boolean finish = false;  
  30.     /** 文件下载器 */  
  31.     private FileDownloader downloader;  
  32.   
  33.     /*** 
  34.      * 构造方法 
  35.      */  
  36.     public DownloadThread(FileDownloader downloader, URL downUrl,  
  37.             File saveFile, int block, int downLength, int threadId) {  
  38.         this.downUrl = downUrl;  
  39.         this.saveFile = saveFile;  
  40.         this.block = block;  
  41.         this.downloader = downloader;  
  42.         this.threadId = threadId;  
  43.         this.downLength = downLength;  
  44.     }  
  45.   
  46.     /** 
  47.      * 线程主方法 
  48.      */  
  49.     @Override  
  50.     public void run() {  
  51.         if (downLength < block) {// 未下载完成  
  52.             try {  
  53.                 HttpURLConnection http = (HttpURLConnection) downUrl  
  54.                         .openConnection();  
  55.                 http.setConnectTimeout(5 * 1000);  
  56.                 http.setRequestMethod("GET");  
  57.                 http.setRequestProperty(  
  58.                         "Accept",  
  59.                         "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash,"  
  60.                                 + " application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, "  
  61.                                 + "application/x-ms-application, application/vnd.ms-excel,"  
  62.                                 + " application/vnd.ms-powerpoint, application/msword, */*");  
  63.                 http.setRequestProperty("Accept-Language""zh-CN");  
  64.                 http.setRequestProperty("Referer", downUrl.toString());  
  65.                 http.setRequestProperty("Charset""UTF-8");  
  66.                 // 该线程开始下载位置  
  67.                 int startPos = block * (threadId - 1) + downLength;  
  68.                 // 该线程下载结束位置  
  69.                 int endPos = block * threadId - 1;  
  70.                 // 设置获取实体数据的范围  
  71.                 http.setRequestProperty("Range""bytes=" + startPos + "-"  
  72.                         + endPos);  
  73.                 http.setRequestProperty(  
  74.                         "User-Agent",  
  75.                         "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0;"  
  76.                                 + " .NET CLR 1.1.4322; .NET CLR 2.0.50727; "  
  77.                                 + ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");  
  78.                 http.setRequestProperty("Connection""Keep-Alive");  
  79.                 /****/  
  80.                 System.out.println("DownloadThread http.getResponseCode():"  
  81.                         + http.getResponseCode());  
  82.                 if (http.getResponseCode() == 206) {  
  83.                     /*** 
  84.                      * //获取输入流 InputStream inStream = http.getInputStream(); 
  85.                      * byte[] buffer = new byte[1024]; int offset = 0; 
  86.                      * print("Thread " + this.threadId + 
  87.                      * " start download from position " + startPos); 
  88.                      *  
  89.                      * // rwd: 打开以便读取和写入,对于 "rw",还要求对文件内容的每一个更新都同步写入到基础存储设备。 
  90.                      * //对于Android移动设备必定要注意同步,不然当移动设备断电的话会丢失数据 RandomAccessFile 
  91.                      * threadfile = new RandomAccessFile( this.saveFile, "rwd"); 
  92.                      * //直接移动到文件开始位置下载的 threadfile.seek(startPos); while 
  93.                      * (!downloader.getExit() && (offset = inStream.read(buffer, 
  94.                      * 0, 1024)) != -1) { threadfile.write(buffer, 0, 
  95.                      * offset);//开始写入数据到文件 downLength += offset; //该线程以及下载的长度增长 
  96.                      * downloader.update(this.threadId, 
  97.                      * downLength);//修改数据库中该线程已经下载的数据长度 
  98.                      * downloader.append(offset);//文件下载器已经下载的总长度增长 } 
  99.                      * threadfile.close(); 
  100.                      *  
  101.                      * print("Thread " + this.threadId + " download finish"); 
  102.                      * this.finish = true; 
  103.                      **/  
  104.                     // 获取输入流  
  105.                     InputStream inStream = http.getInputStream();  
  106.                     BufferedInputStream bis = new BufferedInputStream(inStream);  
  107.                     byte[] buffer = new byte[1024 * 4];  
  108.                     int offset = 0;  
  109.                     RandomAccessFile threadfile = new RandomAccessFile(  
  110.                             this.saveFile, "rwd");  
  111.                     // 获取RandomAccessFile的FileChannel  
  112.                     FileChannel outFileChannel = threadfile.getChannel();  
  113.                     // 直接移动到文件开始位置下载的  
  114.                     outFileChannel.position(startPos);  
  115.                     // 分配缓冲区的大小  
  116.                     while (!downloader.getExit()  
  117.                             && (offset = bis.read(buffer)) != -1) {  
  118.                         outFileChannel  
  119.                                 .write(ByteBuffer.wrap(buffer, 0, offset));// 开始写入数据到文件  
  120.                         downLength += offset; // 该线程以及下载的长度增长  
  121.                         downloader.update(this.threadId, downLength);// 修改数据库中该线程已经下载的数据长度  
  122.                         downloader.append(offset);// 文件下载器已经下载的总长度增长  
  123.                     }  
  124.                     outFileChannel.close();  
  125.                     threadfile.close();  
  126.                     inStream.close();  
  127.                     print("Thread " + this.threadId + " download finish");  
  128.                     this.finish = true;  
  129.                 }  
  130.             } catch (Exception e) {  
  131.                 this.downLength = -1;  
  132.                 print("Thread " + this.threadId + ":" + e);  
  133.             }  
  134.         }  
  135.     }  
  136.   
  137.     private static void print(String msg) {  
  138.         Log.i(TAG, msg);  
  139.     }  
  140.   
  141.     /** 
  142.      * 下载是否完成 
  143.      *  
  144.      * @return 
  145.      */  
  146.     public boolean isFinish() {  
  147.         return finish;  
  148.     }  
  149.   
  150.     /** 
  151.      * 已经下载的内容大小 
  152.      *  
  153.      * @return 若是返回值为-1,表明下载失败 
  154.      */  
  155.     public long getDownLength() {  
  156.         return downLength;  
  157.     }  
  158. }  

   ==================================下面看一个gif动画===========================================

    


能够查看log日志,查看多线程下载的状况

==================================================================================================

  做者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================


    博客写完后,又有读者提出了修改意见,在这儿特别感谢热心的读者给的建议,下面是读者JavaLover00000 给的建议:

修改了部分代码后,具体的优化效果以下所示,修改后下载速度确实变快了不少。

修改前的效果:

修改后的效果

具体修改的代码能够到如下地址进行下载:Android基于HTTP协议的多线程断点下载器的实现源码_第二次优化以后

主要是将一、buffer改成8k  
二、由于发现花费在更新数据库的时间比 read和write加起来的时间都要多一点,因此将更新数据库进度改成下载线程出现异常的时候更新单个线程进度和FileDownloader中的exit()中更新全部线程进度


代码修改的地方具体能够查看源代码中的FileDownloader.java和DownloadThread.java



3.HttpURLConnection实现断点续传



HttpURLConnection继承了URLConnection,所以也可用于向指定网站发送GET请求、POST请求,并且它在URLConnection基础上提供了以下便捷方法:

实现多线程下载的步骤:


下边的总结对我帮助蛮大的~不只用法了解,整个链接流程也要明白!

原文连接地址: 
http://www.blogjava.net/supercrsky/articles/247449.html 

针对JDK中的URLConnection链接Servlet的问题,网上有虽然有所涉及,可是只是说明了某一个或几个问题,是以FAQ的方式来解决的,并且比较零散,如今对这个类的使用就本人在项目中的使用经验作以下总结: 
1:> URL请求的类别: 
分为二类,GET与POST请求。两者的区别在于: 
     a:) get请求能够获取静态页面,也能够把参数放在URL字串后面,传递给servlet, 
     b:) post与get的不一样之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。 


2:> URLConnection的对象问题: 
URLConnection的对象,以下代码示例: 

// 下面的index.jsp由<servlet-mapping>映射到 
// 一个Servlet(com.quantanetwork.getClientDataServlet) 
// 该Servlet的注意点下边会提到 

复制代码
  
  
  
  
1 URL url = new URL( " http://localhost:8080/TestHttpURLConnectionPro/index.jsp " );
2
3 URLConnection rulConnection = url.openConnection();
4 // 此处的urlConnection对象其实是根据URL的
5 // 请求协议(此处是http)生成的URLConnection类
6 // 的子类HttpURLConnection,故此处最好将其转化
7 // 为HttpURLConnection类型的对象,以便用到
8 // HttpURLConnection更多的API.以下:
9  
10 HttpURLConnection httpUrlConnection = (HttpURLConnection) rulConnection;
复制代码

3:> HttpURLConnection对象参数问题 

复制代码
  
  
  
  
1 // 设置是否向httpUrlConnection输出,由于这个是post请求,参数要放在
2   // http正文内,所以须要设为true, 默认状况下是false;
3   httpUrlConnection.setDoOutput( true );
4
5   // 设置是否从httpUrlConnection读入,默认状况下是true;
6   httpUrlConnection.setDoInput( true );
7
8   // Post 请求不能使用缓存
9   httpUrlConnection.setUseCaches( false );
10
11   // 设定传送的内容类型是可序列化的java对象
12   // (若是不设此项,在传送序列化对象时,当WEB服务默认的不是这种类型时可能抛java.io.EOFException)
13   httpUrlConnection.setRequestProperty( " Content-type " , " application/x-java-serialized-object " );
14
15   // 设定请求的方法为"POST",默认是GET
16   httpUrlConnection.setRequestMethod( " POST " );
17
18   // 链接,从上述第2条中url.openConnection()至此的配置必需要在connect以前完成,
19   httpUrlConnection.connect();
复制代码

4:>  HttpURLConnection链接问题: 

  
  
  
  
1 // 此处getOutputStream会隐含的进行connect(即:如同调用上面的connect()方法,
2   // 因此在开发中不调用上述的connect()也能够)。
3   OutputStream outStrm = httpUrlConnection.getOutputStream();

5:> HttpURLConnection写数据与发送数据问题: 

复制代码
  
  
  
  
1 // 如今经过输出流对象构建对象输出流对象,以实现输出可序列化的对象。
2   ObjectOutputStream objOutputStrm = new ObjectOutputStream(outStrm);
3
4 // 向对象输出流写出数据,这些数据将存到内存缓冲区中
5   objOutputStrm.writeObject( new String( " 我是测试数据 " ));
6
7 // 刷新对象输出流,将任何字节都写入潜在的流中(些处为ObjectOutputStream)
8   objOutputStm.flush();
9
10   // 关闭流对象。此时,不能再向对象输出流写入任何数据,先前写入的数据存在于内存缓冲区中,
11   // 在调用下边的getInputStream()函数时才把准备好的http请求正式发送到服务器
12   objOutputStm.close();
13  
14   // 调用HttpURLConnection链接对象的getInputStream()函数,
15   // 将内存缓冲区中封装好的完整的HTTP请求电文发送到服务端。
16   InputStream inStrm = httpConn.getInputStream(); // <===注意,实际发送请求的代码段就在这里
17  
18 // 上边的httpConn.getInputStream()方法已调用,本次HTTP请求已结束,下边向对象输出流的输出已无心义,
19 // 既使对象输出流没有调用close()方法,下边的操做也不会向对象输出流写入任何数据.
20 // 所以,要从新发送数据时须要从新建立链接、从新设参数、从新建立流对象、从新写数据、
21 // 从新发送数据(至因而否不用从新这些操做须要再研究)
22 objOutputStm.writeObject( new String( "" ));
23 httpConn.getInputStream()
复制代码

总结:a:) HttpURLConnection的connect()函数,实际上只是创建了一个与服务器的tcp链接,并无实际发送http请求。 

    不管是post仍是get,http请求实际上直到HttpURLConnection的getInputStream()这个函数里面才正式发送出去。 
       b:) 在用POST方式发送URL请求时,URL请求参数的设定顺序是重中之重, 
    对connection对象的一切配置(那一堆set函数) 
    都必需要在connect()函数执行以前完成。而对outputStream的写操做,又必需要在inputStream的读操做以前。 
    这些顺序其实是由http请求的格式决定的。 
    若是inputStream读操做在outputStream的写操做以前,会抛出例外: 
    java.net.ProtocolException: Cannot write output after reading input....... 
       
       c:) http请求实际上由两部分组成, 
    一个是http头,全部关于这次http请求的配置都在http头里面定义, 
           一个是正文content。 
    connect()函数会根据HttpURLConnection对象的配置值生成http头部信息,所以在调用connect函数以前, 
    就必须把全部的配置准备好。 
       d:) 在http头后面紧跟着的是http请求的正文,正文的内容是经过outputStream流写入的, 
    实际上outputStream不是一个网络流,充其量是个字符串流,往里面写入的东西不会当即发送到网络, 
    而是存在于内存缓冲区中,待outputStream流关闭时,根据输入的内容生成http正文。 
    至此,http请求的东西已经所有准备就绪。在getInputStream()函数调用的时候,就会把准备好的http请求 
    正式发送到服务器了,而后返回一个输入流,用于读取服务器对于这次http请求的返回信息。因为http 
    请求在getInputStream的时候已经发送出去了(包括http头和正文),所以在getInputStream()函数 
    以后对connection对象进行设置(对http头的信息进行修改)或者写入outputStream(对正文进行修改) 
    都是没有意义的了,执行这些操做会致使异常的发生。 

6:> Servlet端的开发注意点: 
a:) 对于客户端发送的POST类型的HTTP请求,Servlet必须实现doPost方法,而不能用doGet方法。 
b:) 用HttpServletRequest的getInputStream()方法取得InputStream的对象,好比: 
     InputStream inStream = httpRequest.getInputStream(); 
     如今调用inStream.available()(该方法用于“返回此输入流下一个方法调用能够不受阻塞地 
     今后输入流读取(或跳过)的估计字节数”)时,永远都反回0。试图使用此方法的返回值分配缓冲区, 
     以保存此流全部数据的作法是不正确的。那么,如今的解决办法是 
     Servlet这一端用以下实现: 
     InputStream inStream = httpRequest.getInputStream(); 
     ObjectInputStream objInStream = new ObjectInputStream(inStream); 
     Object obj = objInStream.readObject(); 
     // 作后续的处理 
     // 。。。。。。 
     // 。。。 。。。 
     而客户端,不管是否发送实际数据都要写入一个对象(那怕这个对象不用),如: 
     ObjectOutputStream objOutputStrm = new ObjectOutputStream(outStrm); 
     objOutputStrm.writeObject(new String("")); // 这里发送一个空数据 
     // 甚至能够发一个null对象,服务端取到后再作判断处理。 
     objOutputStrm.writeObject(null); 
     objOutputStrm.flush(); 
     objOutputStrm.close(); 

注意:上述在建立对象输出流ObjectOutputStream时,若是将从HttpServletRequest取得的输入流 
      (即:new ObjectOutputStream(outStrm)中的outStrm)包装在BufferedOutputStream流里面, 
      则必须有objOutputStrm.flush();这一句,以便将流信息刷入缓冲输出流.以下: 
      ObjectOutputStream objOutputStrm = new ObjectOutputStream(new BufferedOutputStream(outStrm)); 
      objOutputStrm.writeObject(null); 
      objOutputStrm.flush(); // <======此处必需要有. 
      objOutputStrm.close(); 

HttpURLConnection是基于HTTP协议的,其底层经过socket通讯实现。若是不设置超时(timeout),在网络异常的状况下,可能会致使程序僵死而不继续往下执行。能够经过如下两个语句来设置相应的超时: 
System.setProperty("sun.net.client.defaultConnectTimeout", 超时毫秒数字符串); 
System.setProperty("sun.net.client.defaultReadTimeout", 超时毫秒数字符串); 


其中: sun.net.client.defaultConnectTimeout:链接主机的超时时间(单位:毫秒) 
sun.net.client.defaultReadTimeout:从主机读取数据的超时时间(单位:毫秒) 

例如: 
System.setProperty("sun.net.client.defaultConnectTimeout", "30000"); 
System.setProperty("sun.net.client.defaultReadTime 

Java中可使用HttpURLConnection来请求WEB资源。 
HttpURLConnection对象不能直接构造,须要经过URL.openConnection()来得到HttpURLConnection对象,示例代码以下: 

  
  
  
  
1 String szUrl = " http://www.ee2ee.com/ " ;
2 URL url = new URL(szUrl);
3 HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();

HttpURLConnection是基于HTTP协议的,其底层经过socket通讯实现。若是不设置超时(timeout),在网络异常的状况下,可能会致使程序僵死而不继续往下执行。能够经过如下两个语句来设置相应的超时: 
System.setProperty("sun.net.client.defaultConnectTimeout", 超时毫秒数字符串); 
System.setProperty("sun.net.client.defaultReadTimeout", 超时毫秒数字符串); 


其中: sun.net.client.defaultConnectTimeout:链接主机的超时时间(单位:毫秒) 
sun.net.client.defaultReadTimeout:从主机读取数据的超时时间(单位:毫秒) 

例如: 
System.setProperty("sun.net.client.defaultConnectTimeout", "30000"); 
System.setProperty("sun.net.client.defaultReadTimeout", "30000"); 

JDK 1.5之前的版本,只能经过设置这两个系统属性来控制网络超时。在1.5中,还可使用HttpURLConnection的父类URLConnection的如下两个方法: 
setConnectTimeout:设置链接主机超时(单位:毫秒) 
setReadTimeout:设置从主机读取数据超时(单位:毫秒) 

例如: 
  
  
  
  
1 HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();
2 urlCon.setConnectTimeout( 30000 );
3 urlCon.setReadTimeout( 30000 );
  


须要注意的是,笔者在JDK1.4.2环境下,发如今设置了defaultReadTimeout的状况下,若是发生网络超时,HttpURLConnection会自动从新提交一次请求,出现一次请求调用,请求服务器两次的问题(Trouble)。我认为这是JDK1.4.2的一个bug。在JDK1.5.0中,此问题已获得解决,不存在自动重发现象。out", "3000





下面用一个示例来示范使用HttpURLConnection实现多线程下载。此代码来源疯狂讲义一书,该代码主要思路:在Activity中点击按钮,调用DownUtil的download()方法,在download()中启动四个线程去下载资源,每一个线程负责下载本身的那部分资源,代码以下:

Activity:

[java]  view plain copy
  1. package com.home.activity;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.view.View;  
  11. import android.view.View.OnClickListener;  
  12. import android.widget.Button;  
  13. import android.widget.EditText;  
  14. import android.widget.ProgressBar;  
  15.   
  16. import com.home.multithreaddown.R;  
  17. import com.home.util.DownUtil;  
  18.   
  19. public class MultiThreadDownActivity extends Activity {  
  20.     private EditText urlText;  
  21.     private EditText targetText;  
  22.     private Button downBtn;  
  23.     private ProgressBar bar;  
  24.     private DownUtil downUtil;  
  25.     private int mDownStatus;  
  26.     private Handler handler;  
  27.   
  28.     @Override  
  29.     protected void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.main);  
  32.         // 获取界面中控件  
  33.         targetText = (EditText) findViewById(R.id.main_et_name);  
  34.         urlText = (EditText) findViewById(R.id.main_et_url);  
  35.         downBtn = (Button) findViewById(R.id.main_btn_download);  
  36.         bar = (ProgressBar) findViewById(R.id.main_progressBar);  
  37.         // 建立一个Handler对象  
  38.         handler = new Handler() {  
  39.             public void handleMessage(Message msg) {  
  40.                 if (msg.what == 0x123) {  
  41.                     bar.setProgress(mDownStatus);  
  42.                 }  
  43.             }  
  44.         };  
  45.         downBtn.setOnClickListener(new OnClickListener() {  
  46.   
  47.             @Override  
  48.             public void onClick(View v) {  
  49.                 // 初始化DownUtil对象  
  50.                 downUtil = new DownUtil(urlText.getText().toString(),  
  51.                         targetText.getText().toString(), 4);  
  52.                 try {  
  53.                     // 开始下载  
  54.                     downUtil.download();  
  55.                 } catch (Exception e) {  
  56.                     e.printStackTrace();  
  57.                 }  
  58.                 // 定义每秒调度获取一次系统的完成进度  
  59.                 final Timer timer = new Timer();  
  60.                 timer.schedule(new TimerTask() {  
  61.                     public void run() {  
  62.                         // 获取下载任务的完成比率  
  63.                         double completeRate = downUtil.getCompleteRate();  
  64.                         mDownStatus = (int) (completeRate * 100);  
  65.                         // 发送消息通知界面更新进度条  
  66.                         handler.sendEmptyMessage(0x123);  
  67.                         // 下载完成后取消任务调度  
  68.                         if (mDownStatus >= 100) {  
  69.                             timer.cancel();  
  70.                         }  
  71.                     }  
  72.                 }, 0100);  
  73.   
  74.             }  
  75.         });  
  76.     }  
  77.   
  78. }  

下载的工具类(DownUtil):

[java]  view plain copy
  1. package com.home.util;  
  2.   
  3. import java.io.InputStream;  
  4. import java.io.RandomAccessFile;  
  5. import java.net.HttpURLConnection;  
  6. import java.net.URL;  
  7.   
  8. public class DownUtil {  
  9.     // 定义下载资源的路径  
  10.     private String path;  
  11.     // 指定所下载的文件的保存位置  
  12.     private String targetFile;  
  13.     // 定义须要使用多少线程下载资源  
  14.     private int threadNum;  
  15.     // 定义下载的文件的总大小  
  16.     private int fileSize;  
  17.     // 定义下载的线程对象  
  18.     private DownloadThread[] threads;  
  19.   
  20.     public DownUtil(String path, String targetFile, int threadNum) {  
  21.         this.path = path;  
  22.         this.threadNum = threadNum;  
  23.         // 初始化threads数组  
  24.         threads = new DownloadThread[threadNum];  
  25.         this.targetFile = targetFile;  
  26.     }  
  27.   
  28.     public void download() throws Exception {  
  29.         URL url = new URL(path);  
  30.         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  31.         conn.setConnectTimeout(5 * 1000);  
  32.         conn.setRequestMethod("GET");  
  33.         conn.setRequestProperty(  
  34.                 "Accept",  
  35.                 "image/gif,image/jpeg,image/pjpeg,application/x-shockwaveflash,application/x-ms-xbap,application/xaml+xml,application/vnd.ms-xpsdocument,application/x-ms-application,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/*");  
  36.         conn.setRequestProperty("Accept-Language""zh-CN");  
  37.         conn.setRequestProperty("Charset""UTF-8");  
  38.         conn.setRequestProperty(  
  39.                 "User-Agent",  
  40.                 "Mozilla/4.0(compatible;MSIE7.0;Windows NT 5.2;Trident/4.0;.NET CLR 1.1.4322;.NET CLR 2.0.50727;.NET CLR 3.0.04506.30;.NET CLR 3.0.4506.2152;.NET CLR 3.5.30729)");  
  41.   
  42.         conn.setRequestProperty("Connection""Keep-Alive");  
  43.         // 获得文件大小  
  44.         fileSize = conn.getContentLength();  
  45.         conn.disconnect();  
  46.         int currentPartSize = fileSize / threadNum + 1;  
  47.         RandomAccessFile file = new RandomAccessFile(targetFile, "rw");  
  48.         // 设置本地文件的大小  
  49.         file.setLength(fileSize);  
  50.         file.close();  
  51.         for (int i = 0; i < threadNum; i++) {  
  52.             // 计算每条线程的下载的开始位置  
  53.             int startPos = i * currentPartSize;  
  54.             // 每一个线程使用一个RandomAccessFile进行下载  
  55.             RandomAccessFile currentPart = new RandomAccessFile(targetFile,  
  56.                     "rw");  
  57.             // 定位该线程的下载位置  
  58.             currentPart.seek(startPos);  
  59.             // 建立下载线程  
  60.             threads[i] = new DownloadThread(startPos, currentPartSize,  
  61.                     currentPart);  
  62.             // 启动下载线程  
  63.             threads[i].start();  
  64.         }  
  65.     }  
  66.   
  67.     /**  
  68.      * 获取下载完成的百分比  
  69.      *   
  70.      * @return  
  71.      */  
  72.     public double getCompleteRate() {  
  73.         // 统计多条线程已经下载的总大小  
  74.         int sumSize = 0;  
  75.         for (int i = 0; i < threadNum; i++) {  
  76.             sumSize += threads[i].length;  
  77.         }  
  78.         // 返回已经完成的百分比  
  79.         return sumSize * 1.0 / fileSize;  
  80.     }  
  81.   
  82.     private class DownloadThread extends Thread {  
  83.         // 当前线程的下载位置  
  84.         private int startPos;  
  85.         // 定义当前线程负责下载的文件大小  
  86.         private int currentPartSize;  
  87.         // 当前线程须要下载的文件块  
  88.         private RandomAccessFile currentPart;  
  89.         // 定义该线程已下载的字节数  
  90.         private int length = 0;  
  91.   
  92.         public DownloadThread(int startPos, int currentPartSize,  
  93.                 RandomAccessFile currentPart) {  
  94.             this.startPos = startPos;  
  95.             this.currentPartSize = currentPartSize;  
  96.             this.currentPart = currentPart;  
  97.         }  
  98.   
  99.         public void run() {  
  100.             try {  
  101.                 URL url = 
相关文章
相关标签/搜索