Android-Bitmap的简单使用及其优化

在日常开发中不可避免的都会遇到图片展示的功能,而通常都会使用到Bitmap,且每个程序都有相应的最大运行内存;由于Bitmap所消耗的内存相当的大(超出程序最大运行内存)在加载多、大图的时候非常容易引起OOM。。这篇文章简单介绍Bitmap的优化以及内存的计算方式。

如何获取我们当前程序的最大运行内存?

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d("TAG", "Max memory is " + maxMemory + "KB");

1.Bitmap使用及其优化

常见的加载方法

public static Bitmap decodeFile(String pathName) {}  //从文件中加载

public static Bitmap decodeResource(Resources res, int id) { } //从资源文件中加载

public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {} //从byte[]中加载

public static Bitmap decodeStream(@Nullable InputStream is) {}  //从输入流中加载

 public static Bitmap decodeFileDescriptor(FileDescriptor fd) {}  //从FileDescriptor 中加载

之前的文章中有说明相同图片在不同drawable文件夹下对用的宽高也不同,那是因为内部加载时进行了缩放,源码:

if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
    const int density = env->GetIntField(options, gOptions_densityFieldID);
    const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
    const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
    if (density != 0 && targetDensity != 0 && density != screenDensity) {
        scale = (float) targetDensity / density;
    }
}
...
int scaledWidth = decoded->width();
int scaledHeight = decoded->height();

if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}
...
if (willScale) {
    const float sx = scaledWidth / float(decoded->width());
    const float sy = scaledHeight / float(decoded->height());
    bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);
    bitmap->allocPixels(&javaAllocator, NULL);
    bitmap->eraseColor(0);
    SkPaint paint;
    paint.setFilterBitmap(true);
    SkCanvas canvas(*bitmap);
    canvas.scale(sx, sy);
    canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
}

可以看出Bitmap 最终绘制出来后会经过一个scale操作,scale比例=设备分辨率/资源目录分辨率

Bitmap所占用内存的加载公式

  1. memorySize=横像素x竖像素*每个像素字节数
  2. memorySize=宽x高x(设备分辨率/资源目录分辨率)平方x每个像素字节数

获取设备分辨率

  int densityDpi = getResources().getDisplayMetrics().densityDpi;

资源目录分辨率相关见:https://blog.csdn.net/qq_34341338/article/details/86706751

其中每个像素所代表的字节数根据BitmapFactory.Option.inPreferredConfig 参数决定:

参数字节数
ALPHA_81
RGB_5652
ARGB_8888(默认)4
RGBA_F168

根据上面的公式,我们可以有以下优化方式:

  • 调整inPreferredConfig参数,改变像素对应的字节数
  • 调整图片所在的资源目录,选择合适的资源目录
  • 通过缩放图片的宽高

接下来展示如何优化Bitmap内存大小:

  • 第二种方式参照自己手机的dpi与对应的drawable文件夹的dpi即可
  • 方式一和三通过一个小案例来进行实现(自己封装成一个工具类)

BitmapUtil,java工具类

package com.example.administrator.drawabletest;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * 创建时间: 2019/2/15 16:00
 * 描述:    TODO
 */
@SuppressWarnings("unused")
public class BitmapUtil {
    /**
     * 计算缩放比率
     *
     * @param options BitmapFactory.Options
     * @param reqW    压缩后的宽
     * @param reqH    压缩后的高
     * @return 缩放比例
     */
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqW, int reqH) {
        if (options == null) return 1;
        //源图片的宽高
        final int sourceW = options.outWidth;
        final int sourceH = options.outHeight;
        //缩放比例
        int sampleSize = 1;
        //原图宽高大于需要的宽高
        if (sourceW > reqW || sourceH > reqH) {
            //计算缩放比率
            final int wRatio = Math.round((float) sourceW / (float) reqW);
            final int hRatio = Math.round((float) sourceH / (float) reqH);
            //选择小的缩放比率,保证最终图片宽高大于等于目标宽高
            sampleSize = wRatio > hRatio ? hRatio : wRatio;
        }
        return sampleSize;
    }


    /**
     * 获取缩放后的图片
     *
     * @param options   BitmapFactory.Options
     * @param resources Resources
     * @param resId     资源ID
     * @param reqW      目标宽
     * @param reqH      目标高
     * @return 所需Bitmap
     */
    public static Bitmap decodeBitmapFromResource(BitmapFactory.Options options, Resources resources, int resId, int reqW, int reqH) {
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = true;  //禁止为Bitmap分配内存
        //获取图片大小等信息,不生成像素数据
        BitmapFactory.decodeResource(resources, resId, options);
        //计算缩放比率
        options.inSampleSize = calculateInSampleSize(options, reqW, reqH);
        //再次解析图片
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, resId, options);
    }
}

