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




相关文章
|
2月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
44 2
|
18天前
|
安全 网络安全 Android开发
安卓与iOS开发:选择的艺术网络安全与信息安全:漏洞、加密与意识的交织
【8月更文挑战第20天】在数字时代,安卓和iOS两大平台如同两座巍峨的山峰,分别占据着移动互联网的半壁江山。它们各自拥有独特的魅力和优势,吸引着无数开发者投身其中。本文将探讨这两个平台的特点、优势以及它们在移动应用开发中的地位,帮助读者更好地理解这两个平台的差异,并为那些正在面临选择的开发者提供一些启示。
113 56
|
9天前
|
监控 Java API
Android经典实战之OkDownload:一个经典强大的文件下载开源库,支持断点续传
本文介绍的 OkDownload 是一个专为 Android 设计的开源下载框架,支持多线程下载、断点续传和任务队列管理等功能,具备可靠性、灵活性和高性能特点。它提供了多种配置选项和监听器,便于开发者集成和扩展。尽管已多年未更新,但依然适用于大多数文件下载需求。
50 1
|
23天前
|
API Windows
揭秘网络通信的魔法:Win32多线程技术如何让服务器化身超级英雄,同时与成千上万客户端对话!
【8月更文挑战第16天】在网络编程中,客户/服务器模型让客户端向服务器发送请求并接收响应。Win32 API支持在Windows上构建此类应用。首先要初始化网络环境并通过`socket`函数创建套接字。服务器需绑定地址和端口,使用`bind`和`listen`函数准备接收连接。对每个客户端调用`accept`函数并在新线程中处理。客户端则通过`connect`建立连接,双方可通过`send`和`recv`交换数据。多线程提升服务器处理能力,确保高效响应。
33 6
|
27天前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
34 4
|
10天前
|
安全 网络安全 Android开发
探索安卓开发之旅:从新手到专家网络安全与信息安全:防范网络威胁,保护数据安全
【8月更文挑战第29天】在这篇技术性文章中,我们将踏上一段激动人心的旅程,探索安卓开发的世界。无论你是刚开始接触编程的新手,还是希望提升技能的资深开发者,这篇文章都将为你提供宝贵的知识和指导。我们将从基础概念入手,逐步深入到安卓开发的高级主题,包括UI设计、数据存储、网络通信等方面。通过阅读本文,你将获得一个全面的安卓开发知识体系,并学会如何将这些知识应用到实际项目中。让我们一起开启这段探索之旅吧!
|
18天前
|
Java Android开发 Kotlin
Android项目架构设计问题之要在Glide库中加载网络图片到ImageView如何解决
Android项目架构设计问题之要在Glide库中加载网络图片到ImageView如何解决
23 0
|
18天前
|
Java Android开发 开发者
Android项目架构设计问题之使用Retrofit2作为网络库如何解决
Android项目架构设计问题之使用Retrofit2作为网络库如何解决
27 0
|
2月前
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
|
算法 Java Android开发
Android模拟多线程下载
Android模拟多线程下载
74 0
下一篇
DDNS