本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢
导读:
1.http关于下载的相关字段.
2.Android多线程下载的原理.
3.Android上面进行Http请求下载的需要解决的一些问题.
(1)Transfer-Encoding
(2)content-length
(3)range
Transfer-Encoding,是一个HTTP头部字段,字面意思是传输编码.
Transfer-Encoding则是用来改变报文格式,Transfer-Encoding在HTTP1.1中,只定义了一种传输编码:分块编码(chunked).
Transfer-Encoding:chunked
而Transfer-Encoding:chunked表示输出的内容长度不能确定,普通的静态页面,图片之类的基本上都用不到这个,但是像动态的一些界面就会用到这个字段,因为无法确定其大小只能进行分块传输.
但是在Android的多线程下载中,出现Transfer-Endcoding关键字的时候,往往都是确定不了实体的长度,所以在响应头中拿不到content-length关键字,因而就无法进行多线程下载.
表示实体主体的大小,单位是字节,但是在Transfer-Encoding:chunck出现时,一般不会出现此关键字.
content-length:2030
对于只需要获取部分资源的范围请求,包含首部字段Range即可告知服务器资源的指定范围,上面事例表示请求从第5001字节至第10000字节的资源.
注意以下这种情况是请求从5001字节到文件最大长度数据.
Range:5001-
接收到附带Range首部字段请求的服务器,会在处理请求之后返回状态码为206 Partial Content的相应.
无法处理该范围请求时,则会返回状态码200OK的响应及全部资源.
Range:bytes = 5001-10000
1.通过创建线程池来维护多线程,注意当前线程数量超过corePoolSize数量时,会形成阻塞.
2.根据content-length来确定各个子线程的分段请求区域.(Range),
正常如果在做多线程下载的时候,服务器会把数据大小和url都从请求中返回直接一次请求就可以.
3.使用RandomAccessFile在分段区域位置读写数据,实现文件的拼接.
(1)创建线程池的代码
private static final ThreadPoolExecutor sThreadPool =
new ThreadPoolExecutor(
MAX_THREAD,
MAX_THREAD,
60,
TimeUnit.MILLISECONDS,
new SynchronousQueue(), new ThreadFactory() {
private AtomicInteger mInteger = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "download thread # " + mInteger.getAndIncrement());
return thread;
}
});
(2)当拿到需要进行多线程下载的数据时,根据content-length来确定各个子线程分段请求区域
private void processDownload(String url, long length, DownloadCallback callback) {
//100 2 50 0-49 50-99
long threadDownloadSize = length / CORE_THREAD;
for (int i = 0; i < CORE_THREAD; i++) {
long startSize = i * threadDownloadSize;
long endSize = 0;
if (i == CORE_THREAD - 1) {
endSize = length - 1;
} else {
endSize = (i + 1) * threadDownloadSize - 1;
}
Log.d("线程详细数据", "第" + i + "个线程 " + "startSize : " + startSize + "endSize : " + endSize);
sThreadPool.execute(new DownloadRunnable(startSize, endSize, url, callback));
}
}
(3)使用RandomAccessFile进行IO操作,因为RandomAccessFile支持任意位置操作读写数据.
注意在每次使用RandomAccessFile进行IO操作的时候,需要制定操作文件的位置
//mStart---->从什么位置开始写入文件
randomAccessFile.seek(mStart);
核心代码
File file = FileStorageManager.getInstance().getFileByName(mUrl);
try {
//rwd读写模式
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rwd");
//对文件制定偏移位置.
randomAccessFile.seek(mStart);
byte[] buffer = new byte[1024 * 500];
int len;
InputStream inStream = response.body().byteStream();
while ((len = inStream.read(buffer, 0, buffer.length)) != -1) {
randomAccessFile.write(buffer, 0, len);
}
mCallBack.success(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
在Android中,文件一般储存在SD卡中,但是有些手机不支持,所有会做出判断如果不支持就存储在缓存目录中:
在存之前先加入读写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
核心代码
/**
* 生成文件,并储存
*
* @param url
* @return
*/
public File getFileByName(String url) {
File parent;
//判断SD卡储存是否可用
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
parent = mContext.getExternalCacheDir();
} else {
//不可用就存入缓存目录
parent = mContext.getCacheDir();
}
String fileName = MD5Utils.generateCode(url);
File file = new File(parent, fileName);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
通常来说检验文件是否受损的方法,是比较文件所有内容的MD5值是否一样,来确定文件是否受损或者改动.这时候服务端会有一个完好的文件的MD5在下载后进行比对.
这就在存储文件之前需要检查存储空间是否足够.这里提供代码参考:
获取SD卡可用空间
void readSDCard() {
String state = Environment.getExternalStorageState();
if(Environment.MEDIA_MOUNTED.equals(state)) {
File sdcardDir = Environment.getExternalStorageDirectory();
StatFs sf = new StatFs(sdcardDir.getPath());
long blockSize = sf.getBlockSize();
long blockCount = sf.getBlockCount();
long availCount = sf.getAvailableBlocks();
Log.d("", "block大小:"+ blockSize+",block数目:"+ blockCount+",总大小:"+blockSize*blockCount/1024+"KB");
Log.d("", "可用的block数目::"+ availCount+",剩余空间:"+ availCount*blockSize/1024+"KB");
}
}
获取系统内部的存储空间
void readSystem() {
File root = Environment.getRootDirectory();
StatFs sf = new StatFs(root.getPath());
long blockSize = sf.getBlockSize();
long blockCount = sf.getBlockCount();
long availCount = sf.getAvailableBlocks();
Log.d("", "block大小:"+ blockSize+",block数目:"+ blockCount+",总大小:"+blockSize*blockCount/1024+"KB");
Log.d("", "可用的block数目::"+ availCount+",可用大小:"+ availCount*blockSize/1024+"KB");
}
StatFs获取的都是以block为单位的
这里我解释一下block的概念:
1.硬件上的"block size", 应该是"sector size",Linux的扇区大小是512byte.
2.有文件系统的分区的"block size", 是"block size",大小不一,可以用工具查看.
3.没有文件系统的分区的"block size",也叫“block size”,大小指的是1024 byte.
4.Kernel buffer cache 的"block size", 就是"block size",大部分PC是1024.
5.磁盘分区的"cylinder size",用fdisk可以查看。
我们这里的"block size"是第二种情况,一般SD卡都是fat32的文件系统,"block size"是4096byte.
这块主要得到当前的各个线程下载量之和然后和总长度作比较得到下载进度的百分比.更新进度的方式有很多,Handler,广播很多种实现方式.
使用RandomAccessFile即可实现分段数据,随时插入.完成了文件的拼接.
HTTP 协议中的 Transfer-Encoding
https://imququ.com/post/transfer-encoding-header-in-http.html
Android http协议实现文件下载
http://www.cnblogs.com/duanxz/p/3514781.html
Java IO的RandomAccessFile的使用
http://blog.csdn.net/czplplp_900725/article/details/37809579
Java RandomAccessFile用法
http://blog.csdn.net/akon_vm/article/details/7429245
Android:StatFs类 获取系统/sdcard存储空间信息
http://www.cnblogs.com/zhangs1986/p/3251171.html