Android Glide 的使用总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/w627947015/article/details/56666145

Android Glide 的使用总结


初识

在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是bumptech。这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会上发布的官方app。

之前很多同学都看到过这篇介绍Glide的文章,原文在这里。文中从各个方面介绍和比较了Glide与Picasso,总体来说二者极为相似,有着近乎相同的API的使用风格。但Glide在缓存策略和加载GIF方面略胜一筹。最后作者也极力推荐了这个库。

学习


推荐一个Glide系列是文章,全面对Glide的了解,Glide - 系列综述

使用总结


遇到的问题


在使用过程中发现了一些问题,也在其他博客上看到了相关的解决方案,例如:

  1. 为什么 有的图片第一次加载的时候只显示占位图,第二次才显示正常的图片呢?
  2. 为什么 我总会得到类似You cannot start a load for a destroyed activity这样的异常呢?
  3. 为什么 我不能给加载的图片setTag()呢?

第一个问题第一次只显示占位符:

            Glide.with(AppContext.context())
                .load(url)
                .placeholder(R.mipmap.placeholdermid) //占位符
                .error(R.mipmap.placeholdermid)       //错误占位符
                .dontAnimate()//没有任何淡入淡出效果
                .override(640, 428)//调整图片大小
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .priority(Priority.HIGH)//优先级
                .into(iv);

AppContext.context() 这里是对Application封装获取getApplicationContext的一个单例。

这是我在listview当中使用到的相关配置选项,当我发现有时候只需改一改Glide加载的相关选项就能解决掉这个只显示占位符的问题。
其他的解决方案:

  • 不使用Glide的默认动画;
  • 不设置占位;
  • 使用Glide的Transformation API自定义圆形Bitmap的转换,例如这里

第二个问题:

不要再非主线程里面使用Glide加载图片,如果真的使用了,请把context参数换成getApplicationContext。上面的使用方式上我全部使用getApplicationContext这个上下文。

第三个问题:

从Glide的3.6.0之后,新添加了全局设置的方法。具体方法如下:
先实现GlideMoudle接口,全局设置ViewTaget的tagId:

public class MyGlideMoudle implements GlideModule{
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        ViewTarget.setTagId(R.id.glide_tag_id);
    }

    @Override
    public void registerComponents(Context context, Glide glide) {

    }
}

同样,也需要在ids.xml下添加id

<item name="glide_tag_id" type="id"/>

最后在AndroidManifest.xml文件里面添加

<meta-data
    android:name="com.yourpackagename.MyGlideMoudle"
    android:value="GlideModule" />

Glide获取缓存大小并清除缓存


清除Glide缓存

Glide自带清除缓存的功能;

Glide.get(context).clearDiskCache();//(清除磁盘缓存)
Glide.get(context).clearMemory();//(清除内存缓存)

其中clearDiskCache()方法必须运行在子线程,clearMemory()方法必须运行在主线程,这是这两个方法所强制要求的,详见源码。

获取Glide缓存空间大小

以下方法适合在Glide为默认的缓存目录的情况,不论是内部存储空间还是外部。因为我们可以通过

InternalCacheDiskCacheFactory.DEFAULT_DISK_CACHE_DIR
ExternalCacheDiskCacheFactory.DEFAULT_DISK_CACHE_DIR

获取到位于内部与外部存储的缓存文件夹的文件夹名,并通过

context.getCacheDir()
context.getExternalCacheDir()

获取内部与外部存储的路径.进而可以通过遍历文件夹内的文件进行缓存文件大小求和与全部清除。

工具类如下:

import android.content.Context;
import android.os.Looper;
import android.text.TextUtils;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.cache.ExternalCacheDiskCacheFactory;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;

import java.io.File;
import java.math.BigDecimal;

/**
 * Created by wman on 2017/2/23.
 */

public class GlideCacheUtil {

    private static GlideCacheUtil inst;

    public static GlideCacheUtil getInstance() {
        if (inst == null) {
            inst = new GlideCacheUtil();
        }
        return inst;
    }

