使用Glide模仿微信图片加载策略

为什么要对图片进行压缩

比较压缩与否造成的资源消耗

你能区别出:下面两个九方格图片展示的区别不?
图1:
使用Glide模仿微信图片加载策略_第1张图片
图2:
使用Glide模仿微信图片加载策略_第2张图片
区别在于:图1使用的是压缩图,图2使用的是高清图
以下是高清图地址
http://o9xuvf3m3.bkt.clouddn.com/new_york.jpg
http://o9xuvf3m3.bkt.clouddn.com/peru.jpg
http://o9xuvf3m3.bkt.clouddn.com/trochilidae.jpg
http://o9xuvf3m3.bkt.clouddn.com/france-217.jpg
http://o9xuvf3m3.bkt.clouddn.com/france-220.jpg
http://o9xuvf3m3.bkt.clouddn.com/france-216.jpg
http://o9xuvf3m3.bkt.clouddn.com/france-221.jpg
以九方格中第一张图为例(高清地址:http://o9xuvf3m3.bkt.clouddn.com/new_york.jpg)
大小2.7M2560*1920

压缩后的地址http://o9xuvf3m3.bkt.clouddn.com/new_york.jpg?imageView2/3/h/360/w/360
压缩后大小31KB,480*360
压缩后的样子使用Glide模仿微信图片加载策略_第3张图片
压缩比高达1:89,但是神奇的是,它们在界面展示上展示效果几乎一致。溜不溜….

Monitors

手机尺寸总共就那么大,这里我是用小米Note,一格的宽度=一格的高度=一个1/3屏幕宽度,经过计算占用像素值是360*360,控件既不能放大又不能缩小,我们给再大的图又有什么意义。当然,图片很难可以达到完全适配的理想效果,所以我是用一张480*360大小的图片,然后用Center_Crop模式填充即可。
我们AndroidStudio Monitors上测试下,影响到底有多大
使用Glide模仿微信图片加载策略_第4张图片
我家是20M光纤,我一个人独显,都要6,7S,我在想要是2G手机使用高清图加载的话,得多久。
动手算算,2.2M*7=15.4M,
10K/S,1540秒,也就是26min,这种App要是拿给网络差一点的用户,估计直接就删了。
总结下为什么要对图片进行压缩:

  1. 节约带宽,消耗更少的流量和网络请求时间
  2. 节约CPU资源(又为Android机动不动就发热的问题尽了一份力)
  3. 节约内存资源,这个影响不明显,但是蚊子肉也是肉不是

另一个用处,加载缩略图


就像微信一样,在加载图片的时候,先加载一张缩略图,然后再加载大图,这种模式下,我们可以在图片还没完全加载出来的时候大概已经知道它要描述的事物了。
不是说我们刚才已经采用了压缩过图片的方式了么,怎么还要加载缩略图。
使用Glide模仿微信图片加载策略_第5张图片
查看日志,这里因为ViewPager预加载的原因,所以会一次加载4张图片(可以修改默认值),其中2张大图,2张缩略图
我们对原高清图,进行适应手机大小压缩后的大小为
http://o9xuvf3m3.bkt.clouddn.com/new_york.jpg?imageView2/3/h/1134/w/800
他已经已经从原来的2.7M下降到418K,而且界面显示效果和原来一致,成果是喜人的,但是如果在一些网络不好的情况,比如10k/s的2G网络 他任然需要42S才能加载完成。所以我们还想它更快一点,但是图片已经没法优化了,这时候我们可以考虑牺牲一部分图片质量来换取速度的方式呈现
这时候在它加载出来之前先加载缩略图
http://o9xuvf3m3.bkt.clouddn.com/new_york.jpg?imageView2/3/h/50/w/100这里写图片描述
http://o9xuvf3m3.bkt.clouddn.com/peru.jpg?imageView2/3/h/50/w/100这里写图片描述
只有4KB大小的缩略图,对带宽和流量的消耗可以忽略不计,就算是10K/S的网络一秒内也可以加载完成,这时候如果用户判定对这张图不感兴趣,就可以直接切换,我们取消后台加载大图的任务,就可以达到节约带宽的目的。

实现步骤

服务器接口支持

这里的Demo用的API,我直接使用了Saas服务—七牛云。
七牛云图片处理API文档参考这里:http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
很简单,你看几个例子就知道咯
http://o9xuvf3m3.bkt.clouddn.com/france-221.jpg
原图,2560*1026 (宽*高)
http://o9xuvf3m3.bkt.clouddn.com/france-221.jpg?imageView2/3/h/50/w/100
按比例切分,125*50
http://o9xuvf3m3.bkt.clouddn.com/france-221.jpg?imageView2/3/h/100/w/50
按比例切分, 250*100
根据这个规律,很快就能知道它的用法了。

指定图片需要具体尺寸参数

刚才,我们提到需要什么尺寸,手机端发送需要尺寸的url请求到服务器就行了,但是我们怎么知道手机需要什么尺寸呢?手动算吗,控件那么多,那不得算得蛋疼。
所幸Glide提供了这个功能

定义接口

接口主要用来实现,把
http://o9xuvf3m3.bkt.clouddn.com/france-221.jpg 结合Glide测绘的width和height
组装成
http://o9xuvf3m3.bkt.clouddn.com/france-221.jpg?imageView2/3/h/100/w/50的样式

public class CustomImageModelLoader extends BaseGlideUrlLoader<CustomImageSizeModel> {
    public CustomImageModelLoader(Context context) {
        super(context);
    }

    @Override
    protected String getUrl(CustomImageSizeModel model, int width, int height) {
        return model.requestCustomSizeUrl(width, height);
    }
}

实现接口

public class CustomImageSizeModelImp implements CustomImageSizeModel, Parcelable {
    private String baseUrl;
    private static final String TAG = "CustomImageSizeModelImp";

    public CustomImageSizeModelImp(String baseUrl) {
        this.baseUrl = baseUrl;
    }


    @Override
    public String requestCustomSizeUrl(int width, int height) {
        String url = baseUrl + "?imageView2/3/h/" + height + "/w/" + width;
        Log.d(TAG, "requestCustomSizeUrl: " + url);
        return url;
    }

    @Override
    public String getBaseUrl() {
        return baseUrl;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.baseUrl);
    }

    protected CustomImageSizeModelImp(Parcel in) {
        this.baseUrl = in.readString();
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        @Override
        public CustomImageSizeModelImp createFromParcel(Parcel source) {
            return new CustomImageSizeModelImp(source);
        }

        @Override
        public CustomImageSizeModelImp[] newArray(int size) {
            return new CustomImageSizeModelImp[size];
        }
    };
}

