Android超长图加载与subsampling scale image view实现分析

Android中的图片加载一直是很重要的一块,也是很使人头疼的一块,动不动就出现OOM。因此咱们有fresco等优秀的第三方框架,什么三级缓存,一行代码就帮咱们轻松实现。但当面对超级长超级大分辨率尺寸的图时,就显得无能为力了,若是直接加载到内存中就又会出现OOM。git

1.BitmapRegionDecoder

实现长图大图的加载,最关键的类就是BitmapRegionDecoder他能够实现对图片的局部加载。github

mDecoder=BitmapRegionDecoder.newInstance(image,false);

            //这里不能用option来获取图片的宽和高,由于通过BitmapRegionDecoder类处理事后的inputstream不能在获取的其信息,自动返回-1
            imageHeight=mDecoder.getHeight();
            imageWidth=mDecoder.getWidth();
            bmp = mDecoder.decodeRegion(mRect, option);

复制代码

这里注意通过BitmapRegionDecoder类处理事后的inputstream不能再用option获取其信息,会自动返回-1。当建立decoder对象后其实并无将图片加载到内存中,只有调用了bmp = mDecoder.decodeRegion(mRect, option);以后才将这个mrect矩形的图片局部加载到内存中。canvas

本身实现

那么既然Android有这么方便的类,那咱们岂不是很简单就能够本身实现啦!因此参考 鸿洋_的Android 高清加载巨图方案 拒绝压缩图片 这篇博客,咱们能够本身实现一个简易的加载长图框架:数组

  • 自定义一个view,重写他的ondraw()onTouchEvent()
  • 建立GestureDetector.OnGestureListener的实现类和scroller去接管触摸事件,在move时记录滑动距离,重写computeScroll()去辅助滑动
  • 初始化咱们的局部加载类BitmapRegionDecoder,当屏幕滑动到哪,记录其坐标到rect里而后直接mDecoder.decodeRegion(mRect, option);调用 invalidate()去ondraw()
  • 同时注意设置option.inBitmap开启图片的复用,进一步减小内存占用
  • 另外一个减少内存开销的就是设置合适的采样率,根据控件的大小对图片进行合适的采样压缩
int insamplesize=1;
           while (imageWidth>1.6*width) {
                 imageWidth /= 2;
                 insamplesize*=2;
           }
           option.inMutable=true;
           option.inSampleSize=insamplesize;
复制代码
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDecoder!=null) {
                option.inBitmap=bmp;
                matrix.setScale(scale,scale);
                bmp = mDecoder.decodeRegion(mRect, option);
                canvas.drawBitmap(bmp,matrix,bitmapPaint);
            Log.i("TAG", "onDraw: "+bmp.getByteCount());
           }
    }
复制代码

注意:这里记录一个小小的坑:当用matrix进行图片的放大时,必定必定要设置画笔Paint,设置抗锯齿等优化,设与不设的差距真的挺大的缓存

private Paint bitmapPaint;

        bitmapPaint = new Paint();
        bitmapPaint.setAntiAlias(true);
        bitmapPaint.setFilterBitmap(true);
        bitmapPaint.setDither(true);
复制代码

结论

这样一个简易的图片加载框架就实现了,完美的避免了OOM,由于不用把图片完整的加载到内存中!可是,通过实测,这种方法在加载分辨率比较小的大图时滑动仍是挺丝滑的,但一遇到分辨率再大一点的,就会感觉到明显的滑动的卡顿,因而我把目光转向了subsampling scale image view这个目前应该是最流行的开源框架bash

2.subsampling scale imag实现原理

这里以原理实现为主,不想贴不少代码,具体可本身下载阅读github地址数据结构

1.ImageSource

subsampling scale image view经过这个类ImageSource去获取图片,因此咱们的图片资源都须要经过ImageSource去加载,支持从assets,文件,流中加载,从源码上看他其实就是一个工具类,用于方便加载各个路径的文件框架

2.fullImageSampleSize

