安卓加载图片一般会用到ImageView控件,然后用setImageBitmap()、setImageResource()等方法指定要显示的图片,这些方法最终都会调用到BitmapFactory.decode()方法来生成一个Bitmap进行显示,这样加载一些小图片没什么问题,但连续加载大图片的时候就会发生典型的OOM(Out Of Memory)问题,也就是内存溢出,安卓虚拟机呢为每个activity分配的内存基线是16M,所以,Bitmap显示大图优化是必要的
先放一个下面Demo的图(Linux录个gif不容易,将就着看吧)
1.Bitmap的缩放(位图重采样)
生成Bitmap都要通过BitmapFactory的decode()方法,使用此方法时可以传入一个解析参数BitmapFactory.Option来对控制解析过程,比如获得长宽、改变采样率、改变采样格式等。而对Bitmap的缩放就是通过改变采样率来实现的,具体操作如下
BitMapFactory.Option对象设置inSampleSize方法,设置采样比例,
/****
* 官方 计算位图采样比例 计算公式
* @param options //传入BitmapFactory.Options对象
* @param reqWidth //需要的宽
* @param reqHeigh //需要的高
* @return
*/
public int calculatorInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeigh){
int w = options.outWidth; //获取位图的原来的宽度
int h = options.outHeight; //获取位图的原来的高度
Log.e("TAG", "w==="+w + "h===="+h);
int inSampleSize = 1;
//判断尺寸,如果原图比所需要的图像大
if(w > reqWidth || h > reqHeigh) {
if(w > h) {
//按照高度缩放
inSampleSize = Math.round(((float) h)/(float)reqHeigh);
}else {
inSampleSize = Math.round(((float) w)/(float)reqWidth);
}
}
Log.e("TAG", "inSampleSize===="+inSampleSize); //打印缩放比例
return inSampleSize;
}
2.上面这个计算缩放比例的公式就是谷歌官方推荐的,接下来我们使用这个公式重新采样位图,具体的实现继续往下看
/**
* 位图重新采样
* @param res //资源文件 传入getResource()即可
* @param resid //资源的id eg:R.id.imageView
* @param reqWidth //想要得到的图片宽度
* @param reqHeigh //想要得到的图片高度
* @return
*/
public Bitmap decodeSampledBitmapFromResource(Resources res,int resid,int reqWidth,int reqHeigh){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; //只得到图像的宽高,不加载图像
BitmapFactory.decodeResource(res,resid,options);
options.inSampleSize = calculatorInSampleSize(options,reqWidth,reqHeigh); //进行图像比例采样计算
options.inJustDecodeBounds = false; //开始加载图像
return BitmapFactory.decodeResource(res,resid,options);
}
3.因为加载图片属于耗时操作,一般异步进行,使用asyncTask进行具体加载的逻辑操作
//异步加载缓存,三个参数,第一个可以看做图片的url,第二个是加载过程的进度值,第三个是后台执行加载完毕后的返回结果
class BitmapTask extends AsyncTask{
MainActivity activity;
ImageView imageView;
public BitmapTask(Activity activity,ImageView imageView) {
this.activity = (MainActivity) activity;this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),params[0],100,100);
addBitmapToCache(String.valueOf(params[0]),bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
activity.imageView.setImageBitmap(bitmap);
}
}
1>首先,我在前面说过,单个activity的内存基线是16M,我们一般使用内存的1/8作为缓存
2>既然是基于内存比例进行缓存的,首先我们获得到activity总的内存
//获取当前activity内存大小
private LruCache lruCache; //定义LruCache集合,第一个参数为缓存时的key,第二个参数为缓存的Bitmap
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int activityMem = activityManager.getMemoryClass();
Log.e("TAG", "activityMem======"+activityMem);
final int cacheSize = activityMem/8 * 1024 *1024; //字节 1/8的内存作为缓存的大小
lruCache = new LruCache<>(cacheSize); //设置缓存区的容量
//LruCache缓存图片 缓存大小的定义,一般为当前activity的1/8
//从缓存中取出bitmap
public Bitmap getBitmapFromCache(String key){
return lruCache.get(key);
}
//从缓存中添加bitmap
public void addBitmapToCache(String key,Bitmap bitmap){
if(getBitmapFromCache(key)== null) {
lruCache.put(key,bitmap);
}
}
1.MainActivity.java
package com.example.allan.bitmap;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.util.LruCache;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
/***
* 有效处理较大的位图之位图重新采样
* BitMapFactory
* options.inJustDecodeBounds = true; 得到宽高,先不加载
* 1.BitMapFactory.Option对象设置inSampleSize方法,设置采样比例,例如将2014*1536的图像使用inSampleSize的
* 值为4,则将产生约512*384大小的位图(同时除以4),ARGB_8888占用4个字节,ARGB_656占用2字节
* 2.使用2的幂数设置inSampleSize的值可能更快,采用合适计算可能会更节省内存
*
* 3.缓存位图-------采样测试域Lru算法分析
* (1)内存缓存 LRU是Least Recently Used 近期最少使用算法
* 最近被引用的对象保存在一个强引用 LinkedHashMap中,在缓存超过了其指定大小,会释放最近很少使用的内存对象
* (过去推荐使用SoftReference和WeakReference)
* (2)
*
*/
public class MainActivity extends AppCompatActivity {
private android.widget.ImageView imageView;
private android.widget.Button button;
private LruCache lruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.button = (Button) findViewById(R.id.button);
this.imageView = (ImageView) findViewById(R.id.imageView);
//获取当前activity内存大小
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int activityMem = activityManager.getMemoryClass();
Log.e("TAG", "activityMem======" + activityMem);
final int cacheSize = activityMem / 8 * 1024 * 1024; //字节 1/8的内存作为缓存的大小
lruCache = new LruCache<>(cacheSize);
}
/**
* 位图重新采样
*
* @param res
* @param resid
* @param reqWidth
* @param reqHeigh
* @return
*/
public Bitmap decodeSampledBitmapFromResource(Resources res, int resid, int reqWidth, int reqHeigh) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; //只得到图像的宽高,不加载图像
BitmapFactory.decodeResource(res, resid, options);
options.inSampleSize = calculatorInSampleSize(options, reqWidth, reqHeigh); //进行图像比例采样计算
options.inJustDecodeBounds = false; //开始加载图像
return BitmapFactory.decodeResource(res, resid, options);
}
/****
* 官方 计算位图采样比例 计算公式
* @param options
* @param reqWidth //需要的宽
* @param reqHeigh //需要的高
* @return
*/
public int calculatorInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeigh) {
int w = options.outWidth; //获取位图的原来的宽度
int h = options.outHeight; //获取位图的原来的高度
Log.e("TAG", "w===" + w + "h====" + h);
int inSampleSize = 1;
//判断尺寸,如果原图比所需要的图像大
if (w > reqWidth || h > reqHeigh) {
if (w > h) {
//按照高度缩放
inSampleSize = Math.round(((float) h) / (float) reqHeigh);
} else {
inSampleSize = Math.round(((float) w) / (float) reqWidth);
}
}
Log.e("TAG", "inSampleSize====" + inSampleSize);
return inSampleSize;
}
//加载图片
public void loadImage(View view) {
loadBitmap(R.mipmap.a, imageView);
}
//点击按钮加载图片的具体实现
private void loadBitmap(int resId, ImageView imageView) {
String key = String.valueOf(resId); //将图片的路径作为key
Bitmap bitmap = getBitmapFromCache(key); //先从缓存中获取bitmap
if (bitmap != null) { //如果缓存中有bitmap
imageView.setImageBitmap(bitmap); //将图片放入缓存
} else {
Log.e("TAG", "没有发现BitMap缓存!!!!!");
new BitmapTask(MainActivity.this, imageView).execute(resId);
}
}
//LruCache缓存图片 缓存大小的定义,一般为当前activity的1/8
//从缓存中取出bitmap
public Bitmap getBitmapFromCache(String key) {
return lruCache.get(key);
}
//从缓存中添加bitmap
public void addBitmapToCache(String key, Bitmap bitmap) {
if (getBitmapFromCache(key) == null) {
lruCache.put(key, bitmap);
}
}
//异步加载缓存
class BitmapTask extends AsyncTask {
MainActivity activity;
ImageView imageView;
public BitmapTask(Activity activity, ImageView imageView) {
this.activity = (MainActivity) activity;
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100);
addBitmapToCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
activity.imageView.setImageBitmap(bitmap);
}
}
}