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




相关文章
|
24天前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
49 2
|
3天前
|
开发工具 Android开发 数据安全/隐私保护
探索iOS与安卓应用开发的异同:技术、工具和市场趋势
在移动操作系统的广阔舞台上,iOS和安卓两大主角各自演绎着怎样的精彩?本文将深入剖析这两大平台在应用开发过程中的技术差异、开发工具的选择以及面对的市场环境。通过数据支撑和案例分析,我们将一窥这两个系统如何影响开发者的决策,并探讨它们未来的发展方向。
|
5天前
|
存储 缓存 Java
Android性能优化:内存管理与LeakCanary技术详解
【7月更文挑战第21天】内存管理是Android性能优化的关键部分,而LeakCanary则是进行内存泄漏检测和修复的强大工具。
|
23天前
|
机器学习/深度学习 人工智能 文字识别
文本,文字扫描01,OCR文本识别技术展示,一个安卓App,一个简单的设计,文字识别可以应用于人工智能,机器学习,车牌识别,身份证识别,银行卡识别,PaddleOCR+SpringBoot+Andr
文本,文字扫描01,OCR文本识别技术展示,一个安卓App,一个简单的设计,文字识别可以应用于人工智能,机器学习,车牌识别,身份证识别,银行卡识别,PaddleOCR+SpringBoot+Andr
|
1月前
|
安全 网络协议 算法
Android网络基础面试题之HTTPS的工作流程和原理
HTTPS简述 HTTPS基于TCP 443端口,通过CA证书确保服务器身份,使用DH算法协商对称密钥进行加密通信。流程包括TCP握手、证书验证(公钥解密,哈希对比)和数据加密传输(随机数加密,预主密钥,对称加密)。特点是安全但慢,易受特定攻击,且依赖可信的CA。每次请求可能复用Session ID以减少握手。
28 2
|
1月前
|
Android开发
技术经验分享:Android前后台切换的监听
技术经验分享:Android前后台切换的监听
19 2
|
13天前
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
|
18天前
|
安全 搜索推荐 Android开发
安卓与iOS的较量:技术深度与用户体验的博弈
在数字时代的浪潮中,安卓与iOS两大操作系统如同巨人般主宰着移动设备的生态。本文将深入探讨这两大系统在技术架构、安全性、应用生态和用户体验等方面的差异,并结合最新的市场数据和技术发展动态,为读者呈现一个全面而深入的分析视角。我们将看到,尽管安卓以其开放性和高度可定制性赢得了广泛的市场份额,iOS则以其卓越的用户体验和强大的品牌忠诚度构建了坚固的护城河。两者的竞争不仅推动了技术的快速进步,也不断塑造着我们对智能手机的期待和想象。
|
25天前
|
安全 Java 数据处理
Android多线程编程实践与优化技巧
Android多线程编程实践与优化技巧
|
26天前
|
5G Android开发 iOS开发
探索iOS与安卓在移动操作系统领域的技术竞争与合作
本文将深入探讨iOS和安卓这两大移动操作系统的技术竞争与合作。通过对市场份额、用户忠诚度、技术创新、生态系统建设以及安全性等方面的比较,我们将揭示这两个系统各自的优势和挑战。同时,我们还将分析它们如何通过技术合作来推动整个移动行业的发展。