    /**
     * 清除图片磁盘缓存
     */
    public void clearImageDiskCache(final Context context) {
        try {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Glide.get(context).clearDiskCache();
                        //BusUtil.getBus().post(new GlideCacheClearSuccessEvent());
                    }
                }).start();
            } else {
                Glide.get(context).clearDiskCache();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清除图片内存缓存
     */
    public void clearImageMemoryCache(Context context) {
        try {
            if (Looper.myLooper() == Looper.getMainLooper()) { //只能在主线程执行
                Glide.get(context).clearMemory();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清除图片所有缓存
     */
    public void clearImageAllCache(Context context) {
        clearImageDiskCache(context);
        clearImageMemoryCache(context);
        String ImageExternalCatchDir = context.getExternalCacheDir() + ExternalCacheDiskCacheFactory.DEFAULT_DISK_CACHE_DIR;
        deleteFolderFile(ImageExternalCatchDir, true);
    }

    /**
     * 获取Glide造成的缓存大小
     *
     * @return CacheSize
     */
    public String getCacheSize(Context context) {
        try {
            return getFormatSize(getFolderSize(new File(context.getCacheDir() + "/" + InternalCacheDiskCacheFactory.DEFAULT_DISK_CACHE_DIR)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 获取指定文件夹内所有文件大小的和
     *
     * @param file file
     * @return size
     * @throws Exception
     */
    private long getFolderSize(File file) throws Exception {
        long size = 0;
        try {
            File[] fileList = file.listFiles();
            for (File aFileList : fileList) {
                if (aFileList.isDirectory()) {
                    size = size + getFolderSize(aFileList);
                } else {
                    size = size + aFileList.length();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return size;
    }

    /**
     * 删除指定目录下的文件,这里用于缓存的删除
     *
     * @param filePath       filePath
     * @param deleteThisPath deleteThisPath
     */
    private void deleteFolderFile(String filePath, boolean deleteThisPath) {
        if (!TextUtils.isEmpty(filePath)) {
            try {
                File file = new File(filePath);
                if (file.isDirectory()) {
                    File files[] = file.listFiles();
                    for (File file1 : files) {
                        deleteFolderFile(file1.getAbsolutePath(), true);
                    }
                }
                if (deleteThisPath) {
                    if (!file.isDirectory()) {
                        file.delete();
                    } else {
                        if (file.listFiles().length == 0) {
                            file.delete();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 格式化单位
     *
     * @param size size
     * @return size
     */
    private static String getFormatSize(double size) {
        double kiloByte = size / 1024;
        if (kiloByte < 1) {
            return size + "Byte";
        }
        double megaByte = kiloByte / 1024;
        if (megaByte < 1) {
            BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
            return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB";
        }
        double gigaByte = megaByte / 1024;
        if (gigaByte < 1) {
            BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
            return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB";
        }
        double teraBytes = gigaByte / 1024;
        if (teraBytes < 1) {
            BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
            return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";
        }
        BigDecimal result4 = new BigDecimal(teraBytes);
        return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";
    }
}

利用策略模式对图片加载的封装


这个段落的答案,摘抄自Stormzhang的文章 如何正确使用开源项目?

对于开源项目,我们知道有些库设计的确实很棒,使用者调用起来非常方便,一行代码直接搞定,拿图片加载库 Glide 举个例子:

Glide.with(context)
    .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
    .into(ivImg);

使用起来是不是特简单?你也许问我,都封装的这么好了还用得着再封装一层么?那你错了,哪怕他已经很完美了,我都会这么做:

public class ImageLoader {
    public static void with(Context context, String imageUrl, ImageView imageView) {
        Picasso.with(context).load(imageUrl).into(imageView); 
    }
}

这样我所有项目调用的方式直接就是 ImageLoader.with() ,这样做的好处是:

入口统一,所有图片加载都在这一个地方管理,一目了然,即使有什么改动我也只需要改这一个类就可以了。

具体的实现详见这边文章:网络图片加载的封装【从零开始搭建android框架系列(4)】

  • ImageUtil类

可以看到ImageUtil提供的是单例模式

public class ImageLoaderUtil {

    public static final int PIC_LARGE = 0;
    public static final int PIC_MEDIUM = 1;
    public static final int PIC_SMALL = 2;

    public static final int LOAD_STRATEGY_NORMAL = 0;
    public static final int LOAD_STRATEGY_ONLY_WIFI = 1;

    private static ImageLoaderUtil mInstance;
    private BaseImageLoaderStrategy mStrategy;

    public ImageLoaderUtil() {
        mStrategy = new GlideImageLoaderStrategy();
    }

    //single instance
    public static ImageLoaderUtil getInstance() {
        if (mInstance == null) {
            synchronized (ImageLoaderUtil.class) {
                if (mInstance == null) {
                    mInstance = new ImageLoaderUtil();
                    return mInstance;
                }
            }
        }
        return mInstance;
    }


    public void loadImage(Context context, ImageLoader img) {
        mStrategy.loadImage(context, img);
    }

    public void setLoadImgStrategy(BaseImageLoaderStrategy strategy) {
        mStrategy = strategy;
    }
}

  • BaseImageLoaderProvider类

可以看到我们ImageUtil中是采用这个类的loadImage方法去加载图片的。这里是一个接口。由具体的子类(GlideImageLoaderProvider)去实现loadImage方法。

public interface BaseImageLoaderStrategy {
    void loadImage(Context ctx, ImageLoader img);
}

  • GlideImageLoaderProvider类

是BaseImageLoaderProvider的实现类,完成具体的加载图片操作。这里面会有wifi下加载图片的判断。具体判断将放在util工具类中进行实现。这里也是利用图片加载库Glide进行实现。后期如果工程项目决定使用其他的图片加载框架,当然可以采用其他类继承BaseImageLoaderProvider。

public class GlideImageLoaderStrategy implements BaseImageLoaderStrategy {
    @Override
    public void loadImage(Context ctx, ImageLoader img) {
        int strategy = img.getWifiStrategy();
        if (strategy == ImageLoaderUtil.LOAD_STRATEGY_ONLY_WIFI) {
            int netType = AppContext.getNetworkType();
            //如果是在wifi下才加载图片,并且当前网络是wifi,直接加载
            if (1 == netType) {
                loadNormal(ctx, img);
            } else {
                //如果是在wifi下才加载图片,并且当前网络不是wifi,加载缓存
                loadCache(ctx, img);
            }
        } else {
            //如果不是在wifi下才加载图片
            loadNormal(ctx, img);
        }
    }


    /**
     * load image with Glide
     */
    private void loadNormal(Context ctx, ImageLoader img) {
        Glide.with(ctx)
                .load(img.getUrl())
                .placeholder(img.getPlaceHolder()) //占位符
                .error(img.getPlaceHolder())       //错误占位符
                .crossFade()//平滑和养眼动画默认的持续时间是 100毫秒
                .dontAnimate()//没有任何淡入淡出效果
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .priority(Priority.HIGH)//优先级
                .into(img.getImgView());
    }


    /**
     * load cache image with Glide
     */
    private void loadCache(Context ctx, ImageLoader img) {
        Glide.with(ctx).using(new StreamModelLoader<String>() {
            @Override
            public DataFetcher<InputStream> getResourceFetcher(final String model, int i, int i1) {
                return new DataFetcher<InputStream>() {
                    @Override
                    public InputStream loadData(Priority priority) throws Exception {
                        throw new IOException();
                    }

                    @Override
                    public void cleanup() {

                    }

                    @Override
                    public String getId() {
                        return model;
                    }

                    @Override
                    public void cancel() {

                    }
                };
            }
        }).load(img.getUrl())
                .placeholder(img.getPlaceHolder()) //占位符
                .error(img.getPlaceHolder())       //错误占位符
                .crossFade()//平滑和养眼动画默认的持续时间是 100毫秒
                .dontAnimate()//没有任何淡入淡出效果
                .override(640, 428)//调整图片大小
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .priority(Priority.HIGH)//优先级
                .into(img.getImgView());
    }
}

  • ImageLoader类

在ImageUtil的load方法中进行图片加载,第一个参数是Context,那么第二个参数呢?正是这里的ImageLoader,采用Builder建造者模式。Builder模式可以将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以构建不同的对象。

因为在图片加载中,会处理到的数据必定有图片的url,必定有ImageView的实例,可能有加载策略(是否wifi下加载),可能有图片加载类型(大图,中图,小图),也会有图片加载没有成功时候的占位符。那么这么多数据操作,所以用到了Builder模式,一步一步的创建一个复杂对象的创建者模式,它允许用户在不知道内部构建细节的情况下,可以更精细的控制对象的构建流程。比如这里的ImageLoader。

public class ImageLoader {
    private int type;           //类型 (大图,中图,小图)
    private String url;         //需要解析的url
    private int placeHolder;    //当没有成功加载的时候显示的图片
    private ImageView imgView;  //ImageView的实例
    private int wifiStrategy;   //加载策略,是否在wifi下才加载

    private ImageLoader(Builder builder) {
        this.type = builder.type;
        this.url = builder.url;
        this.placeHolder = builder.placeHolder;
        this.imgView = builder.imgView;
        this.wifiStrategy = builder.wifiStrategy;
    }

    public int getType() {
        return type;
    }

    public String getUrl() {
        return url;
    }

    public int getPlaceHolder() {
        return placeHolder;
    }

    public ImageView getImgView() {
        return imgView;
    }

    public int getWifiStrategy() {
        return wifiStrategy;
    }

    public static class Builder {
        private int type;
        private String url;
        private int placeHolder;
        private ImageView imgView;
        private int wifiStrategy;

        public Builder() {
            this.type = ImageLoaderUtil.PIC_SMALL;
            this.url = "";
            this.placeHolder = R.mipmap.ic_launcher;
            this.imgView = null;
            this.wifiStrategy = ImageLoaderUtil.LOAD_STRATEGY_NORMAL;
        }

        public Builder type(int type) {
            this.type = type;
            return this;
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder placeHolder(int placeHolder) {
            this.placeHolder = placeHolder;
            return this;
        }

        public Builder imgView(ImageView imgView) {
            this.imgView = imgView;
            return this;
        }

        public Builder strategy(int strategy) {
            this.wifiStrategy = strategy;
            return this;
        }

        public ImageLoader build() {
            return new ImageLoader(this);
        }
    }
}

ImageLoader的公共访问入口,以后使用的方式,将会是:

            ImageLoaderUtil.getInstance().loadImage(context, new ImageLoader.Builder()
                .url(url)
                .placeHolder(resourceId)
                .imgView(iv)
                .build());

如果需要其他图片加载策略,比如PicassoImageLoaderStrategy使用Picasso框架来加载图片,实现BaseImageLoaderStrategy的loadImage接口即可。

猜你喜欢

转载自blog.csdn.net/w627947015/article/details/56666145