这里的Parcelable主要是用来传输的,这里可以忽略不看

定义一个BaseGlideUrlLoader的子类

这个用来,通知Glide,我们load不使用load(url)这种,而是使用如下配置加载

    private List mDatas;
    Glide.with(context)
      .using(new CustomImageModelLoader(mContext))
      .load(mDatas.get(position))
public class CustomImageModelLoader extends BaseGlideUrlLoader<CustomImageSizeModel> {
    public CustomImageModelLoader(Context context) {
        super(context);
    }

    @Override
    protected String getUrl(CustomImageSizeModel model, int width, int height) {
        return model.requestCustomSizeUrl(width, height);
    }
}

最后就像普通加载一样使用

 Glide.with(mContext)
                    .using(new CustomImageModelLoader(mContext))
                    .load(mDatas.get(position))
                    .into(imageHolder.mImageView);

这里的宽高测绘过程,对我们用户完全封闭,由Glide内部自己实现和传递给CustomImageSizeModelImp

先加载缩略图,再加载大图

这里需要使用.thumbnail()操作符,也很简单,使用如下

        void displayImage(final CustomImageSizeModel model, final ImageView imageView, final View loading, final View download) {
            DrawableRequestBuilder thumbnailBuilder = Glide
                    .with(imageView.getContext())
                    .load(new CustomImageSizeModelImp(model
                            .getBaseUrl())
                            .requestCustomSizeUrl(100, 50));

            Glide.with(ImageViewPagerActivity.this)
                    .using(new CustomImageModelLoader(imageView.getContext()))
                    .load(model)
                    .listener(new RequestListener() {
                        @Override
                        public boolean onException(Exception e, CustomImageSizeModel model, Target target, boolean isFirstResource) {
                            return false;
                        }

                        @Override
                        public boolean onResourceReady(GlideDrawable resource, CustomImageSizeModel model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
                            loading.setVisibility(View.GONE);
                            download.setVisibility(View.VISIBLE);
                            PhotoViewAttacher attacher = new PhotoViewAttacher(imageView);
//                            mAttacher.update();
                            return false;
                        }
                    })
                    .thumbnail(thumbnailBuilder)
                    .into(imageView);
        }

手动请求生成一个100*50大小的缩略图,参数值不正确,服务器选择了一个最接近的125*50返回给我们。
这儿.listener()操作符,主要用来监听控件是否绘制完成,好通知进度条隐藏,点击下载高清图按钮呈现出来。

查看原图,下载高清图

这个很好实现吧,4点多了太晚了,睡了
周末愉快~~~

代码下载地址:https://github.com/zhouruikevin/ImageLoadPK

你可能感兴趣的:(Android开发)