使用

package com.example.administrator.drawabletest;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;

public class BitmapActivity extends AppCompatActivity {
    private ImageView ivImage;
    private static final String TAG = "wdl";
    private TextView tvOption;
    int on = 4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bitmap);
        ivImage = findViewById(R.id.iv_image);
        tvOption = findViewById(R.id.tv_option);
//        优化方法一   改变config
//        options.inPreferredConfig = Bitmap.Config.RGB_565;
//
//        //优化方式二  选择合适的资源文件目录
//
//        优化方式三  缩放图片
        BitmapFactory.Options options = new BitmapFactory.Options();

//        options.inJustDecodeBounds = true;  //禁止为Bitmap分配内存
//        BitmapFactory.decodeResource(getResources(), R.drawable.bit, options);
//        options.inJustDecodeBounds = false;
        //高效加载大图避免OOM
        Bitmap bit = BitmapUtil.decodeBitmapFromResource(options, getResources(), R.drawable.bit, 100, 100);

        ivImage.setImageBitmap(bit);
        int w = bit.getWidth();
        int width = options.outWidth;
        int h = bit.getHeight();
        int height = options.outHeight;

        int densityDpi = getResources().getDisplayMetrics().densityDpi;
        int inDensity = options.inDensity;
        if (options.inPreferredConfig == Bitmap.Config.RGB_565)
            on = 2;
        else if (options.inPreferredConfig == Bitmap.Config.ARGB_8888)
            on = 4;
        //像素*4  ARGB_8888
        int memorySizeMethod1 = w * h * on;
        int memorySizeMethod2 = (int) (width * height * Math.pow(densityDpi / inDensity, 2) * on);

        StringBuilder builder = new StringBuilder();
        builder.append("w:").append(w).append("\n")
                .append("h:").append(h).append("\n")
                .append("memorySizeMethod1:").append(memorySizeMethod1).append("\n")
                .append("memorySizeMethod2:").append(memorySizeMethod2).append("\n")
                .append("config:").append(options.inPreferredConfig).append("\n")
                .append("资源目录分辨率-inDensity:").append(inDensity).append("\n")
                .append("inScreenDensity:").append(options.inScreenDensity).append("\n")
                .append("inTargetDensity:").append(options.inTargetDensity).append("\n")
                .append("设备分辨率-densityDpi:").append(densityDpi).append("\n")
                .append("dpiX:").append(getResources().getDisplayMetrics().xdpi).append("\n")
                .append("dpiY:").append(getResources().getDisplayMetrics().ydpi).append("\n");
        tvOption.setText(builder.toString());
        //Log.e(TAG, "w: " + w + " h: " + h + " " +  + " memorySize:" + w * h * 4);

    }
}

其中的注释都比较完整,不一一进行说明。

大致流程:
1.在加载图片到内存之前先计算图片的一系列值赋值给BitmapFactory.Options.
2.在改变inPreferredConfig
3.计算缩放比例并赋值给options.inSampleSize
4.重新加载进内存并返回Bitmap

其中BitmapFactory.Options options起到很重要的作用,我们来看一下它的一些参数:

参数含义
inPreferredConfig设置解码时图片压缩的色彩质量参数
inSampleSize尺寸压缩的比例
inJustDecodeBoundstrue:只加载图片相关信息,不加载bitmap到内存中,false:重新加载返回bitmap
outHeight图片高
outWidth图片宽
inPreferredConfig显示精度

参考自:
https://blog.csdn.net/guolin_blog/article/details/9316683
https://www.jianshu.com/p/3f6f6e4f1c88
https://blog.csdn.net/wxl_344600977/article/details/69398823

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值