1,图片加载
1)Bitmap
Bitmap在Android中指的是一张图片,可以是png、jpg等常见图片格式。
2)加载过程
BitmapFactory类提供了4类方法:
–decodeFile
从文件系统加载出一个Bitmap对象。
间接调用了decodeStream方法。
–decodeResource
从资源加载出一个Bitmap对象。
间接调用了decodeStream方法。
–decodeStream
从输入流加载出一个Bitmap对象。
–decodeByteArray
从字节数组加载出一个Bitmap对象。
3)OOM
由于Bitmap的特殊性以及Android对单个应用所施加的内存限制,这导致加载Bitmap的适合很容易出现内存溢出:java.lang.OutofMemoryError:bitmap size exceeds VM budget
4)如何有效地加载一个Bitmap?
采用BitmapFactory.Options加载所需尺寸的图片。
通过ImageView来显示图片时,ImageView可能没有图片的原始尺寸那么大,可以通过BitmapFactory.Options按一定的采样率来加载缩小后的图片。这样可以降低内存占用从而在一定程度上避免OOM,提高Bitmap加载时的性能。
–inSampleSize参数 采样率
当值<=1时,采样后的图片为原始大小;
当值>1时,比如是2,那么采样后的图片其宽高为原图的1/2,像素为原图的1/4,占有内存为原图的1/4。注意:取值应该为2的指数,否则系统会自动以最接近的2的指数代替。
注意:
通过采样率可有效地加载图片,为了避免图片不必要的拉升失真,获取采样率应遵循以下流程:
①将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
(nJustDecodeBounds参数,值为true,BitmapFactory只解析图片的原始宽高,并不会加载图片,是轻量级操作
)
②从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
③根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize.
④将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
避免OOM的常用方法:
---BitmapFactory.Options.inPreferredConfig:将ARGB_8888改为RGB_565,改变编码方式,节约内存。
---BitmapFactory.Options.inSampleSize:缩放比例,可以参考Luban那个库,根据图片宽高计算出合适的缩放比例。
---BitmapFactory.Options.inPurgeable:让系统可以内存不足时回收内存。
5)占用内存大小
Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存.
//在Bitmap里有两个获取内存占用大小的方法。
---getByteCount()
API12 加入,代表存储 Bitmap 的像素需要的最少内存。
---getAllocationByteCount()
API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。
注:这里inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高
进行拉伸,进而改变Bitmap占用内存的大小。
在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大
小,getAllocationByteCount() 表示被复用 Bitmap真实占用的内存大小(即 mBuffer 的长度)。
6)性能优化
避免在Bitmap浪费过多的内存,使用压缩过的图片,也可以使用Fresco等库来优化对Bitmap显示的管理。
2,缓存策略
通过缓存策略,每次不需要在网上请求图片或者从存储设备中加载图片,这样就极大的提高了图片的加载效率以及产品的用户体验。
如何避免过多的流量消耗?—缓存
当程序第一次从网络加载图片后,就缓存到设备上,以后再用就不需要再重新获取,节省流量、提高用户体验。
目前常用的缓存算法是LRU(Lest Recently Used,最近最少使用),当缓存快满时,淘汰近期最少使用的缓存目标。采用LRU算法的缓存有2种:
1)LRU算法
①LruCache(least recentlly use,最少最近使用算法)
用于内存缓存。是线程安全的。
LruCache是Android3.1所提供的一个缓存类,为了兼容2.2版本,建议采用support-v4兼容包种提供的LruCache。
LruCache是一个泛型类,内部是一个LinkedHashMap以强引用的方式存储外界缓存对象,提供了get和put完成缓存的获取和添加操作。
为什么会选择LinkedHashMap呢?
这跟LinkedHashMap的特性有关,LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。
通过 LruCache
②DiskLruCache
用于存储设备缓存,即磁盘缓存。将缓存对象写入了文件系统。
不属于Android SDK的一部分,是官方文件的推荐。
DiskLruCache与LruCache原理相似,只是多了一个journal文件来做磁盘文件的管理。
2)图片的三级缓存
①概念
网络加载,不优先加载,速度慢,浪费流量
本地缓存,次优先加载,速度快
内存缓存,优先加载,速度最快
②为什么要使用三级缓存
每次启动app都从网络拉取图片,会消耗很多流量;
重复浏览一些图片时,每次都通过网络获取,浪费流量。
所以提出三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量。
③原理
首次加载 Android App 时,通过网络交互来获取图片,之后将图片保存至本地SD卡和内存中。
之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片。
3)ImageLoader
①概念
结合LruCache、DiskLruCache。
②功能
图片的同步/异步加载;
图片压缩;
内存缓存;
磁盘缓存;
网络拉取。
除此意外,还需要处理一些特殊的情况:
如ListView或者GridView中,View的复用造成的图片不同步问题。
3,列表的滑动流畅性
1)问题
ListView和GridView由于加载大量的子视图,当用户快速滑动时就容易出现卡顿现象。
2)解决
①在getView中执行了耗时操作
如直接在getView中加载图片。
解决方案:类似于ImageLoader实现的那样,通过异步的方式处理。
②控制异步任务的执行频率
如第一种方案中所述,在getView中通过异步加载图片,如果用户可以地频繁上下滑动,就会在一瞬间产生上百个异步任务,这些异步任务会造成线程池的拥堵并带来大量UI更新操作,从而造成卡顿。
解决方案:给ListView或者GridView设置setOnScrollListener,并在OnScrollListener的onScrollStateChanged方法中判断列表是否处于滑动状态,如果是则停止加载图片。
public void onScrollStateChanged(AbsListView view, int scrollState){
if(scrollState == OnScrollListener.SCROLL_STATE_IDLE){
mIsGridViewIdle = true;
mImageAdapter.notifyDataSetChanged();
}else{
mIsGridViewIdle = false;
}
}
然后再getView方法中,仅当列表静止时才能加载图片:
if(mIsGridViewIdle && mCanGetBitmapFromNetWork){
imageView.setTag(uri);
mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);
}
③开启硬件加速
大多数情况下,硬件加速可以解决莫名的卡顿问题。
设置:android:hardwareAccelerated="true"
可为Activity开启硬件加速 。
4)清除缓存
①场景
卸载时清除数据。
②目录介绍
i>getCacheDir()
用于获取:/data/data/(应用包名)/cache目录。
一般存放:临时缓存数据。
对应目录:设置->应用->应用详情里面的”清除缓存“选项
ii>getFilesDir()
用于获取:/data/data//files目录。
一般存放:长时间保存的数据。
对应目录:设置->应用->应用详情里面的”清除数据“选项
iii>getExternalFilesDir()
iv>getExternalCacheDir()
较优秀的程序都会专门写一个方法来获取缓存地址,如下所示:
public String getDiskCacheDir(Context context) {
String cachePath = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {//当SD卡存在或者SD卡不可被移除的时候
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return cachePath;
}
5)
4,demo
1)获取View对应的Bitmap
public static Bitmap getBitmapFromView(View v) {
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.RGB_565);
Canvas c = new Canvas(b);
v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
// Draw background
Drawable bgDrawable = v.getBackground();
if (bgDrawable != null)
bgDrawable.draw(c);
else
c.drawColor(Color.WHITE);
// Draw view to canvas
v.draw(c);
return b;
}
2)清除缓存
获取缓存:
CacheUtils.getIntense().getTotalCacheSize(context)
清除缓存:
CacheUtils.getIntense().clearAllCache(context);
注意:
使用时,注意传入的context级别,防止内存泄漏。为了安全,应该直接在application中清除缓存。
CacheUtils.java:
package com.rendu.bim.util.manager.cache;
import android.content.Context;
import android.os.Environment;
import java.io.File;
import java.math.BigDecimal;
public class CacheUtils {
public static CacheUtils intance = new CacheUtils();
public static CacheUtils getIntance() {
return intance;
}
/**
* 清除缓存
*/
public void clearAllCache(Context context) {
deleteDir(context.getCacheDir());
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
deleteDir(context.getExternalCacheDir());
}
}
/**
* 删除缓存文件
*
* @param dir
* @return
*/
private boolean deleteDir(File dir) {
if (dir != null && dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
return dir.delete();
}
public long getFolderSize(File file) throws Exception {
long size = 0;
try {
File[] fileList = file.listFiles();
for (int i = 0; i < fileList.length; i++) {
// 如果下面还有文件
if (fileList[i].isDirectory()) {
size = size + getFolderSize(fileList[i]);
} else {
size = size + fileList[i].length();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return size;
}
/**
* 格式化单位
*
* @param size
*/
public String getFormatSize(double size) {
double kiloByte = size / 1024;
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "KB";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "MB";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "GB";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
+ "TB";
}
public String getTotalCacheSize(Context context) throws Exception {
long cacheSize = getFolderSize(context.getCacheDir());
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
cacheSize += getFolderSize(context.getExternalCacheDir());
}
return getFormatSize(cacheSize);
}
}