Bitmap加载大图优化之位图重采样与Bitmap缓存Lru算法分析

为什么要优化Bitmap

    安卓加载图片一般会用到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方法,设置采样比例,
例如将2014*1536的图像(ARGB_8888方式加载占用内存约为2014x1536x4字节,约为12M)但是改变inSampleSize的值为4时,
则将产生约512*384大小的位图(宽高同时除以4,这样占用内存不足1M),ARGB_8888占用4个字节,ARGB_6556占用2字节


/****
     *   官方 计算位图采样比例 计算公式
     * @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);
    }

在这里我解释一下options.inJustDecodeBounds,这个属性接收一个boolean类型参数,如果传入false,则代表,不加载图片,只得到图片的宽高属性(这样就可以加载之前进行缩放工作),如果传入true,就代表加载图片

3.因为加载图片属于耗时操作,一般异步进行,使用asyncTask进行具体加载的逻辑操作

 //异步加载缓存,三个参数,第一个可以看做图片的url,第二个是加载过程的进度值,第三个是后台执行加载完毕后的返回结果
    class BitmapTask extends AsyncTask<Integer,Integer,Bitmap>{
        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);
        }
    }

4.单单进行简单的缩放优化是不够的,接下来还要在此基础上进行图片缓存操作--------缓存位图(采样测试域Lru算法分析),名字比较高大上,其实就是缓存呗(LRU就是Least Recently Used 近期最少使用算法),其实现原理就是,最近被引用的对象保存在一个强引用LinkedHashMap中,当缓存超过了其指定大小时,会释放最近很少使用的内存对象。

1>首先,我在前面说过,单个activity的内存基线是16M,我们一般使用内存的1/8作为缓存

2>既然是基于内存比例进行缓存的,首先我们获得到activity总的内存

//获取当前activity内存大小
        private LruCache<String,Bitmap> 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);         //设置缓存区的容量

3>然后定义两个方法,一个用于写入缓存,一个用于取出缓存

    //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<String, Bitmap> 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<Integer, Integer, Bitmap> {
        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);
        }
    }


}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.allan.bitmap.MainActivity">
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher" />
    <Button
        android:id="@+id/button"
        android:onClick="loadImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载图片" />
</LinearLayout>




猜你喜欢

转载自blog.csdn.net/Allan_Bst/article/details/72859318