android 图片内存处理

Bitmap内存占用

内存大小计算

     ARGB_8888  ARGB各占8位,即WIDTH*HEIGHT*4

     RGB_565    R5,G6,B5,16WIDTH*HEIGH*2

      getByteCount  返回可用于存储此位图像素的最小字节数

BitmapFactory.Options  控制解码图片参数

inDensity:  表示这个bitmap的像素密度,根据drawable目录

inTargetDensity:  表示要被画出来的目(屏幕)像素密度 , getResources().getDisplayMetrics().densityDpi

inJustDecodeBounds    读取图片out…系列参数,如outWidth与outHeight,可用于计算内存大小

inPreferedConfig    设置图片解码后的像素格式,如ARGB_8888/RGB_565

inSampleSize    设置图片解码缩放比,如值为4,则加载图片宽高是原图的1/4,内存大小则是1/16

inDensity赋值

  drawable-ldpi    120

  drawable-mdpi    160

  drawable-hdpi    240

  drawable-xhdpi   320

  drawable-xxhdou   480

对于内存的降低,无论是选择jpg还是png更或者是webp。其实都是毫无意义的。Jpg是属于有损压缩,我们看见的jpgpng文件小,那是因为压缩率高。这都是属于文件存储范畴。对于内存来说,我们加载一张不带alpha通道使用RGB_565格式的png与一张jpg占用的内存大小都是一样的。对于内存的压缩我们能做的就是缩小图片尺寸与改变像素格式。

官方示例:

https://github.com/googlesamples/android-DisplayingBitmaps/#readme

https://developer.android.google.cn/topic/performance/graphics/manage-memory#java(重点关注inBitmap参数)

可被复用的Bitmap必须设置inMutabletrue

Android4.4(API 19)之前只有格式为jpgpng,同等宽高(要求苛刻),inSampleSize1Bitmap才可以复用;

Android4.4(API 19)之前被复用的BitmapinPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig

Android4.4(API 19)之后被复用的Bitmap的内存必须大于等于需要申请内存的Bitmap的内存;

通过bitmap复用,减少频繁申请内存带来的性能问题。API-19

getAllocationByteCount  一般情况下getByteCount()与getAllocationByteCount()是相等的;通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap占用的内存大小。所以可能allocation比bytecount大。

API-19

getAllocationByteCount

  一般情况下getByteCount()与getAllocationByteCount()是相等的;

通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap占用的内存大小。所以可能allocation比bytecount大。

图片的缓存机制

 沿用流传的三级缓存的说法 网络,内存 LruCache,   文件 DiskLruCache、此基础上增加一级弱引用复用池。

(1、对图片宽高使用inSampleSize进行压缩达到节省内存
   如一张1024x1024的图片作为头像,可以在加载进入内存的时候,进行缩放

2、使用inBitmap对不需要的图片进行复用,能够避免反复申请Bitmap内存。
结合弱引用避免无用的复用缓存。)

 

https://github.com/IanIanIanIan/andoidBitmapCache

 

加载大图用 BitmapRegionDecoder 配合Rect 进行裁剪显示
 


/**
 * Created by Administrator on 2018/1/15 0015.
 */
public class BigView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
    private Scroller mScroller;
    private GestureDetector mGestureDetector;
    private BitmapFactory.Options mOptions;
    private Rect mRect;
    private int mImageWidth;
    private int mImageHeight;
    private BitmapRegionDecoder mDecoder;
    private int mViewWidth;
    private int mViewHeight;
    private float mScale;
    private Bitmap bitmap;

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

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

    public BigView(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);
    }

    /**
     * 由使用者输入一张图片 输入流
     *
     * @param is
     */
    public void setImage(InputStream is) {
        //先读取原图片的 宽、高
        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, mOptions);
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;
        //复用
        mOptions.inMutable = true;
        //设置像素格式为 rgb565
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds = false;
        //创建区域解码器 用于区域解码图片
        try {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        requestLayout();
    }

    /**
     * 测量 view的大小
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获得测量的view的大小
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();
        //如果解码器是null 表示没有设置过要现实的图片
        if (null == mDecoder) {
            return;
        }
        //确定要加载的图片的区域
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mImageWidth;
        //获得缩放因子
        mScale = mViewWidth / (float) mImageWidth;
        // 需要加载的高 * 缩放因子 = 视图view的高
        // x * mScale = mViewHeight
        mRect.bottom = (int) (mViewHeight / mScale);
    }

    /**
     * 把图片画上去
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 如果解码器是null 表示没有设置过要现实的图片
        if (null == mDecoder) {
            return;
        }
        //复用上一张bitmap
        mOptions.inBitmap = bitmap;
        //解码指定区域
        bitmap = mDecoder.decodeRegion(mRect, mOptions);
        //使用矩阵 对图片进行 缩放
        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
        //画出来
        canvas.drawBitmap(bitmap, matrix, null);
    }


    /**
     * 手指按下屏幕的回调
     * @param e
     * @return
     */
    @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) {

    }

    /**
     * 手指 不离开屏幕 拖动
     * @param e1 手指按下去 的事件 -- 获取开始的坐标
     * @param e2 当前手势事件  -- 获取当前的坐标
     * @param distanceX  x轴 方向移动的距离
     * @param distanceY  y方向移动的距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // 手指从下往上 图片也要往上 distanceY是负数, top 和 bottom 在减
        // 手指从上往下 图片也要往下 distanceY是正数, top 和 bottom 在加
        //改变加载图片的区域
        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;
    }

    /**
     * 手指离开屏幕 滑动 惯性
     * @param e1
     * @param e2
     * @param velocityX  速度 每秒x方向 移动的像素
     * @param velocityY  y
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        /**
         * startX: 滑动开始的x坐标
         * startY: 滑动开始的y坐标
         * 两个速度
         * minX: x方向的最小值
         * max 最大
         * y
         */
        //计算器
        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、付费专栏及课程。

余额充值