Android Day05-网络编程之文件下载之多线程断点续传技术

简介:

文件下载之多线程断点续传技术底层实现

 通过HttpURLConnection连接

 断点续传核心步骤:      

  1.UI设计

  2.获取服务器文件的大小,通过连接的一个方法getContentLength()来得到。

  3.在客户端创建一个和将要下载的文件的同样大小的同名文件。

  4.计算每个线程的起始位置和结束位置

  5.开启多个线程,采用RandomAccessFile类,根据计算得到的起始位置和结束位置来随机的读取服务

   器资源的输出流,并且实时的采用RandomAccessFile类保存线程读取到的字节位置。

  6.判断线程结束和文件上传成功。

  断点续传具体实现:  

  1.UI设计

    下载主界面设计

   wKioL1WuU0_xbFmEAADX87DPYAY439.jpg

     进度条单独存放在一个布局文件中,注意进度条要采用以下的样式的进度条。

     wKiom1WuUirQJRgdAAAW5ywmjtw245.jpg    

  2.代码逻辑

     多线程断点续传的技术可以说是目前学Android课程以来最难、代码最长的一个案例,那么如何

   有章有序的将代码写出来呢?

     思路整理

    1)网络访问权限和SD卡访问权限的添加

     2)初始化动作及下载点击事件的书写wKiom1WuXfvTjkZPAASURy1t-gc755.jpg 

     3)随机读取文件线程类的定义

      wKiom1WuZonBOlzlAAQ2INrID8c765.jpg  

     具体代码:   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
     // 注册点击事件
     public  void  click(View v) {
         path = et_path.getText().toString().trim();
         String threadcount_str = et_threadcount.getText().toString().trim();
         final  int  threadcount = Integer.parseInt(threadcount_str);
 
         
         if  (TextUtils.isEmpty(path) || threadcount <  0 ) {
             Toast.makeText(getApplicationContext(),  "输入错误" 0 ).show();
             return ;
         }
 
         list_pbBars =  new  ArrayList<ProgressBar>();
         // 先清空进度条布局
         ll_layout.removeAllViews();
         // 添加进度条
         for  ( int  i =  0 ; i < threadcount; i++) {
             //得到进度条的View对象
             View item_pb_View = View.inflate(getApplicationContext(),R.layout.item_progress,  null );
             //通过View再得到进度条
             ProgressBar pb_bar = (ProgressBar) item_pb_View.findViewById(R.id.item_pb);
             list_pbBars.add(pb_bar);
             ll_layout.addView(item_pb_View);
         }
         try  {
             //将服务器文件路径用File包装一下,以便获得它的文件名。
             sourceFile =  new  File(path);
             
             //访问URL得到文件的大小 ,通过Content-Length得到。
             new  Thread(){
                 public  void  run()
                 {
                     System.out.println( "xxx" +Thread.currentThread().getName());
                     try  {
                         //创建HttpURLConnection
                         URL url =  new  URL(path);
                         
                         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                         
                         conn.setRequestMethod( "GET" );
                         //获取状态码
                         int  responseCode = conn.getResponseCode();
                         if (responseCode ==  200 )
                         {
                             //获取资源文件的长度
                             sourceFileLength = conn.getContentLength();
                             
                             
                             sourceFileName = sourceFile.getName();
                             clientFile =  new  RandomAccessFile(Environment.getExternalStorageDirectory() +  "/"
                                             + sourceFileName,  "rw" );
                             clientFile.setLength(sourceFileLength);
                             
                             
                             //正在运行的线程
                             runningThreadCount = threadcount;
                             // 第3步: 计算每个线程读取文件的起始和结束位置
                             int  blockSize = sourceFileLength / threadcount;
                             for  ( int  i =  0 ; i < threadcount; i++) {
                                 //得到每个线程读取的起始和结束位置
                                 int  start_index = i * blockSize;
                                 int  end_index = (i +  1 ) * blockSize -  1 ;
                                 int  last_index = - 1 ;
                                 if (i == threadcount -  1 )
                                 {
                                     end_index = sourceFileLength - 1 ;
                                 }
                                 //------如果采用了断点续传,线程的起始位置就是上次记录位置。
                                 
                                 File recordIndexFile =  new  File(Environment.getExternalStorageDirectory().getPath() +  "/"  +i +  ".txt" );
                                 
                                 //如果有断点文件,就得到上次记录的读取位置。
                                 if (recordIndexFile.exists() && recordIndexFile.length() >  0 )
                                 {
                                     BufferedReader bufr =  new  BufferedReader( new  FileReader(recordIndexFile));
                                     last_index = Integer.parseInt(bufr.readLine());
//                                  last_index++;
                                     bufr.close();
                                 }
                                 else   //否则上次读取位置就是开始位置。
                                 {
                                     last_index = start_index;
                                 }
                                 //设置进度条的总长度
                                 list_pbBars.get(i).setMax(( int )(end_index - start_index));
                                 
                                 System.out.println( "线程" +i + "::" + last_index +  "----"  + end_index);
                                 // 第4步:启动线程
                                 new  DownloadThread(i, start_index, end_index,last_index).start();
                             }
                             
                             
                             
                         }
                         else
                         {
                             showToast( "请求不到资源 " );
                             return ;
                         }
                     catch  (Exception e) {
                         e.printStackTrace();
                     }
                 }
             }.start();
             
             
             
             
                 
             // 第5步:判断线程结束
 
         catch  (Exception e) {
             e.printStackTrace();
         }
     }
 
     // 定义下载的线程类
     class  DownloadThread  extends  Thread {
         private  int  threadID;
         private  int   start_index, end_index,last_index;
         private  int  total =  0 ;       //记录已经读写的总的字节数
         public  DownloadThread( int  threadID,  int  start_index,  int  end_index, int  last_index) {
             this .threadID = threadID;
             this .start_index = start_index;
             this .end_index = end_index;
             this .last_index = last_index;
             this .setName( "线程"  + threadID);
         }
 
         public  void  run() {
             try  {
                 //创建HttpURLConnection
                 URL url =  new  URL(path);
                 
                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                 
                 conn.setRequestMethod( "GET" );
//              conn.setReadTimeout(5);
                 
                 //设置一个头信息Range,表示要读取的流的字节范围。
                 conn.setRequestProperty( "Range" "bytes="  + last_index +  "-"  + end_index);
                 
                 
                 
                 //获取状态码
                 int  responseCode = conn.getResponseCode();
                 //注意多线程下载状态得用206来判断
                 if (responseCode ==  206 )
                 {
                     //得到服务器的输出流
                     InputStream serverIn = conn.getInputStream();
                     //得到客户端的文件输出流
                     RandomAccessFile myClientFile =  new  RandomAccessFile(
                             Environment.getExternalStorageDirectory() +  "/"
                                     + sourceFileName,  "rwd" );
                     myClientFile.seek(last_index);
                     //读取服务器文件写入到客户端文件中
                     //断点续传文件
                     
                     int  len =  0 ;
                     byte [] buf =  new  byte [ 1024 * 1024 ];
                     while ((len = serverIn.read(buf))!= - 1 )
                     {
                         myClientFile.write(buf, 0 ,len);
                         total += len;
                         //实时存储当前读取到的文件位置。
                          int  current_index = last_index + total;
                         //因为此处实时的存储读取文件的位置,将缓冲区buf稍微弄大点。
                         
                         // 改
                         /***注意读取文件位置的流一定要用RandomAccessFile,因为它可以直接同步到缓存(磁盘上)。***/
                         RandomAccessFile raffAccessFile =  new  RandomAccessFile(Environment.getExternalStorageDirectory().getPath()+ "/" +threadID+ ".txt" "rwd" );
                         raffAccessFile.write(String.valueOf(current_index).getBytes());
                         raffAccessFile.close();
                         //设置进度条的当前进度
                         list_pbBars.get(threadID).setProgress(( int )(total + last_index - start_index));
                     }
                     myClientFile.close();
                     serverIn.close();
                     
                     
                     //操作共享资源,注意加锁。
                     synchronized  (DownloadThread. class ) {
                         runningThreadCount--;
                         if (runningThreadCount ==  0 )
                         {
                             Toast.makeText(getApplicationContext(),  "文件下载完毕" 0 ).show();
                         }
                     }
                     
                 }
                 else 
                 {
                     showToast( "服务器忙" );
                 }
             catch  (Exception e) {
                 e.printStackTrace();
             }
             
         }
     }

   


      本文转自屠夫章哥  51CTO博客,原文链接:http://blog.51cto.com/4259297/1676571,如需转载请自行联系原作者




相关文章
|
3天前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
3天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
3天前
|
Android开发
Android网络访问超时
Android网络访问超时
12 2
|
3天前
|
Java Linux API
统计android设备的网络数据使用量
统计android设备的网络数据使用量
16 0
|
1天前
|
Java 测试技术 开发工具
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
|
2天前
|
Android开发
Android中的多线程及AsyncTask的引入,最终入职阿里
Android中的多线程及AsyncTask的引入,最终入职阿里
|
2天前
|
机器学习/深度学习 PyTorch 算法框架/工具
神经网络基本概念以及Pytorch实现,多线程编程面试题
神经网络基本概念以及Pytorch实现,多线程编程面试题
|
2天前
|
设计模式 算法 Android开发
2024年Android网络编程总结篇,androidview绘制流程面试
2024年Android网络编程总结篇,androidview绘制流程面试
2024年Android网络编程总结篇,androidview绘制流程面试
|
3天前
|
Android开发
android检测网络连接是否存在(一)
android检测网络连接是否存在(一)
12 2
|
3天前
|
移动开发 Java Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【4月更文挑战第24天】 在移动开发领域,尤其是对于Android平台而言,网络请求是一个不可或缺的功能。然而,随着用户对应用响应速度和稳定性要求的不断提高,传统的异步处理方式如回调地狱和RxJava已逐渐显示出局限性。本文将探讨如何利用Kotlin协程来简化异步代码,提升网络请求的效率和可读性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android应用中集成和优化网络请求。