Android 多任务多线程断点下载

移动开发 Android
很多时候我们需要在Android设备上下载远程服务器上的文件进安装,前两天晚上我看到一个视频,从网上搜了一下资料,大概理解一下.直接通过Android提供的Http类访问远程服务器,这里AndroidHttpClient是SDK 2.2中新出的方法,

让我们看一下代码的实现方法。

  1. package com.smart.db;   
  2. import java.util.HashMap;   
  3. import java.util.Map;   
  4. import android.content.Context;   
  5. import android.database.Cursor;   
  6. import android.database.sqlite.SQLiteDatabase;   
  7. /**   
  8. * 业务bean   
  9. *   
  10. */   
  11. public class FileService {   
  12. private DBOpenHelper openHelper;   
  13. public FileService(Context context) {   
  14.   openHelper = new DBOpenHelper(context);   
  15. }   
  16. /**   
  17.   * 获取每条线程已经下载的文件长度   
  18.   * @param path   
  19.   * @return   
  20.   */   
  21. public Map<Integer, Integer> getData(String path){   
  22.   SQLiteDatabase db = openHelper.getReadableDatabase();   
  23.   Cursor cursor = db.rawQuery("select threadid, downlength from SmartFileDownlog where downpath=?", new String[]{path});   
  24.   Map<Integer, Integer> data = new HashMap<Integer, Integer>();   
  25.   while(cursor.moveToNext()){   
  26.    data.put(cursor.getInt(0), cursor.getInt(1));   
  27.   }   
  28.   cursor.close();   
  29.   db.close();   
  30.   return data;   
  31. }   
  32. /**   
  33.   * 保存每条线程已经下载的文件长度   
  34.   * @param path   
  35.   * @param map   
  36.   */   
  37. public void save(String path,  Map<Integer, Integer> map){//int threadid, int position   
  38.   SQLiteDatabase db = openHelper.getWritableDatabase();   
  39.   db.beginTransaction();   
  40.   try{   
  41.    for(Map.Entry<Integer, Integer> entry : map.entrySet()){   
  42.     db.execSQL("insert into SmartFileDownlog(downpath, threadid, downlength) values(?,?,?)",   
  43.       new Object[]{path, entry.getKey(), entry.getValue()});   
  44.    }   
  45.    db.setTransactionSuccessful();   
  46.   }finally{   
  47.    db.endTransaction();   
  48.   }   
  49.   db.close();   
  50. }   
  51. /**   
  52.   * 实时更新每条线程已经下载的文件长度   
  53.   * @param path   
  54.   * @param map   
  55.   */   
  56. public void update(String path, Map<Integer, Integer> map){   
  57.   SQLiteDatabase db = openHelper.getWritableDatabase();   
  58.   db.beginTransaction();   
  59.   try{   
  60.    for(Map.Entry<Integer, Integer> entry : map.entrySet()){   
  61.     db.execSQL("update SmartFileDownlog set downlength=? where downpath=? and threadid=?",   
  62.       new Object[]{entry.getValue(), path, entry.getKey()});   
  63.    }   
  64.    db.setTransactionSuccessful();   
  65.   }finally{   
  66.    db.endTransaction();   
  67.   }   
  68.   db.close();   
  69. }   
  70. /**   
  71.   * 当文件下载完成后,删除对应的下载记录   
  72.   * @param path   
  73.   */   
  74. public void delete(String path){   
  75.   SQLiteDatabase db = openHelper.getWritableDatabase();   
  76.   db.execSQL("delete from SmartFileDownlog where downpath=?", new Object[]{path});   
  77.   db.close();   
  78. }   
  79.    
  80. }   
  81. package com.smart.impl;   
  82. import java.io.File;   
  83. import java.io.RandomAccessFile;   
  84. import java.net.HttpURLConnection;   
  85. import java.net.URL;   
  86. import java.util.LinkedHashMap;   
  87. import java.util.Map;   
  88. import java.util.UUID;   
  89. import java.util.concurrent.ConcurrentHashMap;   
  90. import java.util.regex.Matcher;   
  91. import java.util.regex.Pattern;   
  92. import android.content.Context;   
  93. import android.util.Log;   
  94. import com.smart.db.FileService;   
  95. /**   
  96. * 文件下载器   
  97. * @author lihuoming@sohu.com   
  98. */   
  99. public class SmartFileDownloader {   
  100. private static final String TAG = "SmartFileDownloader";   
  101. private Context context;   
  102. private FileService fileService;   
  103. /* 已下载文件长度 */   
  104. private int downloadSize = 0;   
  105. /* 原始文件长度 */   
  106. private int fileSize = 0;   
  107. /* 线程数 */   
  108. private SmartDownloadThread[] threads;   
  109. /* 本地保存文件 */   
  110. private File saveFile;   
  111. /* 缓存各线程下载的长度*/   
  112. private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();   
  113. /* 每条线程下载的长度 */   
  114. private int block;   
  115. /* 下载路径  */   
  116. private String downloadUrl;   
  117. /**   
  118.   * 获取线程数   
  119.   */   
  120. public int getThreadSize() {   
  121.   return threads.length;   
  122. }   
  123. /**   
  124.   * 获取文件大小   
  125.   * @return   
  126.   */   
  127. public int getFileSize() {   
  128.   return fileSize;   
  129. }   
  130. /**   
  131.   * 累计已下载大小   
  132.   * @param size   
  133.   */   
  134. protected synchronized void append(int size) {   
  135.   downloadSize += size;   
  136. }   
  137. /**   
  138.   * 更新指定线程最后下载的位置   
  139.   * @param threadId 线程id   
  140.   * @param pos 最后下载的位置   
  141.   */   
  142. protected void update(int threadId, int pos) {   
  143.   this.data.put(threadId, pos);   
  144. }   
  145. /**   
  146.   * 保存记录文件   
  147.   */   
  148. protected synchronized void saveLogFile() {   
  149.   this.fileService.update(this.downloadUrl, this.data);   
  150. }   
  151. /**   
  152.   * 构建文件下载器   
  153.   * @param downloadUrl 下载路径   
  154.   * @param fileSaveDir 文件保存目录   
  155.   * @param threadNum 下载线程数   
  156.   */   
  157. public SmartFileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {   
  158.   try {   
  159.    this.context = context;   
  160.    this.downloadUrl = downloadUrl;   
  161.    fileService = new FileService(this.context);   
  162.    URL url = new URL(this.downloadUrl);   
  163.    if(!fileSaveDir.exists()) fileSaveDir.mkdirs();   
  164.    this.threads = new SmartDownloadThread[threadNum];        
  165.    HttpURLConnection conn = (HttpURLConnection) url.openConnection();   
  166.    conn.setConnectTimeout(5*1000);   
  167.    conn.setRequestMethod("GET");   
  168.    conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");   
  169.    conn.setRequestProperty("Accept-Language", "zh-CN");   
  170.    conn.setRequestProperty("Referer", downloadUrl);   
  171.    conn.setRequestProperty("Charset", "UTF-8");   
  172.    conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.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)");   
  173.    conn.setRequestProperty("Connection", "Keep-Alive");   
  174.    conn.connect();   
  175.    printResponseHeader(conn);   
  176.    if (conn.getResponseCode()==200) {   
  177.     this.fileSize = conn.getContentLength();//根据响应获取文件大小   
  178.     if (this.fileSize <= 0) throw new RuntimeException("Unkown file size ");         
  179.     String filename = getFileName(conn);   
  180.     this.saveFile = new File(fileSaveDir, filename);/* 保存文件 */   
  181.     Map<Integer, Integer> logdata = fileService.getData(downloadUrl);   
  182.     if(logdata.size()>0){   
  183.      for(Map.Entry<Integer, Integer> entry : logdata.entrySet())   
  184.       data.put(entry.getKey(), entry.getValue());   
  185.     }   
  186.     this.block = (this.fileSize % this.threads.length)==0? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1;   
  187.     if(this.data.size()==this.threads.length){   
  188.      for (int i = 0; i < this.threads.length; i++) {   
  189.       this.downloadSize += this.data.get(i+1);   
  190.      }   
  191.      print("已经下载的长度"+ this.downloadSize);   
  192.     }      
  193.    }else{   
  194.     throw new RuntimeException("server no response ");   
  195.    }   
  196.   } catch (Exception e) {   
  197.    print(e.toString());   
  198.    throw new RuntimeException("don't connection this url");   
  199.   }   
  200. }   
  201. /**   
  202.   * 获取文件名   
  203.   */   
  204. private String getFileName(HttpURLConnection conn) {   
  205.   String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);   
  206.   if(filename==null || "".equals(filename.trim())){//如果获取不到文件名称   
  207.    for (int i = 0;; i++) {   
  208.     String mine = conn.getHeaderField(i);   
  209.     if (mine == null) break;   
  210.     if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){   
  211.      Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());   
  212.      if(m.find()) return m.group(1);   
  213.     }   
  214.    }   
  215.    filename = UUID.randomUUID()+ ".tmp";//默认取一个文件名   
  216.   }   
  217.   return filename;   
  218. }    
  219. /**   
  220.   *  开始下载文件   
  221.   * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null   
  222.   * @return 已下载文件大小   
  223.   * @throws Exception   
  224.   */   
  225. public int download(SmartDownloadProgressListener listener) throws Exception{   
  226.   try {   
  227.    RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");   
  228.    if(this.fileSize>0) randOut.setLength(this.fileSize);   
  229.    randOut.close();   
  230.    URL url = new URL(this.downloadUrl);   
  231.    if(this.data.size() != this.threads.length){   
  232.     this.data.clear();//清除数据   
  233.     for (int i = 0; i < this.threads.length; i++) {   
  234.      this.data.put(i+1, 0);   
  235.     }   
  236.    }   
  237.    for (int i = 0; i < this.threads.length; i++) {   
  238.     int downLength = this.data.get(i+1);   
  239.     if(downLength < this.block && this.downloadSize<this.fileSize){ //该线程未完成下载时,继续下载         
  240.      this.threads = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);   
  241.      this.threads.setPriority(7);   
  242.      this.threads.start();   
  243.     }else{   
  244.      this.threads = null;   
  245.     }   
  246.    }   
  247.    this.fileService.save(this.downloadUrl, this.data);   
  248.    boolean notFinish = true;//下载未完成   
  249.    while (notFinish) {// 循环判断是否下载完毕   
  250.     Thread.sleep(900);   
  251.     notFinish = false;//假定下载完成   
  252.     for (int i = 0; i < this.threads.length; i++){   
  253.      if (this.threads != null && !this.threads.isFinish()) {   
  254.       notFinish = true;//下载没有完成   
  255.       if(this.threads.getDownLength() == -1){//如果下载失败,再重新下载   
  256.        this.threads = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);   
  257.        this.threads.setPriority(7);   
  258.        this.threads.start();   
  259.       }   
  260.      }   
  261.     }      
  262.     if(listener!=null) listener.onDownloadSize(this.downloadSize);   
  263.    }   
  264.    fileService.delete(this.downloadUrl);   
  265.   } catch (Exception e) {   
  266.    print(e.toString());   
  267.    throw new Exception("file download fail");   
  268.   }   
  269.   return this.downloadSize;   
  270. }   
  271. /**   
  272.   * 获取Http响应头字段   
  273.   * @param http   
  274.   * @return   
  275.   */   
  276. public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {   
  277.   Map<String, String> header = new LinkedHashMap<String, String>();   
  278.   for (int i = 0;; i++) {   
  279.    String mine = http.getHeaderField(i);   
  280.    if (mine == null) break;   
  281.    header.put(http.getHeaderFieldKey(i), mine);   
  282.   }   
  283.   return header;   
  284. }   
  285. /**   
  286.   * 打印Http头字段   
  287.   * @param http   
  288.   */   
  289. public static void printResponseHeader(HttpURLConnection http){   
  290.   Map<String, String> header = getHttpResponseHeader(http);   
  291.   for(Map.Entry<String, String> entry : header.entrySet()){   
  292.    String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";   
  293.    print(key+ entry.getValue());   
  294.   }   
  295. }   
  296. //打印日志   
  297. private static void print(String msg){   
  298.   Log.i(TAG, msg);   
  299. }    
  300. }   
  301. package com.smart.impl;   
  302. import java.io.File;   
  303. import java.io.InputStream;   
  304. import java.io.RandomAccessFile;   
  305. import java.net.HttpURLConnection;   
  306. import java.net.URL;   
  307. import android.util.Log;   
  308. public class SmartDownloadThread extends Thread {   
  309. private static final String TAG = "SmartDownloadThread";   
  310. private File saveFile;   
  311. private URL downUrl;   
  312. private int block;   
  313. /* *下载开始位置  */   
  314. private int threadId = -1;   
  315. private int downLength;   
  316. private boolean finish = false;   
  317. private SmartFileDownloader downloader;   
  318. public SmartDownloadThread(SmartFileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) {   
  319.   this.downUrl = downUrl;   
  320.   this.saveFile = saveFile;   
  321.   this.block = block;   
  322.   this.downloader = downloader;   
  323.   this.threadId = threadId;   
  324.   this.downLength = downLength;   
  325. }   
  326. @Override   
  327. public void run() {   
  328.   if(downLength < block){//未下载完成   
  329.    try {   
  330.     HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();   
  331.     http.setConnectTimeout(5 * 1000);   
  332.     http.setRequestMethod("GET");   
  333.     http.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");   
  334.     http.setRequestProperty("Accept-Language", "zh-CN");   
  335.     http.setRequestProperty("Referer", downUrl.toString());   
  336.     http.setRequestProperty("Charset", "UTF-8");   
  337.     int startPos = block * (threadId - 1) + downLength;//开始位置   
  338.     int endPos = block * threadId -1;//结束位置   
  339.     http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围   
  340.     http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.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)");   
  341.     http.setRequestProperty("Connection", "Keep-Alive");       
  342.     InputStream inStream = http.getInputStream();   
  343.     byte[] buffer = new byte[1024];   
  344.     int offset = 0;   
  345.     print("Thread " + this.threadId + " start download from position "+ startPos);   
  346.     RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");   
  347.     threadfile.seek(startPos);   
  348.     while ((offset = inStream.read(buffer, 0, 1024)) != -1) {   
  349.      threadfile.write(buffer, 0, offset);   
  350.      downLength += offset;   
  351.      downloader.update(this.threadId, downLength);   
  352.      downloader.saveLogFile();   
  353.      downloader.append(offset);   
  354.     }   
  355.     threadfile.close();   
  356.     inStream.close();      
  357.     print("Thread " + this.threadId + " download finish");   
  358.     this.finish = true;   
  359.    } catch (Exception e) {   
  360.     this.downLength = -1;   
  361.     print("Thread "+ this.threadId+ ":"+ e);   
  362.    }   
  363.   }   
  364. }   
  365. private static void print(String msg){   
  366.   Log.i(TAG, msg);   
  367. }   
  368. /**   
  369.   * 下载是否完成   
  370.   * @return   
  371.   */   
  372. public boolean isFinish() {   
  373.   return finish;   
  374. }   
  375. /**   
  376.   * 已经下载的内容大小   
  377.   * @return 如果返回值为-1,代表下载失败   
  378.   */   
  379. public long getDownLength() {   
  380.   return downLength;   
  381. }   
  382. }   
  383. package com.smart.activoty.download;   
  384. import java.io.File;   
  385. import android.app.Activity;   
  386. import android.os.Bundle;   
  387. import android.os.Environment;   
  388. import android.os.Handler;   
  389. import android.os.Message;   
  390. import android.view.View;   
  391. import android.widget.Button;   
  392. import android.widget.EditText;   
  393. import android.widget.ProgressBar;   
  394. import android.widget.TextView;   
  395. import android.widget.Toast;   
  396. import com.smart.impl.SmartDownloadProgressListener;   
  397. import com.smart.impl.SmartFileDownloader;   
  398. public class SmartDownloadActivity extends Activity {   
  399.     private ProgressBar downloadbar;   
  400.     private EditText pathText;   
  401.     private TextView resultView;   
  402.     private Handler handler = new Handler(){   
  403.   @Override//信息   
  404.   public void handleMessage(Message msg) {   
  405.    switch (msg.what) {   
  406.    case 1:   
  407.     int size = msg.getData().getInt("size");   
  408.     downloadbar.setProgress(size);   
  409.     float result = (float)downloadbar.getProgress()/ (float)downloadbar.getMax();   
  410.     int p = (int)(result*100);   
  411.     resultView.setText(p+"%");   
  412.     if(downloadbar.getProgress()==downloadbar.getMax())   
  413.      Toast.makeText(SmartDownloadActivity.this, R.string.success, 1).show();   
  414.     break;   
  415.    case -1:   
  416.     Toast.makeText(SmartDownloadActivity.this, R.string.error, 1).show();   
  417.     break;   
  418.    }   
  419.       
  420.   }        
  421.     };       
  422.     @Override   
  423.     public void onCreate(Bundle savedInstanceState) {   
  424.         super.onCreate(savedInstanceState);   
  425.         setContentView(R.layout.main);   
  426.            
  427.         Button button = (Button)this.findViewById(R.id.button);   
  428.         downloadbar = (ProgressBar)this.findViewById(R.id.downloadbar);   
  429.         pathText = (EditText)this.findViewById(R.id.path);   
  430.         resultView = (TextView)this.findViewById(R.id.result);   
  431.         button.setOnClickListener(new View.OnClickListener() {      
  432.    @Override   
  433.    public void onClick(View v) {   
  434.     String path = pathText.getText().toString();   
  435.     if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){   
  436.      File dir = Environment.getExternalStorageDirectory();//文件保存目录   
  437.      download(path, dir);   
  438.     }else{   
  439.      Toast.makeText(SmartDownloadActivity.this, R.string.sdcarderror, 1).show();   
  440.     }   
  441.    }   
  442.   });   
  443.     }   
  444.     //对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错   
  445.     private void download(final String path, final File dir){   
  446.      new Thread(new Runnable() {   
  447.    @Override   
  448.    public void run() {   
  449.     try {   
  450.      SmartFileDownloader loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3);   
  451.      int length = loader.getFileSize();//获取文件的长度   
  452.      downloadbar.setMax(length);   
  453.      loader.download(new SmartDownloadProgressListener(){   
  454.       @Override   
  455.       public void onDownloadSize(int size) {//可以实时得到文件下载的长度   
  456.        Message msg = new Message();   
  457.        msg.what = 1;   
  458.        msg.getData().putInt("size", size);         
  459.        handler.sendMessage(msg);   
  460.       }});   
  461.     } catch (Exception e) {   
  462.      Message msg = new Message();//信息提示   
  463.      msg.what = -1;   
  464.      msg.getData().putString("error", "下载失败");//如果下载错误,显示提示失败!   
  465.      handler.sendMessage(msg);   
  466.     }   
  467.    }   
  468.   }).start();//开始         
  469.     }   
  470. }   

