教你如何在 Android 使用多线程下载文件

===============================================

前言

在 Android 日常开发中,我们会经常遇到下载文件需求,这里我们也可以用系统自带的 api `DownloadManager` 来解决这个问题,当然我们也可以自己来写。在这里我将教大家如何在 Android 使用多线程下载文件。

实现原理

  1. 获取目标文件的文件大小
  2. 根据线程的个数以及文件大小来分配每个线程下载文件的大小

    如:文件大小:9M 线程个数:3,那么每条线程下载的大小为 3M。

    在这里给出计算公式:blockSize=totalSize%countThread==0?totalSize/countThread:totalSize/countThread+1 ----blockSize 为每个线程下载的大小 totalSize 文件大小 countThread 线程个数

    3.开启线程下载(这里要处理比较多的事)

具体实现

1.获取文件的大小

这一步比较简单我直接给出代码:

	 URL url = null;
HttpURLConnection http = null;
try {
url = new URL(this.apk_url);
http = (HttpURLConnection) url
.openConnection();
http.setConnectTimeout(5 * 1000);
http.setReadTimeout(5 * 1000);
http.setRequestMethod("GET");
if (http.getResponseCode() == 200) {
this.filesize = http.getContentLength();//文件大小
} else {
this.filesize = -1;
}
} catch (Exception e) {
e.printStackTrace();
this.filesize = -1;
} finally {
http.disconnect();
}

2.分配线程

既然要对各个线程分配对应的下载大小,我们就有必要知道各个线程对应的信息,那么我么先来定义 bean 类来表示这些信息

package com.h.kidbot.download;
public class DownLoadInfo {
private int threadid;//线程id
private long startpos;//下载的起始位置
private long endpos;//下载的结束位置
private long block;//每条下载的大小
private long downpos;//该条线程已经下载的大小
private String downloadurl;//下载地址 public int getThreadid() {
return threadid;
} public void setThreadid(int threadid) {
this.threadid = threadid;
} public long getStartpos() {
return startpos;
} public void setStartpos(long startpos) {
this.startpos = startpos;
} public long getEndpos() {
return endpos;
} public void setEndpos(long endpos) {
this.endpos = endpos;
} public long getBlock() {
return block;
} public void setBlock(long block) {
this.block = block;
} public long getDownpos() {
return downpos;
} public void setDownpos(long downpos) {
this.downpos = downpos;
} public String getDownloadurl() {
return downloadurl;
} public void setDownloadurl(String downloadurl) {
this.downloadurl = downloadurl;
}
}

定义好了这个类我们就可以根据刚才获取的文件大小来分配单个线程文件下载的大小了,为了方便起见呢 我就设置一条线程吧!

 for (int i = 0; i < this.threadcount; i++) {
DownLoadInfo info = new DownLoadInfo();
long startpos = 0, endpos = 0;
if (i == this.threadcount - 1) {
startpos = i * block;
endpos = this.filesize - 1;
} else {
startpos = i * block;
endpos = (i + 1) * block - 1;
}
info.setBlock(block);
info.setDownpos(0);
info.setStartpos(startpos);
info.setEndpos(endpos);
info.setDownloadurl(this.apk_url);
info.setThreadid(i);
DownDbUtils.insert(this.context, info);
infos.add(info);
info = null;
}

得到每条线程对应的数据之后,我们就可以开启线程啦!下面的做法我和一般的不一样 因为我没有用到 RandomAccessFile 这个类,而是直接用 File + FileOutputStream 这两个类来实现的,原因呢 我发现 RandomAccessFile 这个类的性能非常的差,非常的差,非常的差!重要的是说三遍!因为这原因,我在我司的平板是下载文件的速度很慢很慢!都要哭了!

当然之后我了解了一下 可以用 RandomAccessFile+ nio 来提升文件的写入速度!!

好啦!现在开始介绍下载类啦

