Android 性能优化实践(三)——Bitmap优化

一、Bitmap占用的内存

bitmap内存占用大小是长(像素)x宽(像素)x每个像素占用的位数,每个像素占用的位数跟图片格式有关

public static enum Config {
    ALPHA_8,//每个像素占用1byte内存
    RGB_565,//每个像素占用2byte内存
    ARGB_4444,//每个像素占用2byte内存
    ARGB_8888;//每个像素占用4byte内存;默认模式
}

bitmap内存占用大小还跟将图片放在哪个像素密度文件夹有关。搞像素密度的手机,如果将图片放到低像素密度的文件夹,系统会将低像素密度文件夹里面的图片长宽增大后再放到内存,为了显示不过与模糊。

drawable-ldpi  	120
drawable-mdpi  	160
drawable-hdpi  	240
drawable-xhdpi 	320
drawable-xxhdpi 480

Bitmap内存压缩
1.按图片显示大小计算采样率inSampleSize,然后将图片缩小一定的比率,在加载到内存中显示。
2.判断需求是否需要透明度,如果不需要,采用 RGB_565格式

二、Bitmap缓存

1.采用LruCache工具类管理Bitmap对象
这里就是相对于用用一个LinkedHashMap将Bitmap缓存起来,代码就不写了。

2.采用inBitmap 对Bitmap内存 进行复用
主要代码是以下含注释的代码,需要用BitmapFactory.Options进行相关设置。先设置bitmap对象可变,后设置复用的bitmap对象即可。这里需要关注下Android系统版本,3.0 之前是不能复用,3.0-4.4 宽高一样才能复用, 4.4 只要小于等于就可以复用。

public Bitmap getBitmapFromDisk(String key, Bitmap reusable) {
        DiskLruCache.Snapshot snapshot = null;
        Bitmap bitmap = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                return null;
            }
            InputStream is = snapshot.getInputStream(0);

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inMutable = true;//设置bitmap对象可变,如果是不可变就不能复用
            options.inBitmap = reusable;//设置复用的bitmap对象
            bitmap = BitmapFactory.decodeStream(is, null, options);
            if (bitmap != null) {
                lruCache.put(key, bitmap);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
        return bitmap;
    }

三、Bitmap大图的加载

一般是自定义View 结合GestureDetector,通过将图片等比例缩放适合屏幕宽或者高后,截取适合屏幕显示的大小,加载到内存中,然后通过手势切换其他用户需要加载的区域。

package com.enjoy.bigimage;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import java.io.IOException;
import java.io.InputStream;

public class BigImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {


    Rect mRect;
    BitmapFactory.Options mOptions;
    int mImageWidth;
    int mImageHeight;
    BitmapRegionDecoder mBitmapRegionDecoder;
    private GestureDetector mGestureDetector;
    private Scroller mScroller;

    public BigImageView(Context context) {
        this(context, null, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();

        //手势
        mGestureDetector = new GestureDetector(context, this);
        // 将触摸事件交给手势处理
        setOnTouchListener(this);
        // 滑动帮助
        mScroller = new Scroller(context);

    }

    public void setImage(InputStream is) {

        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, mOptions);

        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;

        mOptions.inMutable = true;
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;

        mOptions.inJustDecodeBounds = false;

        try {
            // false 不共享 图片源
            mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        requestLayout();
    }

    int mViewHeight;
    int mViewWidth;
    float mScale;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();

        if (mBitmapRegionDecoder == null) {
            return;
        }

        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mImageWidth;

        // 缩放因子
        mScale = mViewWidth / (float) mImageWidth;

        // x * mscale = mViewHeight
        mRect.bottom = (int) (mViewHeight / mScale);

        // 第一种方式优化
//        mOptions.inSampleSize = calcuteInSampleSize(mImageWidth, mImageHeight, mViewWidth, mViewHeight);

        // 第二种方式优化
//        float temp = 1.0f / mScale;
//        if (temp > 1) {
//            mOptions.inSampleSize = (int) Math.pow(2, (int) (temp));
//        } else {
//            mOptions.inSampleSize = 1;
//        }

    }

    /**
     * @param w    图片宽
     * @param h    图片高
     * @param maxW View 宽
     * @param maxH View 高
     * @return
     */
    private static int calcuteInSampleSize(int w, int h, int maxW, int maxH) {

        int inSampleSize = 1;

        if (w > maxW && h > maxH) {
            inSampleSize = 2;

            while (w / inSampleSize > maxW && h / inSampleSize > maxH) {
                inSampleSize *= 2;
            }

        }

        return inSampleSize;
    }

    Bitmap bitmap = null;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mBitmapRegionDecoder == null) {
            return;
        }

        mOptions.inBitmap = bitmap;
        bitmap = mBitmapRegionDecoder.decodeRegion(mRect, mOptions);
        

        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
//        matrix.setScale(mScale * mOptions.inSampleSize, mScale * mOptions.inSampleSize);

        canvas.drawBitmap(bitmap, matrix, null);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        // 如果滑动还没有停止 强制停止
        if (!mScroller.isFinished()) {
            mScroller.forceFinished(true);
        }
        //继续接收后续事件
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }


    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //改变加载图片的区域
        mRect.offset(0, (int) distanceY);

        //bottom大于图片高了, 或者 top小于0了
        if (mRect.bottom > mImageHeight) {
            mRect.bottom = mImageHeight;
            mRect.top = mImageHeight - (int) (mViewHeight / mScale);
        }
        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }

        // 重绘
        invalidate();
        return false;
    }


    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        /**
         * startX: 滑动开始的x坐标
         * velocityX: 以每秒像素为单位测量的初始速度
         * minX: x方向滚动的最小值
         * maxX: x方向滚动的最大值
         */
        mScroller.fling(0, mRect.top, 0, (int) -velocityY, 0, 0,
                0, mImageHeight - (int) (mViewHeight / mScale));
        return false;
    }

    /**
     * 获取计算结果并且重绘
     */
    @Override
    public void computeScroll() {
        //已经计算结束 return
        if (mScroller.isFinished()) {
            return;
        }
        //true 表示当前动画未结束
        if (mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
            invalidate();
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 事件交给手势处理
        return mGestureDetector.onTouchEvent(event);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值