.fullImageSampleSamplSize由private int calculateInSampleSize(float scale)计算出,这个值应该是咱们首先应该理解的。
他决定了图片是否须要用BitmapRegionDecoder进行区域加载。若是他的计算结果等于1,则表示这张图的分辨率还不够大,不须要进行切割进行区域加载,因此这种状况下是最简单的,直接将图加载进入,放大缩小,移动,都是经过Matrix来实现的,因此接下来就来讲一下Matrix工具

3.Matrix

matrix,矩阵,不少关于图片的功能都能经过他来作一些十分的变换来实现(惋惜当年线代没学好。。。)好比图片个人位移,放缩,旋转等等。subsampling scale image view也用了matrix来实现图片的放缩和位移,主要方法是
matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4); 有两个数组srA,rray和dstArray dstarray数组决定了图片在屏幕的位置,而大图的移动滑动就是经过他来实现的性能

4.Tile

private static class Tile这个内部类就是切片类,subsampling scale image view中最重要的一个数据结构。

private static class Tile {

        private Rect sRect;
        private int sampleSize;
        private Bitmap bitmap;
        private boolean loading;
        private boolean visible;

        // Volatile fields instantiated once then updated before use to reduce GC.
        private Rect vRect;
        private Rect fileSRect;

    }

复制代码

他的属性也很简单就是用来存储图片的一段切片信息,各类rect和bitmap和一个samplesiz其中须要区分一下各个rect

  • srect和filesrect实际上是保存这个切片的原始大小区域,也就调用是mDecoder.decodeRegion(mRect, option)区域加载时传入的rect
  • vRect描述绘制在view画布中的实际位置,也就是说图片放大后的上下滑动就是经过改变这个rect结合matrix.setPolyToP()来实现的
protected void onDraw(Canvas canvas) {
                            ......
                          if (matrix == null) { matrix = new Matrix(); }
                            matrix.reset();
                            setMatrixArray(srcArray, 0, 0, tile.bitmap.getWidth(), 0, tile.bitmap.getWidth(), tile.bitmap.getHeight(), 0, tile.bitmap.getHeight());
                            if (getRequiredRotation() == ORIENTATION_0) {
                                setMatrixArray(dstArray, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left,  
                                }...
                                ...
                                
                            matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4);
                            canvas.drawBitmap(tile.bitmap, matrix, bitmapPaint);

                            ......
    }
复制代码
5.三个task

TilesInitTask,TileLoadTask ,BitmapLoadTask
subsampling scale image view内部又建立了三个继承自AsyncTask的task用来在后台加载decode图从而不阻碍ui主线程,更加流畅。 因此当fullImageSampleSize==1时,就直接用BitmapLoadTask解码整个图片不需切割,当期大于1时,就须要用TileLoadTask区域解码分割后的图片

Map<Integer, List<Tile>> tileMap

最后介绍这个框架的核心,就是这个map
咱们知道,图片放得越大,所须要的像素分辨率就要越高才能匹配,要否则就会很模糊。相反,若是图片缩得很小,就不须要很高的分辨率,多了就浪费了。而Android中就能够根据 option.inSampleSize来对图片进行采样压缩,减少分辨率。
因此,根据这个原理,subsampling scale image view将其根据须要计算出不一样的采样率,当作key,而后根据不一样的采样率进行切割,生成List<Tile>
放大的时候,subsampling scale image view会选取合适的采样率后获取到List<Tile>而后进行解码,而且,他只会解码显示的部分,也就是til.visiable为true时才会解码。否者将其回收。

综上就是subsampling scale image view的大体实现原理

对比

对比本身实现的和subsampling scale image view,后者在大图的切片方面作得更好只将大图切成若干片,在判断是否可见,若是可见就加载到内存中,否者回收;滑动时只改变矩阵的值进行简单的位移变换,进一步提高了流畅度,并且根据不一样放缩比例选择合适的采样率,进一步减小内存占用。本身实现的每滑动一次就要从新解码绘制好几回,因此后者性能更高,值得学习。

相关文章
相关标签/搜索