public class DownLoadThread extends Thread {
private String apkurl;//下载地址
private long startpos;//起始地址
private long endpos;//结束地址
private long downpos;//已经下载的大小
private String apkpath;//保存地址
private long block;//每块大小
private int threadid;//线程ID
private boolean finish = false; // 是否已经下载完成
private boolean error = false; // 是否出错
private Context context;
private DownLoader loader;
private int downstate;//下载状态
public static final int PAUSE = 2;//暂停
public static final int RUNNING = 1;//正在下载
public static final int STOP = 0;//停止 public DownLoadThread(Context context, String apkurl, long startpos, long endpos, long downpos, String apkpath, long block, int threadid, DownLoader loader) {
this.context = context;
this.apkurl = apkurl;
this.startpos = startpos;
this.endpos = endpos;
this.downpos = downpos;
this.apkpath = apkpath;
this.block = block;
this.threadid = threadid;
this.loader = loader;
this.downstate = RUNNING;
} public DownLoadThread() {
} @Override
public void run() {
File file=null;
FileOutputStream fout=null;
InputStream in = null;
if (downpos < block) {
try {
URL url = new URL(apkurl);
HttpURLConnection http = (HttpURLConnection) url
.openConnection();
http.setConnectTimeout(5 * 1000);
http.setReadTimeout(5 * 1000);
http.setRequestMethod("GET");
http.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x- shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
http.setRequestProperty("Accept-Language", "zh-CN");
http.setRequestProperty("Referer", url.toString());
http.setRequestProperty("Charset", "UTF-8");
http.setRequestProperty("Connection", "Keep-Alive");
long startPos = startpos + downpos;
long endPos = endpos;
http.setRequestProperty("Range", "bytes=" + startPos + "-");// 设置获取实体数据的范围
file=new File(apkpath);
if (file.length()>0){
fout=new FileOutputStream(file,true);
}else{
fout=new FileOutputStream(file);
}
byte[] bytes = new byte[2048];
int len = 0;
in = http.getInputStream(); LogUtils.e("开始");
while ((len = in.read(bytes, 0, bytes.length)) != -1) {
if (PAUSE == this.downstate || STOP == this.downstate) {
DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
break;
}
fout.write(bytes,0,len);
downpos += len;//已下载的大小
this.loader.setDownlength(len);
}
DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
if (!DeviceUtils.isNet(context)) {
this.finish = false;
this.downstate=PAUSE;
} else {
this.finish = true;
}
} catch (Exception e) {
DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
LogUtils.e(e.toString());
e.printStackTrace();
downpos = -1;
this.error = true;
this.finish = false;
} finally {
closeIO(in,fout);
}
}
}
//得到每条线程已经下载的大小 public long getDownpos() {
return downpos;
} public int getDownstate() {
return downstate;
} public void setDownstate(int downstate) {
this.downstate = downstate;
} //是否下载完成
public boolean isFinish() {
return finish;
} public void setFinish(boolean finish) {
this.finish = finish;
} //是否下载出错
public boolean isError() {
return error;
} public void setError(boolean error) {
this.error = error;
} public void setDownpos(long downpos) {
this.downpos = downpos;
} //关闭流
public static void closeIO(Closeable... closeables) {
if (null == closeables || closeables.length <= 0) {
return;
}
for (Closeable cb : closeables) {
try {
if (null == cb) {
continue;
}
cb.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

这里大家得了解下 http 中这个 Range 这个字段的含义:用户请求头中,指定第一个字节的位置和最后一个字节的位置,如(Range:200-300)! 这样就可以指定下载文件的位置了呢!到了这里核心的部分已经说完了!

其他

上面已经把核心的都说完了,其实还有其他的可以说呢:

  1. 实现断点下载(保存下载的长度,用数据库或者文件保存都可以)
  2. 多线程下载的管理 (需要读者实现管理器了)
  3. 可以把下载这个模块放到另外一个进程中,这样可以是主进程更加的流畅。当然这涉及到了进程见通信的问题啦
  4. 一般下载的时候都会有下载进度条,这里要注意下更新的频率的问题,在 listview 中更新太快会造成页面卡顿的哦!

教你如何在 Android 使用多线程下载文件的更多相关文章

  1. Android之——多线程下载演示样例

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46883927 一.概述 说到Android中的文件下载.Android API中明 ...

  2. java 多线程下载文件 以及URLConnection和HttpURLConnection的区别

    使用 HttpURLConnection 实现多线程下载文件 注意GET大写//http public class MultiThreadDownload { public static void m ...

  3. Android利用Http下载文件

    Android利用Http下载文件 一.场景 下载存文本文件和下载如mp3等大容量的文件 界面 二.代码编写 1.AndroidMainfest.xml中配置 主要是解决网络权限和写SDCard的权限 ...

  4. Python之FTP多线程下载文件之分块多线程文件合并

    Python之FTP多线程下载文件之分块多线程文件合并 欢迎大家阅读Python之FTP多线程下载系列之二:Python之FTP多线程下载文件之分块多线程文件合并,本系列的第一篇:Python之FTP ...

  5. Python之FTP多线程下载文件之多线程分块下载文件

    Python之FTP多线程下载文件之多线程分块下载文件 Python中的ftplib模块用于对FTP的相关操作,常见的如下载,上传等.使用python从FTP下载较大的文件时,往往比较耗时,如何提高从 ...

  6. 多线程下载文件,ftp文件服务器

    1: 多线程下载文件 package com.li.multiplyThread; import org.apache.commons.lang3.exception.ExceptionUtils; ...

  7. java 多线程下载文件并实时计算下载百分比(断点续传)

    多线程下载文件 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来 ...

  8. Android 通过SOCKET下载文件的方法

    本文实例讲述了Android通过SOCKET下载文件的方法.分享给大家供大家参考,具体如下: 服务端代码 import java.io.BufferedInputStream; import java ...

  9. java 网络编程基础 InetAddress类;URLDecoder和URLEncoder;URL和URLConnection;多线程下载文件示例

    什么是IPV4,什么是IPV6: IPv4使用32个二进制位在网络上创建单个唯一地址.IPv4地址由四个数字表示,用点分隔.每个数字都是十进制(以10为基底)表示的八位二进制(以2为基底)数字,例如: ...

随机推荐

  1. msado.tli

    // Created by Microsoft (R) C/C++ Compiler Version 10.00.40219.01 (d0b01b1b).//// e:\threadpool\mysq ...

  2. Python3 isidentifier() 方法

    描述 Python3 isidentifier() 方法用于判断字符串是否是有效的 Python 标识符,可用来判断变量名是否合法. 语法 isidentifier() 方法语法: S.isident ...

  3. unity, public+[HideInInspector] vs private

    下面写法不报错: [System.Serializable] public class CmyObj{ CA m_a; ... } public class XXX: MonoBehavior{ [H ...

  4. Django--缓存、信号、序列化

    一 Django的缓存机制 1.1 缓存介绍 1.缓存的简介 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的 ...

  5. PHP学习记录第一篇:Ubuntu14.04下LAMP环境的搭建

    最近一段时间会学习一下PHP全栈开发,将会写一系列的文章来总结学习的过程,以自勉. 第一篇记录一下LAMP环境的安装 0. 安装Apache Web服务器 安装之前先更新一下系统 sudo apt-g ...

  6. FreeRTOS基础知识

    前面一篇文章介绍了一些命名规范之类的基础知识,但是我觉得还缺少一定前言知识,就是裸机和操作系统有什么区别,为什么我们需要学freertos,因为招聘要求?那么为什么招聘网又会有这个要求呢?所以我们为什 ...

  7. php html 转义

    html_entity_decode($string);htmlentities($string);htmlspecialchars($string);htmlspecialchars_decode( ...

  8. TVS二极管的主要参数与选型

    TVS二极管的主要参数--转载 处理瞬时脉冲对器件损害的最好办法是将瞬时电流从敏感器件引开.TVS二极管在线路板上与被保护线路并联,当瞬时电压超过电路正常工作电压后,TVS二极管便发生雪崩,提供给瞬时 ...

  9. oozie4.3.0+sqoop1.4.6实现mysql到hive的增量抽取

    1.准备数据源 mysql中表bigdata,数据如下: 2. 准备目标表 目标表存放hive中数据库dw_stg表bigdata 保存路径为 hdfs://localhost:9000/user/h ...

  10. WEB 项目中JAVA取得WEBROOT物理路径

    http://wwwzhouhui.iteye.com/blog/504330 ———————————————————————————————————————————————————————————— ...