Android下载文件(一)下载进度&断点续传

索引

  • Android下载文件(一)下载进度&断点续传
  • Android下载文件(二)单任务多线程并发&断点续传(待续)
  • Android下载文件(三)自定义进度条(待续)
  • Android下载文件(四)任务信息持久化储存(待续)
  • Android下载文件(五)IPC(待续)
  • Android下载文件(六)XDownloader(待续)

    前言

    从接触Android开发至今也快两年了,一路走过来可以说是站在巨人的肩膀上前进,真的很感激为开源世界作出贡献的人。话说回来,搞了这么久的开发却一直在用别人的劳动成果也不是回事,所以我决定写几篇文章分享我对Android下载文件的理解,并在最后整合并开源一个框架,也是对我在Android之旅中的一个小小的总结。

    注意:本人能力有限,如有错误、不合理、可优化的地方 请务必告知我!复制代码

    实现效果

    本节主要讲解Android下载文件的进度获取和断点续传,效果如下

    所需知识点

  • volatile
  • RandomAccessFile
  • HttpURLConnection
  • Handler

    volatile

    volatile是java中修饰变量的关键字,在这里重点讲下其特性,后面会用到。
    如需深入理解请参考 《深入理解Java虚拟机》12.3.3 对于volatile型变量的特殊规则

1. 保证可见性
根据JVM内存模型得知,JVM将内存分为主内存与工作内存两个部分,所有的变量都存放在主内存中。而每条线程有自己的工作内存,其存放部分主存中变量的拷贝,线程对变量的操作必须在工作内存中完成,然后更新到主存中。
当一个共享变量被volatile修饰,它会保证修改的值立即更新到主存中,其他线程访问时会去主存中读取新的值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时主存中可能还是原来的旧值,因此无法保证可见性。

2. 禁止指令重排
当代码编译时JVM会对指令执行的顺序进行优化,但volatile不会,如下所示

//x、y为非volatile变量
//flag为volatile变量
x = 2;        //语句1
y = 0;        //语句2
flag = true;  //语句3
x = 4;        //语句4
y = -1;       //语句5复制代码

语句3必定在语句1/2后执行,但语句1/2顺序不做保证,同理,语句3也必定在语句4/5前面执行,语句4/5执行的顺序也不做保证。

3. 非原子性
volatile变量是不保证原子性的,但是需要注意的是 volatile关键字对long/double类型的get/set操作保证了原子性,详见这里 。

HttpURLConnection

Android基本网络请求类,这个不必多说,接触过Android开发的同学也一定会了解,如果是Android新同学请点我 。至于为什么我用HttpURLConnection而不用OKhttp或者Retrofit,因为最终我会开源一个Android下载文件的框架,所以不做过多的外部依赖。

RandomAccessFile

这个类很特殊,虽然是java.io包下的,但是只实现了DataOutput, DataInput, Closeable这三个接口,唯一父类是Object。其功能是随机读写文件,换句话说就是可以在一个文件的任何位置读取或者写入。在本文中用它来实现文件下载的断点续传。

Handler

Android开发必然涉及到的东西,新同学请点我 。

###准备好了,开始撸代码
1.首先下载文件需要下载链接/下载路径/文件名等属性,所以我们写一个JavaBean,这里用到了volatile关键字,详见注释

