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,如需转载请自行联系原作者




相关文章
|
8月前
|
API 数据处理 Android开发
Android网络请求演变:从Retrofit到Flow的转变过程。
通过这个比喻,我们解释了 Android 网络请求从 Retrofit 到 Flow 的转变过程。这不仅是技术升级的体现,更是反映出开发者在面对并发编程问题时,持续探索和迭求更好地解决方案的精神。未来,还会有更多新的技术和工具出现,我们期待一同 witness 这一切的发展。
231 36
|
6月前
|
Web App开发 缓存 JavaScript
Android网络小说阅读器的实现
小说阅读Demo,。此Demo使用Jsoup解析HTML,实现小说数据抓取(数据源自网络),并包含自定义View、六章小说缓存等功能,但未实现下载。项目还包括屏幕适配、字体设置等,借助第三方框架完成优化。以下是主页、详情页、阅读页等界面展示。
130 0
|
8月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
294 23
|
8月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
222 15
|
网络协议 Shell 网络安全
解决两个 Android 模拟器之间无法网络通信的问题
让同一个 PC 上运行的两个 Android 模拟器之间能相互通信,出(qiong)差(ren)的智慧。
217 3
|
监控 Java API
Android经典实战之OkDownload:一个经典强大的文件下载开源库,支持断点续传
本文介绍的 OkDownload 是一个专为 Android 设计的开源下载框架,支持多线程下载、断点续传和任务队列管理等功能,具备可靠性、灵活性和高性能特点。它提供了多种配置选项和监听器,便于开发者集成和扩展。尽管已多年未更新,但依然适用于大多数文件下载需求。
1078 1
|
安全 网络安全 Android开发
探索安卓开发之旅:从新手到专家网络安全与信息安全:防范网络威胁,保护数据安全
【8月更文挑战第29天】在这篇技术性文章中,我们将踏上一段激动人心的旅程,探索安卓开发的世界。无论你是刚开始接触编程的新手,还是希望提升技能的资深开发者,这篇文章都将为你提供宝贵的知识和指导。我们将从基础概念入手,逐步深入到安卓开发的高级主题,包括UI设计、数据存储、网络通信等方面。通过阅读本文,你将获得一个全面的安卓开发知识体系,并学会如何将这些知识应用到实际项目中。让我们一起开启这段探索之旅吧!
|
缓存 API 调度
Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架
🔥 介绍 本文通过OkHttp+Retrofit+Rxjava+Hilt实现一个网络请求框。
882 0
Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架
|
XML JSON Android开发
[Android]网络框架之Retrofit(kotlin)
[Android]网络框架之Retrofit(kotlin)
640 0
|
缓存 JSON Android开发
[Android]网络框架之OkHttp(详细)(kotlin)
[Android]网络框架之OkHttp(详细)(kotlin)
781 0

热门文章

最新文章