【编辑推荐】

Android智能手机操作系统

Android开发之旅:Android架构

Android Activity和Intent机制学习笔记

责任编辑:zhaolei 来源: 网络转载
相关推荐

2015-02-03 15:06:23

android多线程下载

2009-07-17 17:29:13

多任务多线程

2013-08-13 14:39:29

多任务下载

2022-04-14 11:44:25

LiteOS线程鸿蒙

2023-08-02 09:29:40

任务池TaskPool

2023-08-01 16:35:48

鸿蒙ArkUI应用开发

2010-02-26 17:47:07

2015-11-18 18:56:36

Java多线程处理

2014-05-09 12:59:26

iOS移动互联网

2014-06-18 10:41:31

Android多任务机制

2009-08-13 09:07:36

Java多线程

2014-12-31 15:42:21

Android多线程软件下载

2012-12-25 11:39:20

Pythoncrawler

2011-07-18 14:23:40

iPhone 多任务

2022-09-28 15:34:06

机器学习语音识别Pytorch

2015-06-17 10:41:50

2021-10-13 09:33:26

Python 多任务进程

2021-09-09 07:16:00

C#多线程开发

2023-11-07 07:13:31

推荐系统多任务学习

2012-05-18 13:26:11

HTC
点赞
收藏

51CTO技术栈公众号