public class TaskInfo {private String name;//文件名private String path;//文件路径private String url;//链接private long contentLen;//文件总长度/*** 迄今为止java虚拟机都是以32位作为原子操作,而long与double为64位,当某线程* 将long/double类型变量读到寄存器时需要两次32位的操作,如果在第一次32位操作* 时变量值改变,其结果会发生错误,简而言之,long/double是非线程安全的,volatile* 关键字修饰的long/double的get/set方法具有原子性。*/private volatile long completedLen;//已完成长度getter/setter省略复制代码

2.下载文件需要在子线程中进行,所以我们写一个类,实现Runnable接口,方便任务的创建

public class DownloadRunnable implements Runnable {private TaskInfo info;//下载信息JavaBeanprivate boolean isStop;//是否暂停/*** 构造器* @param info 任务信息*/public DownloadRunnable(TaskInfo info) {this.info = info;}/*** 停止下载*/public void stop() {isStop = true;}/*** Runnable的run方法,进行文件下载*/@Overridepublic void run() {HttpURLConnection conn;//http连接对象BufferedInputStream bis;//缓冲输入流,从服务器获取RandomAccessFile raf;//随机读写器,用于写入文件,实现断点续传int len = 0;//每次读取的数组长度byte[] buffer = new byte[1024 * 8];//流读写的缓冲区try {//通过文件路径和文件名实例化FileFile file = new File(info.getPath() + info.getName());//实例化RandomAccessFile,rwd模式raf = new RandomAccessFile(file, "rwd");conn = (HttpURLConnection) new URL(info.getUrl()).openConnection();conn.setConnectTimeout(120000);//连接超时时间conn.setReadTimeout(120000);//读取超时时间conn.setRequestMethod("GET");//请求类型为GETif (info.getContentLen() == 0) {//如果文件长度为0,说明是新任务需要从头下载//获取文件长度info.setContentLen(Long.parseLong(conn.getHeaderField("content-length")));} else {//否则设置请求属性,请求制定范围的文件流conn.setRequestProperty("Range", "bytes=" + info.getCompletedLen() + "-" + info.getContentLen());}raf.seek(info.getCompletedLen());//移动RandomAccessFile写入位置,从上次完成的位置开始conn.connect();//连接bis = new BufferedInputStream(conn.getInputStream());//获取输入流并且包装为缓冲流//从流读取字节数组到缓冲区while (!isStop && -1 != (len = bis.read(buffer))) {//把字节数组写入到文件raf.write(buffer, 0, len);//更新任务信息中的完成的文件长度属性info.setCompletedLen(info.getCompletedLen() + len);}if (len == -1) {//如果读取到文件末尾则下载完成Log.i("tag", "下载完了");} else {//否则下载系手动停止Log.i("tag", "下载停止了");}} catch (IOException e) {e.printStackTrace();Log.i("tag",e.toString());}}
}复制代码

3.任务开始/停止和进度回调

public class MainActivity3 extends AppCompatActivity {private ProgressBar bar;//进度条private TaskInfo info;//任务信息private DownloadRunnable runnable;//下载任务//用于更新进度的Handlerprivate Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {//使用Handler制造一个200毫秒为周期的循环handler.sendEmptyMessageDelayed(1, 200);//计算下载进度int l = (int) ((float) info.getCompletedLen() / (float) info.getContentLen() * 100);//设置进度条进度bar.setProgress(l);if (l>=100) {//当进度>=100时,取消Handler循环handler.removeCallbacksAndMessages(null);}return true;}});@Overrideprotected void onDestroy() {//在Activity销毁时移除回调和msg,并置空,防止内存泄露if(handler != null){handler.removeCallbacksAndMessages(null);handler = null;}super.onDestroy();}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main3);//实例化任务信息对象info = new TaskInfo("aa.apk", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download/", "https://download.alicdn.com/wireless/taobao4android/latest/702757.apk");bar = (ProgressBar) findViewById(R.id.bar);//设置进度条的最大值bar.setMax(100);}/*** 开始下载按钮监听* @param view*/public void start(View view) {//创建下载任务runnable = new DownloadRunnable(info);//开始下载任务new Thread(runnable).start();//开始Handler循环handler.sendEmptyMessageDelayed(1, 200);}/*** 停止下载按钮监听* @param view*/public void stop(View view) {//调用DownloadRunnable中的stop方法,停止下载runnable.stop();runnable = null;//强迫症,不用的对象手动置空}
}复制代码

Q:为什么进度信息不用handler发送到主线程,而是直接从主内存中的TaskInfo获取下载进度?
A:单个线程任务确实可以用handler携带下载信息进行线程切换,但是我们过后会涉及到多线程下载,一个下载任务甚至可以达到128线程并发,这么多子线程“同时”向主线程传递消息,主线程压力太大会造成“掉帧”,也就是我们所说的卡顿,并且TaskInfo中所有属性的均具有原子性,不会出现线程安全问题。

Q:Handler是非静态的不会造成内存泄露吗?
A:不会,造成内存泄露的原因是Message持有Handler,Handler持有Activity,造成Message-Handler-Activity的引用链,导致在Activity销毁时无法被GC回收。但在Activity销毁时移除未处理的Message,这样就从源头上解决了内存泄露。

后记

再次强调,本人能力有限,难免有知识上的空缺或者疏漏,如有不足之处请告知!我会用业余时间继续更新,感谢您的阅读。

Android下载文件(一)下载进度断点续传相关推荐

  1. android下载通知栏,Android开发中实现下载文件通知栏显示进度条

