Android 多线程下载及其断点续传原理,BitMap高效加载大图,图片三级缓存原理(Lru算法原理)
一、多线程下载原理
多线程下载文件时,文件是被分成多个部分,是被不同的线程同时下载的,此时就需要每一条线程都分别需要一个记录点,和每个线程完成状态的记录。只有将所有线程的下载状态都出于完成状态时,才能表示文件下载完成。
二、断点续传原理
1、从字面意义上理解:
断点:线程停止的位置
续传:从停止的位置继续下载
2、从代码上理解:
断点:当前线程已经下载完成的数据长度
续传:向服务器请求上次线程停止的位置之后的数据
3、总原理:
每当线程停止时就把已经下载的数据长度写入记录文件,这段长度就是所需要的断点,当重新下载时,从记录的文件位置,通过设置不同的网络请求参数,向服务器请求上次线程停止的位置之后的数据。
4、文件下载
其实就是IO的读写,通过HttpUrlConnection进行网络请求,IO流读取文件并且慢慢下载到本地文件,期中,可以、利用HttpUrlConnection的setRequestProperty(String filed,String newValue)方法可以实现需要下载文件的一个范围。setRequestProperty("Range","bytes="+开始位置+"-"+结束位置) 利用这个方法时,他只能实现续传的一部分需求,并不能提供从指定的位置写入数据的功能,这时候就需要使用RomAccessFile来实现从指定位置给文件写入数据的功能。
三、多线程下载及其断点续传实现原理
1、首先获取要下载文件的长度,用来设置RomdomAccessFile(本地文件)的长度
2、实时保存文件的下载进度(此功能可以用数据库来实现)
3、中断后再次下载,读取进度,再从上次的下载进度继续下载,并在本地的文件续续写如。
获取文件长度:fileLength = HttpUrlConnection.getContentLength()
每条线程需要下载的大小 = fileLength / Thread_Num
BitMap高效加载大图:
1、 我们知道,在应用开发中显示图片时,往往很多原图是比较大的,而我们的手机设备内存是很宝贵的,是不需要在内存中加载一个与原图大小的图片的,否则很容易造成OOM,这就需要我们加载一个符合手机需要大小的图片,以此来减少内存加载图片时过多的内存消耗。
2、OOM造成的原因:
BitmapFactory提供了一系列加载图片的decode系列的方法,
1、 比如SD卡中的图片可以使用decodeFile方法
2、 网络上的图片可以使用decodeStream方法,
3、 资源文件中的图片可以使用decodeResource方法。
这些方法在加载大图时,都会创建bitmap对象,这些方法在构造位图时会尝试这分配内存,如果内存分配不好,很容易造成OOM异常,这就需要我们进行图片的压缩,首先来看图片的4种压缩格式。
分配内存:
1、每个应用程序分配的最大内存空间:Runtime.getRunntime().maxMemory()/1024
系统将应用内存的1/8作为图片的使用如果超出这个范围也会造成OOM异常
3、图片压缩格式(位数越高,越占用内存)
Alpha-8:表示8位的Alpha位图,即A=8,一个像素点占一个字节,他没有颜色,只有透明度。
ARGB-4444:表示16位的ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占 4+4+4+4=16 位,2个字节。
ARGB-8888:表示32位的ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占 8+8+8+8=32 位,4个字节。
RGB-565:表示16位的RGB位图,即R=5,G=6,B=5,一个像素点占 5+6+5=16 位,2个字节。
在这里需要注意,如果不进行压缩格式的设置,默认使用的是 ARGB-8888 ,每个像素点占4个字节
4、一张图片所占用的内存 = 图片长度分辨率 * 图片宽度分辨率 * 一个像素占的字节数 (默认单位是B)
1B = 1字节(B)
1024B = 1KB
1024KB = 1MB
知道图片的压缩格式之后,来看看他是如何进行压缩的。
5、每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的 outWidth、 outHeight 和 outMimeType 属性都会被赋值。这时我们就可以获取到 在加载图片之前图片的长宽的值和MIME类型从而根据情况对图片进行压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; (设置为true,表示不进行下载,只是为了获取原图宽高)
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
1、当inSampleSize(采样率)为1时,采样后的图片大小为原图大小;当inSampleSize(采样率)大于1时, 比如说2,那么采样后的图片宽和高均为原图大小的1/2,像素为原图的1/4,内存大小也就是原图的1/4。有一种特殊情况,当inSampleSize(采样率)小于1时,其作用相当于1,即无缩放效果。
2、知道缩放比例之后,我们就得正式开始下载图片,也就是把压缩后的图片进行下载。
再解析一次图片
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
这时Bitmap高效加载大图就算是完成了。但是我们在进行重复浏览数据的时候,总不可能每次都进行网络请求吧,例如:在使用ListView, GridView等这些大量加载view的组件时,如果没有合理的处理缓存,也会很容易造成OOM异常。或者重复浏览一些图片,这时如果每一次浏览都需要通过网络获取图片,那么将会非常流量。为了节省用户流量,提高图片加载效率,我们通常使用图片三级缓存策略,即通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费,这时候我们就得用到图片的三级缓存了。
图片的三级缓存:
图片的三级缓存,其中用到了LRU+SoftReference关于LRU算法。https://www.300168.com/yidong/show-2423.html
LruCach 源码地址https://www.cnblogs.com/liuling/archive/2015/09/24/2015-9-24-1.html
1、LRU全称是Least Recently Used,即最近最久未使用的意思。
2、LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
3、实现LRU:
1、用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。
2、利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。
3、利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在, 则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。对于第一种方法,需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。
4、使用LinkedHashMap实现LinkedHashMap底层就是用的HashMap加双链表实现的,而且本身已经实现了按照访问顺序的存储。此外,LinkedHashMap中本身就实现了一个方法 removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要重写该方法。即当缓存满后就移除最不常用的数。
5、LRU-K
LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。 只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。数据第一次被访问时,加入到历史访问列表,如果书籍在访问历史列表中没有达到K次访问,则按照一定的规则(FIFO,LRU)淘汰;当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列中删除,将数据移到缓存队列中,并缓存数据,缓存队列重新按照时间排序;缓存数据队列中被再次访问后,重新排序,需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即“淘汰倒数K次访问离现在最久的数据”。LRU-K具有LRU的优点,同时还能避免LRU的缺点,实际应用中LRU-2是综合最优的选择。由于LRU-K还需要记录那些被访问过、但还没有放入缓存的对象,因此内存消耗会比LRU要多。