Bitmap常见优化

1、图片与内存

① 一张图片占用的内存 = 图片长度(单位像素) *图片宽度(单位像素) *单位像素占用的字节数,这里的单位像素占用的字节数则由BitmapFactory.OptionsinPreferredConfig变量来决定的,变量inPreferredConfigBitmap.Config类型,取值如下:

ALPHA_8

只有透明没有RGB,一个像素占一个字节

ARGB_4444

透明、RGB个占4位,共16位即2个字节

ARGB_8888

透明、RGB个占4位,共32位即4个字节,Bitmap的默认格式

RGB_565

没有透明即不支持透明和半透明,R5位,G6位,B5位,共16位即2个字节,通过Resources来取得一张图片时就是以该格式来创建BitmapAndroid4.0之后该选项无效,即便设置为该值系统依然用ARGB_8888来构造

② Dalvik内存:每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例;

③ 通过dumpsys meminfo命令可以查看一个进程的内存使用情况,当然也可以通过它来观察我们创建或销毁一张BitMap图片内存的变化,从而推断出图片占用内存的大小;

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);
        // 然后算一下我们想要的最终的属性
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // 在decode的时候 别忘记直接 把这个属性改为false 否则decode出来的是null
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
    public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // 先从options 取原始图片的 宽高
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            //一直对这个图片进行宽高 缩放,每次都是缩放1倍,然后这么叠加,当发现叠加以后 也就是缩放以后的宽或者高小于我们想要的宽高
            //这个缩放就结束 跳出循环 然后就可以得到我们极限的inSampleSize值了。
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
然后调用已经写好的工具方法

iv=(ImageView)this.findViewById(R.id.iv);

iv.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.drawable.gg,20,20));

3、正确加载Bitmap(这里以ListView为案例)

首先,请求网络来获取JSON数据并解析JSON数据后填充Adapter,Adapter只要要显示的条目的数量,Adapter还知道每个条目对应远程图片的URL,因此,请求远程图片并展示图片的业务逻辑不能写在Adapter中而应该写在ListView的getView方法中,另外把网络请求的业务逻辑写在getView中的另外一个好处就是当必须要请求网络的时候再去请求网络资源;

这里用本地资源来模仿远程资源

其次,在getView方法中要复用convertView对象(这里的convertView暂定是ImageView),因此我们面对一个问题:同一个convertView要显示不同的Bitmap,因此convertView是固定不变的,里面的Bitmap是不断变化的,因此应该把请求网络图片并显示图片的业务逻辑应该封装到Bitmap中,基于此设想编写如下代码:


步骤一:自定义AsyncDrawable,其内部持有AsyncTask,该AsyncTask负责请求网络资源并展示网络资源
class AsyncDrawable extends BitmapDrawable {
    private final WeakReference bitmapWorkerTaskReference;
    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference(bitmapWorkerTask);
    }
                                                                 
    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

步骤二:自定义AsyncTask,用来封装请求网络资源并展示网络资源的业务逻辑

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;


public BitmapWorkerTask(ImageView imageView) {
// 用弱引用来关联这个imageview。大家一定要记住,弱引用是避免android 在各种callback回调里发生内存泄露的最佳方法!
//而软引用则是做缓存的最佳方法 两者不要搞混了!这里主要是ImageView关联到Activity,如果Activity被销毁了,那么ImageView
//同样就不存在了,那么这个Task就没有执行的必要了
imageViewReference = new WeakReference<ImageView>(imageView);
}


// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100);
}


@Override
protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
    return;
        }                                                                                                                             
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

步骤三:编写工具类

//在listview或者gridview的getview方法里 我們就可以直接調用這個方法了
public void loadBitmap(int resId, ImageView imageView) {
//如果取得的task为空 就代表这个iv是新的iv 不是从listview回收站里取的 就可以新建一个task 然后
//用这个task 去新建一个drawable。然后用这个新的imageview去set 这个drawable即可
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}


//从imageview里取得他的drawable。然后从取得的drawable里取得他的task
//这里实际上就可以看出来imageview-drawable-task是1对1的关系了
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
//如果drawable是AsyncDrawable子类,说明里面有任务了,否则说明该imageView是第一次被创建
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}


public static boolean cancelPotentialWork(int data, ImageView imageView) {
//获取imageView对应的任务
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
//如果这个task 不为空 就代表这个iv已经有task了 那这种情况
//任务为空,分2种情况:要么压根就没有,要么执行完毕然后通过WeakReference释放了
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;//这里的data,对应的是其在Adapter中排序序号
// 如果这个task还没有跑完 那就直接cancel这个task。因为没有跑完就肯定是iv 还没有设定值,所以直接cancel
//cancel以后就跳出这个括号 直接返回true了,等同于这个iv是一个新的iv 可以重新绑定新的task
if (bitmapData == 0 || bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// 如果Task已经跑完了 那就别绑定了,否则会错乱的。所以返回false把 这里返回false loadBitmap就什么都不做的。图形就从根本上
//不会错乱了。
return false;
}
}
// task为空的话就返回true了。
return true;
}

步骤四:调用方法
 
 在getView中调用loadBitmap方法

4、图片缓存

这里,无非就是内存、本地、网络,这里需要存储本地,因此别忘了在清单文件中申请SD权限,内存的话就用LruCache;

5、管理内存

这个要分成2个部分来讲。 在3.0 以前的版本bitmap的内存 就是各自存放的,唯一的区别就是2.2的时候 bitmap 还存在native里,而2.3 就一起存放在java heap里了。
我们那会释放bitmap内存的时候 都是调用recyle这个方法的。

那在3.0以后,因为bitmap 都在 java层的 heap中处理了,所以你要释放一个bitmap 只要将引用置为null 就行了 不需要如此麻烦,除此之外3.0以后的版本 还提供了一个很好用的参数 叫options.inBitmap。实际上总结起来就是,如果你使用了这个属性,那么使用这个属性的decode过程中 会直接参考 inBitmap 所引用的那块内存,,大家都知道 很多时候ui卡顿是因为gc 操作过多而造成的。

使用这个属性 能避免大内存块的申请和释放。带来的好处就是gc 操作的数量减少。这样cpu会有更多的时间 做ui线程,界面会流畅很多,同时还能节省大量内存!
通俗来讲,就是多个Bitmap对象共用一块内存,就是拿一个酒瓶多次去灌酒,瓶子还是一个,空间还是同一个,就是内容不一样了

经查看,此时占用的内存大概是11mb,然后注释掉options.inBitmap = inBitmap2;发现占用内存暴增到18mb。


6、inBitmap注意事项

经查看,此时占用的内存大概是11mb,然后注释掉options.inBitmap = inBitmap2;发现占用内存暴增到18mb。
简单来说 就是4.4 以前 你要使用这个属性 那图片大小必须一样,但是4.4 以后只要decode的图片 比inBitmap的图片要小 就可以使用这个属性了。
但是这个属性在使用的时候一定要当心:不同的imageview 使用的scaletype 不同,但是你这些不同的imageview的bitmap 在decode时候 如果都是引用的同一个inBitmap的话,这些图片会相互影响,所以大家一定要注意,使用inBitmap这个属性的时候 一定要小心小心再小心。



经查看,此时占用的内存大概是11mb,然后注释掉options.inBitmap = inBitmap2;发现占用内存暴增到18mb。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值