Android性能优化:Bitmap优化

在日常开发的APP,大部分时候需要想用户展示图片信息,图片最终对应Android中的Bitmap对象。而对于APP端来说Bitmap又是一个比较麻烦的问题,主要表现在Bitmap是非常占用内存的对象,处理不当将导致APP运行卡顿甚至出现OOM。Google在其官方有针对Bitmap的使用专门写了一个专题Displaying Bitmaps Efficiently

一、主动释放Bitmap资源

当你确定这个Bitmap资源不会再被使用的时候(当然这个Bitmap不释放可能会让程序下一次启动或者resume快一些,但是其占用的内存资源太大,可能导致程序在后台的时候被杀掉,反而得不偿失),我们建议手动调用recycle()方法,释放其Native内存:

if(bitmap != null && !bitmap.isRecycled()){  
    bitmap.recycle(); 
    bitmap = null; 
}

调用bitmap.recycle之后,这个Bitmap如果没有被引用到,那么就会被垃圾回收器回收。如果不主动调用这个方法,垃圾回收器也会进行回收工作,只不过垃圾回收器的不确定性太大,依赖其自动回收不靠谱(比如垃圾回收器一次性要回收好多Bitmap,那么需要的时间就会很多,导致回收的时候会卡顿)。所以我们需要主动调用recycle。

二、主动释放ImageView的图片资源

由于我们在实际开发中,很多情况是在xml布局文件中设置ImageView的src或者在代码中调用ImageView.setImageResource/setImageURI/setImageDrawable等方法设置图像,下面代码可以回收这个ImageView所对应的资源:

 private static void recycleImageViewBitMap(ImageView imageView) {
        if (imageView != null) {
            BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
            rceycleBitmapDrawable(bd);
        }
    }

    private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
        if (bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            rceycleBitmap(bitmap);
        }
        bitmapDrawable = null;
    }

    private static void rceycleBitmap(Bitmap bitmap) {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
    }

三、主动释放ImageView的背景资源

如果你的ImageView是有Background,那么下面的代码可以释放他:

 public static void recycleBackgroundBitMap(ImageView view) {
        if (view != null) {
            BitmapDrawable bd = (BitmapDrawable) view.getBackground();
            rceycleBitmapDrawable(bd);
        }
    }

    public static void recycleImageViewBitMap(ImageView imageView) {
        if (imageView != null) {
            BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();
            rceycleBitmapDrawable(bd);
        }
    }

    private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {
        if (bitmapDrawable != null) {
            Bitmap bitmap = bitmapDrawable.getBitmap();
            rceycleBitmap(bitmap);
        }
        bitmapDrawable = null;
    }

四、尽量少用Png图,多用NinePatch的图

现在手机的分辨率越来越高,图片资源在被加载后所占用的内存也越来越大,所以要尽量避免使用大的PNG图,在产品设计的时候就要尽量避免用一张大图来进行展示,尽量多用NinePatch资源。

Android中的NinePatch指的是一种拉伸后不会变形的特殊png图,NinePatch的拉伸区域可以自己定义。这种图的优点是体积小,拉伸不变形,可以适配多机型。Android SDK中有自带NinePatch资源制作工具,Android-Studio中在普通png图片点击右键可以将其转换为NinePatch资源,使用起来非常方便。

五、使用大图之前,尽量先对其进行压缩

图片有不同的形状与大小。在大多数情况下它们的实际大小都比需要呈现出来的要大很多。例如,系统的Gallery程序会显示那些你使用设备camera拍摄的图片,但是那些图片的分辨率通常都比你的设备屏幕分辨率要高很多。
考虑到程序是在有限的内存下工作,理想情况是你只需要在内存中加载一个低分辨率的版本即可。这个低分辨率的版本应该是与你的UI大小所匹配的,这样才便于显示。一个高分辨率的图片不会提供任何可见的好处,却会占用宝贵的(precious)的内存资源,并且会在快速滑动图片时导致(incurs)附加的效率问题。

5.1 图片大小压缩:

直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃。
使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。
属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。

        BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
        bitmapFactoryOptions.inJustDecodeBounds = true;
        bitmapFactoryOptions.inSampleSize = 2;
        // 这里一定要将其设置回false,因为之前我们将其设置成了true  
        // 设置inJustDecodeBounds为true后,decodeFile并不分配空间,即,BitmapFactory解码出来的Bitmap为Null,但可计算出原始图片的长度和宽度  
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);

5.2 图片像素压缩:

Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存

Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(565没有透明度属性),如下:

        public static Bitmap readBitMap(Context context, intresId) {
            BitmapFactory.Options opt = newBitmapFactory.Options();
            opt.inPreferredConfig = Bitmap.Config.RGB_565;
            opt.inPurgeable = true;
            opt.inInputShareable = true;
            //获取资源图片 
            InputStreamis = context.getResources().openRawResource(resId);
            returnBitmapFactory.decodeStream(is, null, opt);
        }

