zoukankan      html  css  js  c++  java
  • Android 多任务多线程断点下载

    1. package com.smart.db;   
    2. import java.util.HashMap;   
    3. import java.util.Map;   
    4. import android.content.Context;    s
    5. import android.database.Cursor;   
    6. import android.database.sqlite.SQLiteDatabase;   
    7. /**    s
    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. }   
  • 相关阅读:
    vnpy源码阅读学习(8):关于app
    vnpy源码阅读学习(6):事件引擎
    vnpy源码阅读学习(5):关于MainEngine的代码阅读
    tensorflow 2.1 采坑记
    vnpy源码阅读学习(4):自己写一个类似vnpy的UI框架
    ABP (.Net Core 3.1版本) 使用MySQL数据库迁移启动模板项目(1)
    'vue-cli-service' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
    C# Winform版批量压缩图片程序
    小程序开发技巧总结
    ASP.NET WebAPI 双向token实现对接小程序登录逻辑
  • 原文地址:https://www.cnblogs.com/xiaoxiaoboke/p/2088124.html
Copyright © 2011-2022 走看看