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

    学习了多线程下载,而且可以断点续传的逻辑,线程数量可以自己选择,但是线程数量过多手机就承受不起,导致闪退,好在有断点续传。

    步骤写在了代码的注释里。大概就是获取服务器文件的大小,在本地新建一个相同大小的文件用来申请空间,然后将服务器的文件读下来写到申请的文件中去。若开多线程,将文件分块,计算每个线程下载的开始位置和结束位置。若断点传输,则保存断开后下载的位置,下次将此位置赋给开始下载的位置即可。细节见代码。

    下面是效果图:

    布局文件activity_main.xml:

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     xmlns:tools="http://schemas.android.com/tools"
     4     android:layout_width="match_parent"
     5     android:layout_height="match_parent"
     6     tools:context=".MainActivity">
     7 
     8     <LinearLayout
     9         android:layout_width="match_parent"
    10         android:layout_height="match_parent"
    11         android:orientation="vertical">
    12 
    13         <EditText
    14             android:id="@+id/et_path"
    15             android:layout_width="match_parent"
    16             android:layout_height="wrap_content"
    17             android:hint="请输入下载路径"
    18             android:text="http://10.173.29.234/test.exe" />
    19 
    20         <EditText
    21             android:id="@+id/et_threadCount"
    22             android:layout_width="match_parent"
    23             android:layout_height="wrap_content"
    24             android:hint="请输入线程数量" />
    25 
    26         <Button
    27             android:layout_width="wrap_content"
    28             android:layout_height="wrap_content"
    29             android:onClick="click"
    30             android:text="下载" />
    31 
    32         <LinearLayout
    33             android:id="@+id/ll_pb"
    34             android:layout_width="match_parent"
    35             android:layout_height="match_parent"
    36             android:background="#455eee"
    37             android:orientation="vertical">
    38 
    39         </LinearLayout>
    40     </LinearLayout>
    41 
    42 </android.support.constraint.ConstraintLayout>

    创建布局文件,用来动态显示每个线程的进度条

    layout.xml:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
    3     android:id="@+id/progressBar"
    4     style="?android:attr/progressBarStyleHorizontal"
    5     android:layout_width="match_parent"
    6     android:layout_height="wrap_content" />

    MainActivity.java:

      1 import...;
      2 
      3 public class MainActivity extends AppCompatActivity {
      4 
      5     private EditText et_path;
      6     private EditText et_threadCount;
      7     private LinearLayout ll_pb;
      8     private String path;
      9 
     10     private static int runningThread;// 代表正在运行的线程
     11     private int threadCount;
     12     private List<ProgressBar> pbList;//集合存储进度条的引用
     13 
     14     @Override
     15     protected void onCreate(Bundle savedInstanceState) {
     16         super.onCreate(savedInstanceState);
     17         setContentView(R.layout.activity_main);
     18 
     19         et_path = findViewById(R.id.et_path);
     20         et_threadCount = findViewById(R.id.et_threadCount);
     21         ll_pb = findViewById(R.id.ll_pb);
     22         //添加一个进度条的引用
     23         pbList = new ArrayList<ProgressBar>();
     24     }
     25 
     26     //点击按钮实现下载逻辑
     27     public void click(View view) {
     28         //获取下载路径
     29         path = et_path.getText().toString().trim();
     30         //获取线程数量
     31         String threadCounts = et_threadCount.getText().toString().trim();
     32         //移除以前的进度条添加新的进度条
     33         ll_pb.removeAllViews();
     34         threadCount = Integer.parseInt(threadCounts);
     35         pbList.clear();
     36         for (int i = 0; i < threadCount; i++) {
     37             ProgressBar v = (ProgressBar) View.inflate(getApplicationContext(), R.layout.layout, null);
     38 
     39             //把v添加到几何中
     40             pbList.add(v);
     41 
     42             //动态获取进度条
     43             ll_pb.addView(v);
     44         }
     45 
     46         //java逻辑移植
     47         new Thread() {
     48             @Override
     49             public void run() {
     50                 /*************/
     51                 System.out.println("你好");
     52                 try {
     53                     URL url = new URL(path);
     54                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
     55                     conn.setRequestMethod("GET");
     56                     conn.setConnectTimeout(5000);
     57                     int code = conn.getResponseCode();
     58                     if (code == 200) {
     59                         int length = conn.getContentLength();
     60                         // 把运行线程的数量赋值给runningThread
     61                         runningThread = threadCount;
     62 
     63                         System.out.println("length=" + length);
     64                         // 创建一个和服务器的文件一样大小的文件,提前申请空间
     65                         RandomAccessFile randomAccessFile = new RandomAccessFile(getFileName(path), "rw");
     66                         randomAccessFile.setLength(length);
     67                         // 算出每个线程下载的大小
     68                         int blockSize = length / threadCount;
     69                         // 计算每个线程下载的开始位置和结束位置
     70                         for (int i = 0; i < length; i++) {
     71                             int startIndex = i * blockSize;// 开始位置
     72                             int endIndex = (i + 1) * blockSize;// 结束位置
     73                             // 特殊情况就是最后一个线程
     74                             if (i == threadCount - 1) {
     75                                 // 说明是最后一个线程
     76                                 endIndex = length - 1;
     77                             }
     78                             // 开启线程去服务器下载
     79                             DownLoadThread downLoadThread = new DownLoadThread(startIndex, endIndex, i);
     80                             downLoadThread.start();
     81 
     82                         }
     83 
     84                     }
     85                 } catch (MalformedURLException e) {
     86                     // TODO Auto-generated catch block
     87                     e.printStackTrace();
     88                 } catch (IOException e) {
     89                     // TODO Auto-generated catch block
     90                     e.printStackTrace();
     91                 }
     92                 /*************/
     93             }
     94         }.start();
     95 
     96     }
     97 
     98     private class DownLoadThread extends Thread {
     99         // 通过构造方法吧每个线程的开始位置和结束位置传进来
    100         private int startIndex;
    101         private int endIndex;
    102         private int threadID;
    103         private int PbMaxSize;//代表当前下载(进度条)的最大值
    104         private int pblastPosition;//如果中断过,这是进度条上次的位置
    105 
    106         public DownLoadThread(int startIndex, int endIndex, int threadID) {
    107             this.startIndex = startIndex;
    108             this.endIndex = endIndex;
    109             this.threadID = threadID;
    110 
    111         }
    112 
    113         @Override
    114         public void run() {
    115             // 实现去服务器下载文件
    116             try {
    117                 //计算进度条最大值
    118                 PbMaxSize = endIndex - startIndex;
    119                 URL url = new URL(path);
    120                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    121                 conn.setRequestMethod("GET");
    122                 conn.setConnectTimeout(5000);
    123                 // 如果中间断过,接着上次的位置继续下载,聪慧文件中读取上次下载的位置
    124                 File file = new File(getFileName(path) + threadID + ".txt");
    125                 if (file.exists() && file.length() > 0) {
    126                     FileInputStream fis = new FileInputStream(file);
    127                     BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
    128                     String lastPosition = bufr.readLine();
    129                     int lastPosition1 = Integer.parseInt(lastPosition);
    130 
    131                     //赋值给进度条位置
    132                     pblastPosition = lastPosition1 - startIndex;
    133                     // 改变一下startIndex的值
    134                     startIndex = lastPosition1 + 1;
    135                     System.out.println("线程id:" + threadID + "真实下载的位置:" + lastPosition + "-------" + endIndex);
    136 
    137                     bufr.close();
    138                     fis.close();
    139 
    140                 }
    141 
    142                 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    143                 int code = conn.getResponseCode();
    144                 if (code == 206) {
    145                     // 随机读写文件对象
    146                     RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
    147                     // 每个线程从自己的位置开始写
    148 
    149                     raf.seek(startIndex);
    150                     InputStream in = conn.getInputStream();
    151                     // 把数据写到文件中
    152                     int len = -1;
    153                     byte[] buffer = new byte[1024];
    154                     int totle = 0;// 代表当前线程下载的大小
    155                     while ((len = in.read(buffer)) != -1) {
    156                         raf.write(buffer, 0, len);
    157                         totle += len;
    158 
    159                         // 实现断点续传就是把当前线程下载的位置保存起来,下次再下载的时候按照上次下载的位置继续下载
    160                         int currentThreadPosition = startIndex + totle;// 存到一个txt文本中
    161                         // 用来存储当前线程当前下载的位置
    162                         RandomAccessFile raff = new RandomAccessFile(getFileName(path) + threadID + ".txt", "rwd");
    163                         raff.write(String.valueOf(currentThreadPosition).getBytes());
    164                         raff.close();
    165 
    166                         //设置进度条当前的进度
    167                         pbList.get(threadID).setMax(PbMaxSize);
    168                         pbList.get(threadID).setProgress(pblastPosition + totle);
    169                     }
    170                     raf.close();
    171                     System.out.println("线程ID:" + threadID + "下载完成");
    172                     // 将产生的txt文件删除,每个线程下载完成的具体时间不知道
    173                     synchronized (DownLoadThread.class) {
    174                         runningThread--;
    175                         if (runningThread == 0) {
    176                             //说明线程执行完毕
    177                             for (int i = 0; i < threadCount; i++) {
    178 
    179                                 File filedel = new File(getFileName(path) + i + ".txt");
    180                                 filedel.delete();
    181                             }
    182 
    183                         }
    184 
    185                     }
    186 
    187                 }
    188             } catch (MalformedURLException e) {
    189                 // TODO Auto-generated catch block
    190                 e.printStackTrace();
    191             } catch (IOException e) {
    192                 // TODO Auto-generated catch block
    193                 e.printStackTrace();
    194             }
    195 
    196         }
    197     }
    198 
    199     public String getFileName(String path) {
    200         int start = path.lastIndexOf("/") + 1;
    201         String subString = path.substring(start);
    202         String fileName = "/data/data/com.lgqrlchinese.heima76android_11_mutildownload/" + subString;
    203         return fileName;
    204 
    205     }
    206 }

    在清单文件中添加以下权限

    1     <uses-permission android:name="android.permission.INTERNET"/>
    2     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    笔记结束

    bug非常多,而且谁又愿意写这么繁琐的逻辑,所以找到别人写开源框架,下一篇将会作下笔记

    昔日我曾苍老,如今风华正茂(ง •̀_•́)ง
  • 相关阅读:
    PHP 将二维数组中某列值作为数组的键名
    MySQL 8下忘密码后重置密码
    单一职责原则
    Linux下安装SVN服务端小白教程
    go 代码玩耍
    centos7 docker开启认证的远程端口2376配置教程
    Dockerfile RUN,CMD,ENTRYPOINT命令区别
    wait-for-it.sh脚本控制docker-compose启动顺序详解
    阿里云服务器漏洞修复_2020.5.22
    Let's Encrypt 免费通配符 SSL 证书申请教程
  • 原文地址:https://www.cnblogs.com/lgqrlchinese/p/10022419.html
Copyright © 2011-2022 走看看