其他知识点:Android 关于dp dip sp px dpi density解析

  • 1.px
    px即像素(Pixel),1px代表了手机屏幕上一个物理的像素点。由于以px为单位的控件在不同手机上显示大小不一定相同,故Android不推荐使用px来设置控件大小。

  • 2.分辨率
    分辨率通常表示为横轴像素长度和纵轴像素长度的乘积,如320*480等。

  • 3.dpi/densityDpi
    dpi的全称是Dots Per Inch,即点每英寸,一般被称为像素密度,它代表了一英寸里面有多少个像素点。计算方法为屏幕总像素点(即分辨率的乘积除以屏幕大小),常见的取值有120,160,240。

  • 4.density
    density直译为密度,它的计算公式为屏幕dpi除以160点每英寸,由于单位除掉了,故density只是一个比值,常见取值为1.0,1.5等。在Android中我们可以通过下面代码获取当前屏幕的density:getResources().getDisplayMetrics().density;

    简单来说,可以理解为 density 的数值是 1dp=density px;densityDpi 是屏幕每英寸对应多少个点(不是像素点),在 DisplayMetrics 当中,这两个的关系是线性的:

    density11.5233.54
    densityDpi160240320480560640
  • 5.dp(dip)
    dp,也叫做dip,全称为Density independent pixels,叫做设备独立像素。他是Android为了解决众多手机dpi不同所定义的单位,dp是一种虚拟抽象的像素单位,他的计算公式为:px = dp * (dpi / 160) = dp * density。因此在dpi大小为160的手机上,1dp = 1px,而在dpi大小为320的手机上,1dp = 2px,即在屏幕越大的手机上,1dp代表的像素也越大。因此我们定义控件大小的时候应该使用dp代替使用px。

  • 6.sp
    sp是Android中定义字体大小的一种单位,全称为Scaled Pixels,叫做放大像素。sp会根据用户手机上设定的字体大小而改变,在用户手机字体大小设置为正常的情况下,1sp = 1dp。sp与px之间的密度比例可以通过如下代码获取:getResources().getDisplayMetrics().scaledDensity;

  • 7.资源文件分辨率
    一般而言,我们存放资源文件的目录(res)会有多个子目录,这些子目录代表了不同系统屏幕分辨率:

密度ldpimdpihdpixhdpixxhdpixxxhdpi
中文低分辨率中分辨率高分辨率超高分辨率超超高分辨率超超超高分辨率
dpi120以下120~160160~240240~320320~480480~640
分辨率240*320320*480480*800720*12801080*19203840*2160
  • 8.找不到对应分辨率资源文件情况
    对于drawable资源,当应用在设备对应dpi目录下没有找到某个资源时,遵循“先高再低”原则,会从附近的分辨率获取图片,然后按比例进行缩放:

    比如,当前为xhdpi设备,并且只有以下几个目录,则drawable的寻找顺序为:
    xhdpi->xxhdpi->xxxhdpi(如果没有更高的了)->nodpi(如果有的话)->hdpi->mdpi,如果在xxhdpi中找到目标图片,则压缩2/3来使用,如果在mdpi中找到图片,则放大2倍来使用。

    因此,以现在主流设备来说一般可能在drawable-xxhdpi放置一份即可,这样可以尽量避免Android为我们放大图片所导致的OOM

    对于values资源,当应用设备在当前dpi对应目录的demins.xml中没有找到目标条目时,采用“就近匹配”原则:

    比如,当前为hdpi设备,并且只有以下几个目录,则values的寻找顺序为:
    hdpi->xhdpi->mdpi->values,即先向上级dpi目录查找,再向下级dpi目录查找,最后一路向下查找到values目录,如果values下都找不到,就只有找values-ldpi,当然,现在有这个目录的应用不多了。

单位换算

  1. 计算dpi
    例:一个手机屏幕4英寸(对角线长度),分辨率480×800,dpi如何计算?
    dpi(像素密度) = 对角线像素数量 ÷ 对角线长度
    对角线像素数量:利用勾股定理,通过屏幕分辨率480×800计算
    dpi = 233像素/英寸
    density = (233 px/inch)/(160 px/inch)=1.46

  2. 计算dp与px
    dp = (dpi / (160像素/英寸)) px = density px
    如1中的dp = 1.46px,意为在dip为233的屏幕上,1dp = 1.46px(像素)
    此时屏幕的相对分辨率为:
    宽度 = (480 / 1.46)dp = 329dp
    高度 = (800 / 1.46)dp = 548dp

    原文作者:iyifei
    原文地址: https://www.jianshu.com/p/1abdbd63096f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