Android内存溢出与优化(二)——不做无意义的内存消耗

总篇地址:Android内存溢出与优化(零)——开题篇 


       写代码时,我们一般会习以为常的按照惯例的方法走,最后项目也能够完成,顺利竣工。但是,有的朋友查看App内存消耗时,会发现“同样的应用产品,自己的App消耗的内存居然比别人多”。原因就在于,技术不断更新,做同样的事,由于方式不同,消耗的内存量也不同。在能完成同样一件事,用户界面体验相同时,我们应该采取更有效的方式(例如内存消耗更低)。下面还是举例,进行说明。


*****个人经验狭隘,难免有所疏漏,若有问题,恳请斧正!*****


1.单张图片的加载与内存消耗

        “加载一张图片,完全不是事,不需要优化”这应该是很多初入开发的朋友的想法,没错,确实是“这都不是事儿”。 但是,应用中最常见的就是图片加载,一旦图片多了,积少成多,就可能出现OOM,所以我们应从小处入手,关注每一张图的优化。(在这里,我先说说单张图片的优化,多图加载将在后面论述中详细说明)

        (1)加载的图片的大小不应超过展示图片的View的大小。举个例子,一张图片是1920px*1080px的,一个ImageView的大小是1280px*720px,当在此ImageView中加载这张图片时,实际显示的是1280px*720px的图片,而内存中却消耗了1920px*1080px的图片的大小。这样就相当于多做了无用功,浪费内存。而我们期望的是实际内存消耗的大小也应该是1280px*720px图片大小,此时我们就应该压缩图片大小后再显示。例如,我们可以按照谷歌官方推荐的方式压缩,我写了一个示例如下:

/**
 * Bitmap宽高压缩工具
 *
 * @author ALion on 2016/11/17 18:46
 */

public class BitmapUtil {

    /**
     * 不让工具类实例化
     */
    private BitmapUtil() {
        throw new UnsupportedOperationException("Do not need instantiate!");
    }

    /**
     * <Enable>压缩Bitmap
     * @param res getResources()
     * @param resId 图片资源Id
     * @param reqWidth  压缩后的最大宽度
     * @param reqHeight 压缩后的最大高度
     * @return 压缩后的Bitmap
     */
    public static Bitmap decodeBitmap(Resources res, int resId, int reqWidth, int reqHeight) {

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//设置为true后,在解码时不会分配内存,返回null的Bitmap,但是可以拿到宽高
        BitmapFactory.decodeResource(res, resId, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);//计算inSampleSize

        options.inJustDecodeBounds = false;//重置为false,解码后返回有效bitmap

        return BitmapFactory.decodeResource(res, resId, options);
    }

    /**
     * 计算inSampleSize
     * @param options 携带原始图片宽高的对象
     * @param reqWidth  压缩后的最大宽度
     * @param reqHeight 压缩后的最大高度
     * @return inSampleSize
     */
    private static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {

        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        //计算出压缩后不大于reqWidth、reqHeight的inSampleSize
        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;//得2的幂的数,是因为正真解码的时候,还是会自动向下处理,得到最靠近2的幂的数
            }
        }

        return inSampleSize;
    }
}

        上述工具类,根据图片和View的大小对图片进行了压缩,这样就保证了图片不会做无意义的内存消耗。同时,你还可以修改工具类,改为传入Bitmap,而不是资源Id。

        (2)注意图片的色彩格式。首先,我们理应知道到一下几点:

	//A:透明度 R:红色 G:绿 B:蓝
	//Bitmap.Config ARGB_8888:1像素=4字节,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
	//Bitmap.Config ARGB_4444:1像素=2字节,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 
	//Bitmap.Config RGB_565:  1像素=2字节,即无A,R=5,G=6,B=5,那么一个像素点占5+6+5=16位
	//Bitmap.Config ALPHA_8:  1像素=1字节,只有透明度,没有颜色。

         不同的色彩格式,消耗的内存也不同,实际使用中应根据想要的效果来确定。例如,一张高清海报应该采用ARGB_8888,日常普通使用应采用ARGB_4444(有透明度,PNG)或者RGB_565(无透明度,JPG),ALPHA_8(只有透明度无颜色)使用很少见。(关于这点,第三方图片加载框架中很明显的例子就是Picasso和Glide,Picasso默认采用ARGB_8888,而Glide默认采用RGB_565,消耗的内存更小)

2.不需要时,就不传参

         在使用一些方法函数时,我们常会传一些参数进去,其实有的时候,某些参数是无需(通常会去new一个对象传进去),传一个null既可。例如,我们在一个Canvas上面添加一张已画好的Bitmap,如下:

//        Canvas canvas = new Canvas(resultBitmap);
//        canvas.drawBitmap(bitmap, 0, 0, new Paint());
        Canvas canvas = new Canvas(resultBitmap);
        canvas.drawBitmap(bitmap, 0, 0, null);

         在canvas调用drawaBitmap时,最后一个参数需要传一个Paint进去,但是实际上我们的Bitmap是一张已经画好的图,这时就可以直接传null,而不是像注释掉的代码一样new Paint()。

        这里只提出一个例子,实际开发中还有很多地方要去注意,不做无意义的内存消耗。多注意你的代码是否需要某个参数,传进去是不是有意义?

3.实现同样的效果,应使用较轻量级的控件

        开发中,要实现同样的一个效果,可能有很多种方式,那么又该如何选择呢?考虑到移动端内存的问题,当然是优选内存消耗较小的控件了。(同时,你还应该多关注网络上最新的实现方案)

        举个例子,想要实现App中最常见的卡片式布局(底部有一排按钮,点击可以切换不同界面)。在过去,我们通常采用的实现方式是TableHost+Activity,而现在可以采用FragmentTableHost+Fragment来实现。Activity属于重量级控件,而Fragment相对属于轻量级,更有利于内存优化,减小内存的使用,因此应该更多的使用Fragment。(另外,你还可以采用ViewPager+Fragment+RadiuGroup,方式很多)


        总结,开发中某些情况按照我们自己的思路,可能顺利的就实现了某个功能,但是我们不应只限于此,而应多思考是否有更好的方式来实现某个功能,也就是常说的“不仅要做到,还要做好!”。多去看,多去揣摩,多去思考,多去实践一番,去除不好的实现方式,得到更好的实现方式。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值