    android开发中实现下载文件通知栏显示进度条. 1.使用asynctask异步任务实现,调用publishprogress()方法刷新进度来实现(已优化) public class myasync ...

  2. Android 下载文件并显示进度条

    2019独角兽企业重金招聘Python工程师标准>>> OK,上一篇文章讲了上传文件到服务端,并显示进度条 那么这边文章主要讲下载文件并显示进度条. 由于简单,所以只上传代码.还是需 ...

  3. aaynctask控制多个下载进度_AsyncTask用法解析-下载文件动态更新进度条

    1. 泛型 AysncTask Params:启动任务时传入的参数,通过调用asyncTask.execute(param)方法传入. Progress:后台任务执行的进度,若不用显示进度条,则不需要 ...

  4. new blob文件设置编码_前端下载文件amp;下载进度

    前端最基础的就是 HTML+CSS+Javascript.掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些.前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础 ...

  5. 实现在 .net 中使用 HttpClient 下载文件时显示进度

    在 .net framework 中,要实现下载文件并显示进度的话,最简单的做法是使用 WebClient 类.订阅 DownloadProgressChanged 事件就行了. 但是很可惜,WebC ...

  6. Android --- Retrofit 上传/下载文件扩展实现进度的监听

    本文使用okhttp作为client来做,其实说白了跟用okhttp做下载上传进度监听几乎一样,参考了这篇文章:Android OkHttp文件上传与下载的进度监听扩展 1. 首先我们写两个接口用来下 ...

  7. OC/Swift 技术 下载文件(断点续传 AFN下载文件 Alamofire下载文件 原生下载)(源码)

    一直觉得自己写的不是技术,而是情怀,一个个的教程是自己这一路走来的痕迹.靠专业技能的成功是最具可复制性的,希望我的这条路能让你们少走弯路,希望我能帮你们抹去知识的蒙尘,希望我能帮你们理清知识的脉络,希 ...

  8. 转:delphi用URLDownloadToFile下载文件,用进度条跟踪下载进度

    用URLDownloadToFile下载文件,如何用进度条跟踪下载进度 1:OnDownloadProgress  2:可有否具体的例子.  3:unit Unit1; interface uses ...

  9. 使用libcurl开源库和Duilib做的下载文件并显示进度条的小工具

    转载:http://blog.csdn.net/mfcing/article/details/43603525 转载:http://blog.csdn.net/infoworld/article/de ...

  10. C# Winform下载文件并显示进度条

    private void btnDown_Click(object sender, EventArgs e) { DownloadFile("http://localhost:1928/We ...

最新文章

  1. 图片的奇怪Cache_MISS原因!
  2. 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)
  3. 深度学习笔记之使用Faster-Rcnn进行目标检测 (实践篇)
  4. 大数据与数据挖掘考试题_2017-2019年全国Ⅱ卷高考考点数据分析(理综合)
  5. Hibernate中session的get方法和load方法的区别
  6. LeetCode 1618. 找出适应屏幕的最大字号(二分查找)
  7. php intval trim,php数据入库前清理 注意php intval与mysql的int取值范围不同_PHP教程
  8. 使用Docker-Compose安装GitLab服务器
  9. VB6 Socket编程
  10. KindEditor使用
  11. 智慧城市网络安全建设框架及实践
  12. 五年饮冰,难凉热血”,一名专科生的求学历程
  13. 高德地图获取城市所有小区的POI
  14. QML 语法(Syntax)
  15. opencv实现眼动检测【胡子哥哥】
  16. A Surface Defect Detection Method Based on Positive Samples
  17. 禁用右键 回车 ESC 和 ALT+F4组合建
  18. php 斐多纳契数列,菲波纳契数列对股市的影响
  19. 小程序textarea字体错位
  20. The Codeless Code: Case 5 Void(void本质是什么)

热门文章

  1. ScrollView中嵌套ListView控件,数据无法显示完全
  2. MaterialImageView
  3. 【技术贴】虚拟机 VMware win7 win8网卡驱动下载 解决虚拟机不识别网卡没有本地连接...
  4. 动态加载flex皮肤.
  5. 「APIO2018」选圆圈
  6. 爱上MVC~为Html.EditorForModel自定义模版
  7. 娃哈哈信息部李钒助阵FBS2017 共探食品饮料信息化之路
  8. 小程序学习---开启小程序之旅(项目、配置、页面、数据绑定)
  9. 微服务、分布式、云架构构建电子商务平台
  10. 祝贺自己itpub和csdn双双荣获专家博客标题