Skip to content

2019-8-28: 说说如何处理大长图的加载? #24

Open

Description

@zhjlong
No description provided.

Activity

  1. rainbow7 commented on Aug 27, 2019

    @rainbow7

    BitmapRegionDecoder

    它的官方解释

    BitmapRegionDecoder can be used to decode a rectangle region from an image.
    BitmapRegionDecoder is particularly useful when an original image is large and
    you only need parts of the image

    很直白,加载一张图片的一部分。

    使用也很简单。rect是一个显示区域,options是图片的设置项。这样就可以显示一张大图的的部分了。显示不同的区域,根据手势调整rect的区域即可。

    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
            BitmapFactory.Options.validate(options);
            synchronized (mNativeLock) {
                checkRecycled("decodeRegion called on recycled region decoder");
                if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
                        || rect.top >= getHeight())
                    throw new IllegalArgumentException("rectangle is outside the image");
                return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
                        rect.right - rect.left, rect.bottom - rect.top, options);
            }
        }
  2. firehell commented on Aug 28, 2019

    @firehell

    https://www.jianshu.com/p/116e05dfd66b
    参考这个文章,一些想法
    1.用浏览器加载的方法,从上到下一点一点加载,这样的好处是图片清晰度比较高
    2.先加载一张缩略图占位,然后压缩整个图片,减少图片的像素加载出图片,会快一些但不够清晰
    //尺寸压缩(通过缩放图片像素来减少图片占用内存大小)
    `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();
        }
    }`
    
  3. chinwetang commented on Aug 28, 2019

    @chinwetang

    看到小伙伴回答的,大概是基于加载速度和内存优化两个方向的,我没其他想法,拾人牙慧答一下;

    加载优化的就是Http协议中的Range / Accept-Range ,他可以指定获取body的一部分,从而达到分块加载图片的效果;
    内存优化的方向是,我屏幕只有这么大,那我就只加载跟屏幕一样的Bitmap就好了,超出屏幕外面的就没必要加到内存里面增加负担;

    @Override 
    protected void onDraw(Canvas canvas) { 
    //这个mDecoder就是上面同学说到的BitmapRegionDecoder,options就是BitmapFactory.Options
          Bitmap bm = mDecoder.decodeRegion(mRect, options);
          canvas.drawBitmap(bm, 0, 0, null);
    } 
    

    根据手势滑动每次重新计算mRect去获取部分bitmap绘制到屏幕上。

  4. skylarliuu commented on Aug 28, 2019

    @skylarliuu

    (谢谢上面同学的回答)

    加载大长图可以使用 压缩或局部加载的方法。

    压缩可以使用BitmapFactory类的相关方法,获取到图片的原始大小后,再计算图片压缩比,最后压缩图片后再显示。不过压缩就会降低图片的清晰度,如果要按原图加载图片,可以用BitmapRegionDecoder实现图片局部加载。

    BitmapRegionDecoder可以用来显示图片指定的一个矩形区域。通过自定义View重写onTouchEvent()判断手势滑动位置,从而改变矩形区域的位置。

    如果是加载网络大图片,还可以分块请求数据,让图片更快地显示出来。

    (分享鸿洋的一篇博客:Android 高清加载巨图方案 拒绝压缩图片)

  5. zhengjunke commented on Aug 28, 2019

    @zhengjunke

    参考简书资料:https://www.jianshu.com/p/4640764bfbc6
    图片太大,进行压缩处理:
    BitmapFactory这个类就提供了多个解析方法(decodeResource、decodeStream、decodeFile等)用于创建Bitmap。我们可以根据图片的来源来选择解析方法。比如如果图片来源于网络,就可以使用decodeStream方法;如果是sd卡里面的图片,就可以选择decodeFile方法;如果是资源文件里面的图片,就可以使用decodeResource方法等。
    BitmapFactory为这些方法都提供了一个可选的参数BitmapFactory.Options,用来辅助我们解析图片。这个参数有一个属性inSampleSize,这个属性可以帮助我们来进行图片的压缩。
    如果不进行压缩处理,图片超出屏幕显示范围,进行局部加载处理:
    BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
    bitmapRegionDecoder.decodeRegion(rect, options);
    参数一是一个rect,控制显示区域,参数二是BitmapFactory.Options,你可以控制图片的inSampleSize,inPreferredConfig等。

  6. pioneerz commented on Aug 28, 2019

    @pioneerz

    1.对图片进行压缩处理

    BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(), R.mipmap.wei, options);
            options.inSampleSize = Math.max(options.outWidth / DensityUtils.screenWidth(this),
                    options.outHeight / DensityUtils.screenHeight(this));
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.wei, options);
            mImageView.setImageBitmap(bitmap);
            mMemoryText.setText("bitmapMemory="+bitmap.getByteCount() + "   imageMemory="+bitmap.getAllocationByteCount());
    

    这种方案虽然也能够显示,但是不能够保证图片的清晰度。

    2.局部展示:

    InputStream is = null;
            try {
                is = getAssets().open("wei.png");
                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
                BitmapFactory.Options options1 = new BitmapFactory.Options();
                options1.inPreferredConfig = Bitmap.Config.ARGB_8888;
                Bitmap bitmap = decoder.decodeRegion(new Rect(0, 0, DensityUtils.screenWidth(this),
                        DensityUtils.screenHeight(this)- DensityUtils.dip2px(this, 200)), options1);
                mImageView.setImageBitmap(bitmap);
                mMemoryText.setText("bitmapMemory="+bitmap.getByteCount() + "   imageMemory="+bitmap.getAllocationByteCount());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    这种方案是加载图片的局部,如果要观察其余部分,可以通过增加手势操作或者触摸事件。

  7. chengying1216 commented on Aug 29, 2019

    @chengying1216
  8. SeniorDoctorHui commented on Aug 29, 2019

    @SeniorDoctorHui

    对于加载大长图主要有以下一些路径:

    1,对于加载网络的图片,如果只是作为缩略图展示的话,可以下载该图片的缩略图来展示,以提高加载速度和省流量的目的。点击详情图片时,才去加载原图。

    2,就是使用上面各位同学说的BitmapRegionDecoder这个类来展示局部图片,根据可见区域来加载图片,从而避免oom的问题。但是该类只是Android为我们提供的基本API,实际使用中,你会发现如果在onDraw方法中绘制bitmap对象会占用主线程资源,导致丢帧而造成卡顿的现象。除此之外还需要考虑快速滑动而造成bitmap大量创建和大量回收而生成的抖动问题。而且如果要加载长图,还要有放大缩小的功能,这些也需要我们自己实现。

    3,使用SubsamplingScaleImageView框架来加载长图,该框架底层就是使用BitmapRegionDecoder来加载局部图片的,该框架提供了缩放和触摸等手势的功能。该框架为了解决滑动卡顿的问题,对于move事件进行了优化,不会直接加载清晰度高的图片,而是对于原解码率的图片进行scale放大,直到up事件发生后,才会在后台加载相应解码率的图片,生成对应的bitmap,再进行相应的更新。因此滑动时,我们先会看到模糊的图片,然后才能看到清晰的图片。它的缺点是不能直接加载网络的图片,要把网络的图片下载到本地才能加载。

    4,使用SketchImageView来加载长图,底层也是BitmapRegionDecoder实现的。这是一套很完整的图片加载框架,对于大图的分块加载,缩放手势,缓存复用缓存回收应有尽有,支持加载网络链接的大图。是一套很不错的加载大图的商业级方案,具体介绍可以看https://github.com/panpf/sketch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions