Android 处理大图问题

背景

无论在现实开发中,还是面试中,这个问题都会经常遇到。

具体情况可以分为两种

  • 图片的大小很大,但是需要在android中显示的区域却没有图片真正大小那么大。

比如一个高清图片作为头像,图片的大小是1M,10241024。但是在手机里只需要显示8080的大小。

比如著名的清明上河图(30000*926)如果只是要显示缩略图,就不必加载原图

  • 图片的大小很大,需要在Android中可以显示原图大小。

比如要查看高清头像的原图

比如著名的清明上河图(30000*926)如果显示原图这样的大图加载到内存中占用106M的大小,很显然系统是接受不了的。

针对这两种不同的需求我们采用不同的策略。

图片的大小很大,但是需要在android中显示的区域却没有图片真正大小那么大。

首先针对要缩略图这类的需求
压缩图片方式有很多种:

  • 质量压缩
  • 尺寸压缩
  • 采样率压缩
  • libjpeg库来进行压缩

一、质量压缩用于本地想服务器上传图片,或者保存到本地的时候

/**
     * 质量压缩
     * 设置bitmap options属性,降低图片的质量,像素不会减少
     * 第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
     * 设置options 属性0-100,来实现压缩
     *
     * @param bmp
     * @param file
     */
    public static void qualityCompress(Bitmap bmp, File file) {
        // 0-100 100为不压缩
        int quality = 20;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、尺寸压真生实现像素的减少 多用于缓存缩略图

需要考虑将大图片读入内存后的释放事宜。

/**
     * 尺寸压缩(通过缩放图片像素来减少图片占用内存大小)
     *
     * @param bmp
     * @param file
     */

    public static void sizeCompress(Bitmap bmp, File file) {
        // 尺寸压缩倍数,值越大,图片尺寸越小
        int ratio = 8;
        // 压缩Bitmap到对应尺寸
        Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
        canvas.drawBitmap(bmp, null, rect, null);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把压缩后的数据存放到baos中
        result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

三、采样率压缩

设置图片的采样率,降低图片像素
不会先将大图片读入内存,大大减少了内存的使用,也不必考虑将大图片读入内存后的释放事宜。不过因为采样率是整数,有时候采样率会不合适。2太大,3太小。
不同的压缩方法,看情景去应用

这就需要BitmapFactory. Options里的两个参数

  • inJustDecodeBounds
    设置为true就可以让解析方法禁止为bitmap分配内存,就是只会得到里面的数据信息,而不会把bitmap加载到内存里面
  • inSampleSize
    采样率,这个就是图像数字化的时候,单位时间采样本数,白话就是图片的某一点需要样本去表示。比如我们本来需要16个,采样率变成2 ,那么就变成4个了。
    >1 说明高清变普清
    <1 就是普清变高清
    更加官方的解释请自行百度,我就是比喻一下。

这样我们就可以把图片压缩成我们想要的结果了。

public static int calculateInSampleSize(BitmapFactory.Options options,
        int reqWidth, int reqHeight) {
    // 源图片的高度和宽度
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;
    if (height > reqHeight || width > reqWidth) {
        // 计算出实际宽高和目标宽高的比率
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
        // 一定都会大于等于目标的宽和高。
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;


public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    // 调用上面定义的方法计算inSampleSize值
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    // 使用获取到的inSampleSize值再次解析图片
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
  img.setImageBitmap(
                decodeSampledBitmapFromResource(getResources(), R.drawable.qmsht, 100, 100));

一般来说这样就完了,但是我们的图片如果来自于IO流里面数据呢,这样做就有问题了。

  private void loadSLImg() {
        InputStream inputStream = null;
        try {
            inputStream = getAssets().open("qmsht.jpg");
        } catch (IOException e) {
            e.printStackTrace();
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inputStream, null, options);

        options.inSampleSize = calculateInSampleSize(options, 800, 100);
    
        options.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
        img.setImageBitmap(bitmap);


    }

结果运行我们发现,并没有出现图片,这是为什么呢?
因为我们之前

options.inJustDecodeBounds = true;

已经生效,所以

Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);

bitmap 为null,并不会有位图解析出来。
你可能会说 后来不是 设置 false了吗?

options.inJustDecodeBounds = false;

但是inputStream流文件已经被设置options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);方法之后这个流文件某些属性已经被更改。因为源码在Native,所以我们并不是具体情节,所以我们可以做一些测试看看结果。

options.inJustDecodeBounds = false;

  InputStream inputStream = null;
        try {
            inputStream = getAssets().open("qmsht.jpg");
            Log.e("inputStream", "原size:" + inputStream.available());
        } catch (IOException e) {
            e.printStackTrace();
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
        Log.e("inputStream", "处理后size:" + inputStream.available());
        Log.e("inputStream", "bitmap 是否为null:" + (bitmap==null)+" getWidth  "+bitmap.getWidth());

E/inputStream: 原size:8063397
E/inputStream: 处理后size:0
E/inputStream: bitmap 是否为null:false getWidth  30000

说明inputStream被消耗掉了
options.inJustDecodeBounds = true;

 InputStream inputStream = null;
        try {
            inputStream = getAssets().open("qmsht.jpg");
            Log.e("inputStream", "原size:" + inputStream.available());
        } catch (IOException e) {
            e.printStackTrace();
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
        Log.e("inputStream", "处理后size:" + inputStream.available());
        Log.e("inputStream", "bitmap 是否为null:" + (bitmap==null));

E/inputStream: 原size:8063397
E/inputStream: 处理后size:8057973
E/inputStream: bitmap 是否为null:true

从结果不难看出,所以我们的策略就是,一个流分析,计算出inSampleSize
流一个流来加载图片。
至于根据path来在家图片就没有那么麻烦了
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
你懂的其实他也是两次。

图片的大小很大,需要在Android中可以显示原图大小。

这一类就也很好办,那我就一部分一部分来显示吧,局部显示总不会超吧,如果局部显示也会超,那么先压缩再局部显示。
android 中有这么一个类 BitmapRegionDecoder。看名称就是区域解析。然后通过手势来移动不同的区域,动态的解析不懂得区域,达到看图无缝连接。

  private void loadBigImg() {
        if (inputStream == null) {
            try {
                inputStream = getAssets().open("qmsht.jpg");
            } catch (IOException e) {
                e.printStackTrace();
            }

            //获得图片的宽、高
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            tmpOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(inputStream, null, tmpOptions);
            width = tmpOptions.outWidth;
            height = tmpOptions.outHeight;


            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(old_x, 0, old_x + width / 40, height), options);
        img.setImageBitmap(bitmap);
    }

很简单吧,然后可以通过手势,来改变Rect的四个坐标,我们就可以随意看大图了。

@Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                dow_x = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:

                old_x += (int) (dow_x - event.getX());
                loadBigImg();

                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        return true;
    }

结尾

这些都是原理和简单操作,要解决实际问题还需要更细致的处理。共勉~

大家可以点个关注,告诉我大家想要深入探究哪些问题,希望看到哪方面的文章,我可以免费给你写专题文章。。哈哈。。。
希望大家多多支持。。你的一个关注,是我坚持的最大动力。。

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