开源库GlideAndroid框架

Android图片加载框架——Glide(Glide v4)

2018-08-24  本文已影响2626人  luoqiang108

前言

android中图片加载框架有很多,所有框架最终达到的目都是在Android平台上以极度简单的方式加载和展示图片,如果我们每个都拿来学习和使用的话,这样会极大的浪费我们的时间和经历,所以经过我的一番比较之后,我决定用Glide作为我研究和今后使用的对象。经过一个礼拜的学习和研究之后,我发现Glide是真的强大,通过简单的一行代码就可以展示图片,但是其背后所做的是事却是我们没法想象的,想深入去学习源码的可以去看下郭神的Glide最全解析专栏。本人由于精力和实力的不足,源码就不解析了,郭神的那个系列也写的很详细了,自己看了差不多一个礼拜,可能还是有很多不懂的地方,这里说下看的方法,源码看起来肯定很枯燥,一定要有耐心,不能急,不然可能就看不下去了,一遍看不懂可以多看几遍,来回看慢慢看就会有感觉的。郭神的那个系列第二篇是Glide源码的整个流程,特别长,我的建议可以先跳过去,先看后面几篇关于源码的细节部分,看完再回来看整个流程,这样会好一点。不然第二篇不仅量大而且乱很容易走神,这样一来很可能就看不下去直接放弃了,后面几篇先过下留个大致印象再回过来看整体流程这样会容易一点。还有一个就是可能有人会觉得我会用不就好了,我一个开发人员我没事看懂框架源码干嘛,我又不需要写源码,我要会写我还用这个框架干嘛。这个首先你看了可以增长你技术方面的见识,虽然可能能力增长的也不多,但是最起码开开眼界。其次看懂了源码如果在使用框架过程中有些方面不符合需求,你可以快速在源码的基础上做修改来满足我们的需求。最后还有一点就是看了源码之后可以更好的掌握这个框架的用法,了解的更深,对其内部的实现最起码有个大概的了解,不仅知其然,还知其所以然。好了,前面的话就扯这么多了,在本篇博客中只对Glide(基于Glide v4)用法做个总结,尽量把所有用法总结全,若有没涉及到的欢迎评论补充,不胜感激

进入正题

1. 添加依赖

注意:这里我们要添加两个依赖库,其中compiler库是用于生成Generated API的,后面我们会讲到它。

dependencies {
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}

2. 下载图片需要网络权限,所以在AndroidManifest.xml中声明一下网络权限

<uses-permission android:name="android.permission.INTERNET" />

3. 加载图片

3.1 首先来尝试加载一张网络图片吧,图片地址:http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg
3.2 布局文件:很简单就一个Button和一个用于展示的ImageView,点击Button之后就去加载该张图片,给按钮添加了个点击的方法doClick。布局用的是ConstraintLayout,不懂的自己可以去学习下,之前我的博客android布局和屏幕适配相关里面也链接了两个大神关于这个布局的博客,我都是直接拖拽出来的。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:onClick="doClick"
        android:text="加载图片"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />
</android.support.constraint.ConstraintLayout>
3.3 为了点击Button的时候能够将刚才的图片显示在ImageView上,需要修改MainActivity中的代码,如下所示:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import com.bumptech.glide.Glide;

public class MainActivity extends AppCompatActivity {

    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
    }

    public void doClick(View view) {
        String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
        Glide.with(this).load(url).into(imageView);
    }
}
3.4 就一行代码就搞定了,就是这么简单,我们来运行下程序,运行效果如下图所示: 加载图片.gif

可以看到,一张网络上的图片已经被成功下载,并且展示到ImageView上了。而我们到底做了什么?实际上核心的代码就只有这一行而已:

Glide.with(this).load(url).into(imageView);

千万不要小看这一行代码,实际上仅仅就这一行代码,你已经可以做非常非常多的事情了,包括加载网络上的图片、加载手机本地的图片、加载应用资源中的图片等等。

3.5 下面我们就来详细解析一下这行代码。
  1. 首先,调用Glide.with()方法用于创建一个加载图片的实例。with()方法可以接收Context、Activity或者Fragment类型的参数。也就是说我们选择的范围非常广,不管是在Activity还是Fragment中调用with()方法,都可以直接传this。那如果调用的地方既不在Activity中也不在Fragment中呢?也没关系,我们可以获取当前应用程序的ApplicationContext,传入到with()方法当中。注意with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。
  2. 接下来看一下load()方法,这个方法用于指定待加载的图片资源。Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等。因此load()方法也有很多个方法重载,除了我们刚才使用的加载一个字符串网址之外,你还可以这样使用load()方法:
// 加载本地图片
File file = new File(getExternalCacheDir() + "/image.jpg");
Glide.with(this).load(file).into(imageView);

// 加载应用资源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);

// 加载二进制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);

// 加载Uri对象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);
  1. 最后看一下into()方法,这个方法就很简单了,我们希望让图片显示在哪个ImageView上,把这个ImageView的实例传进去就可以了。当然,into()方法不仅仅是只能接收ImageView类型的参数,还支持很多更丰富的用法,不过那个属于高级技巧,我们后面再说。
  2. 那么回顾一下Glide最基本的使用方式,其实就是关键的三步走:先with(),再load(),最后into()。熟记这三步,你就已经入门Glide了。
3. 6 设置加载动画
Glide.with(this)
                .load(url)
                //transition(TransitionOptions transitionOptions)
                .transition(DrawableTransitionOptions.withCrossFade())
                .into(imageView);
  1. GenericTransitionOptions 通用型
  2. DrawableTransitionOptions
  3. BitmapTransitionOptions
RequestOptions options = new RequestOptions()
                .dontTransform();
3. 7 设置下载优先级
RequestOptions options = new RequestOptions()
                .priority(Priority.NORMAL);

4. 占位图

4.1 加载前占位图
        RequestOptions options = new RequestOptions()
                .placeholder(R.mipmap.ic_launcher);
        Glide.with(this)
                .load(url)
                .apply(options)
                .into(imageView);
        RequestOptions options = new RequestOptions()
                .placeholder(R.mipmap.ic_launcher)
                .diskCacheStrategy(DiskCacheStrategy.NONE);
        Glide.with(this)
                .load(url)
                .apply(options)
                .into(imageView);
4.2 异常占位图
        RequestOptions options = new RequestOptions()
                .placeholder(R.mipmap.ic_launcher)
                .error(R.drawable.error)
                .diskCacheStrategy(DiskCacheStrategy.NONE);
        Glide.with(this)
                .load(url)
                .apply(options)
                .into(imageView);

5. 指定图片加载格式

http://p1.pstatp.com/large/166200019850062839d3

现在重新运行一下代码,效果如下图所示: 加载图片3.gif
Glide.with(this)
                .asBitmap()
                .load(url)
                .apply(options)
                .into(imageView);

6. 指定图片大小

        RequestOptions options = new RequestOptions()
                .override(100, 100);
        Glide.with(this)
                .load(url)
                .apply(options)
                .into(imageView);
        RequestOptions options = new RequestOptions()
                override(Target.SIZE_ORIGINAL);
        Glide.with(this)
                .load(url)
                .apply(options)
                .into(imageView);

7. 缓存机制

7.1 首先来看内存缓存
RequestOptions options = new RequestOptions()
        .skipMemoryCache(true);
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);
7.2 接下来我们开始学习硬盘缓存方面的内容
RequestOptions options = new RequestOptions()
        .diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);
  1. DiskCacheStrategy.NONE: 表示不缓存任何内容。
  2. DiskCacheStrategy.DATA: 表示只缓存原始图片。
  3. DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
  4. DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
  5. DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
7.3 清理缓存,注意清理磁盘缓存因为涉及到文件操作不能放主线程中
//清理内存缓存 可以在UI主线程中进行
Glide.get(this).clearMemory();
//清理磁盘缓存 需要在子线程中执行
new Thread(new Runnable() {
    @Override
    public void run() {
        Glide.get(MainActivity.this).clearDiskCache();
    }
}).start();
7.4 读取磁盘缓存数据大小
import android.content.Context;

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

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

public class GlideDiskCacheUtil {
    /**
     * 获取Glide造成的磁盘缓存大小
     * @return DiskCacheSize
     */
    public static String getDiskCacheSize(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 static 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 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";
    }
}
7.5 高级技巧(图片的url中带用于保护图片资源的变化的token)

http://url.com/image.jpg?token=d9caa6e02c990b0a

import com.bumptech.glide.load.model.GlideUrl;

public class MyGlideUrl extends GlideUrl {

    private String mUrl;

    public MyGlideUrl(String url) {
        super(url);
    }

    @Override
    public String getCacheKey() {
        //将token部分参数替换为空字符串后返回作为缓存Key
        return mUrl.replace(findTokenParam(), "");
    }

    /**
     * 查找token部分参数的方法
     * @return token部分参数String
     */
    private String findTokenParam() {
        String tokenParam = "";
        int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
        if (tokenKeyIndex != -1) {
            int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
            if (nextAndIndex != -1) {
                tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
            } else {
                tokenParam = mUrl.substring(tokenKeyIndex);
            }
        }
        return tokenParam;
    }
}
Glide.with(this)
                .load(new MyGlideUrl(url))
                .into(imageView);

8. 回调与监听

8.1 into()方法

SimpleTarget,它是一种极为简单的Target,我们使用它可以将Glide加载出来的图片对象 resource获取到,而不是像之前那样只能将图片在ImageView上显示出来,虽然我们下面实现的也是将图片在ImageView上显示,但是我们能拿到图片对象resource,有了这个对象之后就可以使用它进行任意的逻辑操作了。
SimpleTarget<Drawable> simpleTarget = new SimpleTarget<Drawable>() {
    @Override
    public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
        imageView.setImageDrawable(resource);
    }
};

public void loadImage(View view) {
    Glide.with(this)
         .load("https://www.baidu.com/img/bd_logo1.png")
         .into(simpleTarget);
}
ViewTarget要复杂一点,这里我们通过一个例子来演示一下吧,比如我创建了一个自定义布局MyLayout,代码如下所示:
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.LinearLayout;

import com.bumptech.glide.request.target.ViewTarget;
import com.bumptech.glide.request.transition.Transition;

public class MyLayout extends LinearLayout{
    private ViewTarget<MyLayout,Drawable> viewTarget;

    public MyLayout(Context context) {
        this(context,null);
    }
    public MyLayout(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }
    public MyLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        viewTarget = new ViewTarget<MyLayout,Drawable>(this) {
            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
                MyLayout myLayout = getView();
                myLayout.setBackgroundDrawable(resource);
            }
        };
    }

    public ViewTarget<MyLayout,Drawable> getTarget() {
        return viewTarget;
    }
}
public class MainActivity extends AppCompatActivity {
    private MyLayout myLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myLayout = (MyLayout) findViewById(R.id.background);
    }

    public void loadImage(View view) {
        String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
        Glide.with(this)
             .load(url)
             .into(myLayout.getTarget());
    }
}

8.2 preload()方法

Glide.with(this)
     .load("https://www.baidu.com/img/bd_logo1.png")
     .preload();
Glide.with(this)
     .load("https://www.baidu.com/img/bd_logo1.png")
     .into(imageView);

8.3 submit()方法

  1. submit():下载原始尺寸的图片。
  2. submit(int width, int height):下载指定尺寸的图片。
private void downloadImage() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                String url = "http://p1.pstatp.com/large/166200019850062839d3";
                final Context context = getApplicationContext();
                FutureTarget<File> target = Glide.with(context)
                        .asFile()
                        .load(url)
                        .submit();
                final File imageFile = target.get();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, imageFile.getPath(), Toast.LENGTH_LONG).show();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

8.4 下载图片并保存到指定路径

submit方法可以下载图片并获取到图片缓存路径路径,但是不知道大家有没有这样的需要就是想让图片下载到指定的路径,因为这样我们之后快速使用这部分图片,也方便对这部分图片进行管理,同时不需要受限于Glide的磁盘缓存机制,因为如果由Glide自动管理缓存的话,当下载的图片超过设定的缓存大小,一些比如长时间不使用的图片就会被Glide删除,但是其实这张图片我们之后还是需要使用的,这就很尴尬了,所以我们需要把图片下载到我们指定的位置,由我们自己来进行管理。代码如下:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.Target;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class DownloadImage implements Runnable{
    //要下载图片的url
    private String url;
    //Glide下载所需的Context,最好用ApplicationContext,
    //如果再用Activity作为Context了,可能会有Activity销毁了但子线程下载还没执行完这种情况出现。
    private Context context;
    //指定下载宽度,如果想下载原始宽带指定为0
    private int width;
    //指定下载高度,如果想下载原始高带指定为0
    private int height;
    //指定下载位置
    private File mFile;
    //下载完之后的回调
    private ImagedownLoadCallBack callBack;
    public interface ImagedownLoadCallBack{
        void onDownLoadSuccess(Bitmap bitmap);
        void onDownLoadFailed();
    }

    //用于回调到主线程的Handler,便于在回调回去的方法中执行UI操作
    private Handler mHandler;

    public DownloadImage(String url, Context context, int width, int height, File mFile, ImagedownLoadCallBack callBack) {
        this.url = url;
        this.context = context;
        this.width = width;
        this.height = height;
        this.mFile = mFile;
        this.callBack = callBack;
        mHandler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void run() {
        Bitmap bitmap = null;
        FileOutputStream fos = null;
        try {
            if (width==0){
                width = Target.SIZE_ORIGINAL;
            }
            if (height==0){
                height = Target.SIZE_ORIGINAL;
            }
            bitmap = Glide.with(context)
                    .asBitmap()
                    .load(url)
                    .submit(width,height)
                    .get();
            if (bitmap != null){
                //上级文件夹不存在则创建
                if (!mFile.getParentFile().exists()){
                    mFile.getParentFile().mkdirs();
                }
                //文件不存在则创建
                if (!mFile.exists()){
                    mFile.createNewFile();
                }
                fos = new FileOutputStream(mFile);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bitmap != null && mFile.exists()) {
                final Bitmap finalBitmap = bitmap;
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callBack.onDownLoadSuccess(finalBitmap);
                    }
                });
            } else {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callBack.onDownLoadFailed();
                    }
                });
            }
        }
    }
}
public void doClick(View view) {
        String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
        File mFile = new File(Environment.getExternalStorageDirectory()+File.separator+"Glide","glideDownload.png");
        DownloadImage downloadImage = new DownloadImage(url, getApplicationContext(), 600, 600, mFile, new DownloadImage.ImagedownLoadCallBack() {
            @Override
            public void onDownLoadSuccess(Bitmap bitmap) {
                Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onDownLoadFailed() {
                Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
            }
        });
        new Thread(downloadImage).start();
    }

注意:这里submit(width,height)方法指定宽高后下载下来的图片尺寸并不是完全按我们指定尺寸来的,Glide是会做处理的,会保留原始尺寸的宽高比,以缩小比例较小边取我们指定的值的大小。比如上面我们下载的那张图片原始尺寸是1920 * 1080,我们指定尺寸为600 * 600,最后下载下来的图片尺寸是1067 * 600(宽是缩小比例较小边(1080/600<1920/600)取指定值600,长则按长宽比不变进行计算求得,计算过程:长=600 * (1920/1080)=1067)。除此之外我还试了下override()方法和preload()方法指定尺寸Glide是否也做了同样的处理,结果是是的。其实这如果源码读的很细应该是可以看出来的,不过这也确实有点太细节了,毕竟源码篇幅还是很多的,这种细节还是挺难注意到的,如果有精力可以找这部分去精读下,理解会更深。

8.5 listener()方法

             Glide.with(this)
                .load("http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg")
                .listener(new RequestListener<Drawable>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                        return false;
                    }
                    @Override
                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                        return false;
                    }
                })
                .into(imageView);

9. 图片变换功能

RequestOptions options = new RequestOptions()
        .transforms(...);
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);
  RequestOptions options = new RequestOptions()
                .dontTransform();
RequestOptions options = new RequestOptions()
        .centerCrop();

RequestOptions options = new RequestOptions()
        .fitCenter();

RequestOptions options = new RequestOptions()
        .circleCrop();
String url = "http://guolin.tech/book.png";
RequestOptions options = new RequestOptions()
        .circleCrop();
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);

重新运行一下程序并点击加载图片按钮,效果如下图所示。


加载图片4.gif
dependencies {
    implementation 'jp.wasabeef:glide-transformations:3.0.1'
}
String url = "http://guolin.tech/book.png";
RequestOptions options = new RequestOptions()
        .transforms(new BlurTransformation(), new GrayscaleTransformation());
Glide.with(this)
     .load(url)
     .apply(options)
     .into(imageView);

可以看到,同时执行多种图片变换的时候,只需要将它们都传入到transforms()方法中即可。现在重新运行一下程序,效果如下图所示。


加载图片5.gif

10. 自定义模块

import android.content.Context;
import android.support.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;

@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    //更改Glide配置
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
    }
    //替换Glide组件
    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        super.registerComponents(context, glide, registry);
    }
}

更改Glide配置

  • setMemoryCache()
    用于配置Glide的内存缓存策略,默认配置是LruResourceCache。
  • setBitmapPool()
    用于配置Glide的Bitmap缓存池,默认配置是LruBitmapPool。
  • setDiskCache()
    用于配置Glide的硬盘缓存策略,默认配置是InternalCacheDiskCacheFactory。
  • setDiskCacheExecutor()
    用于配置Glide读取缓存中图片的异步执行器,默认配置是FifoPriorityThreadPoolExecutor,也就是先入先出原则。
  • setResizeService()
    用于配置Glide读取非缓存中图片的异步执行器,默认配置也是FifoPriorityThreadPoolExecutor。
  • setDefaultRequestOptions()
    用于配置Glide加载图片的默认请求选项,其中解码模式的配置就包含在里面,默认配置是RGB_565。
将加载的图片缓存到SD卡
//更改Glide配置
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
    }
修改Glide默认的缓存大小
    private static final long DISK_CACHE_SIZE = 500*1024*1024;
    //更改Glide配置
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
    }
String url = "http://guolin.tech/book.png";
Glide.with(this)
     .load(url)
     .into(imageView);
修改Glide加载图片的格式为ARGB_8888
private static final long DISK_CACHE_SIZE = 500*1024*1024;
    //更改Glide配置
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
        builder.setDefaultRequestOptions(
                new RequestOptions()
                        .format(DecodeFormat.PREFER_ARGB_8888)
        );
    }

替换Glide组件(替换HTTP通讯组件)

dependencies {
     implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}
import android.support.annotation.NonNull;
import android.util.Log;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.HttpException;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;
import com.bumptech.glide.util.Preconditions;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class OkHttpFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
    private static final String TAG = "OkHttpFetcher";
    private final Call.Factory client;
    private final GlideUrl url;
    private InputStream stream;
    private ResponseBody responseBody;
    private DataFetcher.DataCallback<? super InputStream> callback;
    private volatile Call call;

    @SuppressWarnings("WeakerAccess")
    public OkHttpFetcher(Call.Factory client, GlideUrl url) {
        this.client = client;
        this.url = url;
    }

    @Override
    public void loadData(@NonNull Priority priority,
                         @NonNull final DataCallback<? super InputStream> callback) {
        Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
            String key = headerEntry.getKey();
            requestBuilder.addHeader(key, headerEntry.getValue());
        }
        Request request = requestBuilder.build();
        this.callback = callback;

        call = client.newCall(request);
        call.enqueue(this);
    }

    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "OkHttp failed to obtain result", e);
        }

        callback.onLoadFailed(e);
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) {
        responseBody = response.body();
        if (response.isSuccessful()) {
            long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
            stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
            callback.onDataReady(stream);
        } else {
            callback.onLoadFailed(new HttpException(response.message(), response.code()));
        }
    }

    @Override
    public void cleanup() {
        try {
            if (stream != null) {
                stream.close();
            }
        } catch (IOException e) {
            // Ignored
        }
        if (responseBody != null) {
            responseBody.close();
        }
        callback = null;
    }

    @Override
    public void cancel() {
        Call local = call;
        if (local != null) {
            local.cancel();
        }
    }

    @NonNull
    @Override
    public Class<InputStream> getDataClass() {
        return InputStream.class;
    }

    @NonNull
    @Override
    public DataSource getDataSource() {
        return DataSource.REMOTE;
    }
}
import android.support.annotation.NonNull;

import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;

import java.io.InputStream;

import okhttp3.Call;
import okhttp3.OkHttpClient;

public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
    private final Call.Factory client;

    @SuppressWarnings("WeakerAccess")
    public OkHttpGlideUrlLoader(@NonNull Call.Factory client) {
        this.client = client;
    }

    @Override
    public boolean handles(@NonNull GlideUrl url) {
        return true;
    }

    @Override
    public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
                                               @NonNull Options options) {
        return new LoadData<>(model, new OkHttpFetcher(client, model));
    }

    @SuppressWarnings("WeakerAccess")
    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
        private static volatile Call.Factory internalClient;
        private final Call.Factory client;

        private static Call.Factory getInternalClient() {
            if (internalClient == null) {
                synchronized (OkHttpGlideUrlLoader.Factory.class) {
                    if (internalClient == null) {
                        internalClient = new OkHttpClient();
                    }
                }
            }
            return internalClient;
        }

        public Factory() {
            this(getInternalClient());
        }

        public Factory(@NonNull Call.Factory client) {
            this.client = client;
        }

        @NonNull
        @Override
        public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
            return new OkHttpGlideUrlLoader(client);
        }

        @Override
        public void teardown() {}
    }
}
import android.content.Context;
import android.support.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;

import java.io.InputStream;

@GlideModule
public class MyAppGlideModule extends AppGlideModule {

    private static final long DISK_CACHE_SIZE = 500*1024*1024;
    //更改Glide配置
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
        builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
        builder.setDefaultRequestOptions(
                new RequestOptions()
                        .format(DecodeFormat.PREFER_ARGB_8888)
        );
    }
    //替换Glide组件
    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        super.registerComponents(context, glide, registry);
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
    }
}

更简单的组件替换

implementation "com.github.bumptech.glide:okhttp3-integration:4.7.1"
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation "com.github.bumptech.glide:volley-integration:4.7.1"
implementation 'com.mcxiaoke.volley:library:1.0.19'  

实现下载进度监听

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class ProgressInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        return response;
    }
}
import android.content.Context;
import android.support.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;

import java.io.InputStream;

import okhttp3.OkHttpClient;

@GlideModule
public class MyAppGlideModule extends AppGlideModule {

    private static final long DISK_CACHE_SIZE = 500*1024*1024;
    //更改Glide配置
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
    }
    //替换Glide组件
    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        super.registerComponents(context, glide, registry);
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(new ProgressInterceptor());
        OkHttpClient okHttpClient = builder.build();
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));
    }
}
public interface ProgressListener {
    void onProgress(int progress);
}
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class ProgressInterceptor implements Interceptor {
    static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();

    public static void addListener(String url, ProgressListener listener) {
        LISTENER_MAP.put(url, listener);
    }

    public static void removeListener(String url) {
        LISTENER_MAP.remove(url);
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        return response;
    }
}
import android.util.Log;

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

public class ProgressResponseBody extends ResponseBody {
    private static final String TAG = "ProgressResponseBody";

    private BufferedSource bufferedSource;

    private ResponseBody responseBody;

    private ProgressListener listener;

    public ProgressResponseBody(String url, ResponseBody responseBody) {
        this.responseBody = responseBody;
        listener = ProgressInterceptor.LISTENER_MAP.get(url);
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
        }
        return bufferedSource;
    }

    private class ProgressSource extends ForwardingSource {

        long totalBytesRead = 0;

        int currentProgress;

        ProgressSource(Source source) {
            super(source);
        }

        @Override
        public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead = super.read(sink, byteCount);
            long fullLength = responseBody.contentLength();
            if (bytesRead == -1) {
                totalBytesRead = fullLength;
            } else {
                totalBytesRead += bytesRead;
            }
            int progress = (int) (100f * totalBytesRead / fullLength);
            Log.d(TAG, "download progress is " + progress);
            if (listener != null && progress != currentProgress) {
                listener.onProgress(progress);
            }
            if (listener != null && totalBytesRead == fullLength) {
                listener = null;
            }
            currentProgress = progress;
            return bytesRead;
        }
    }
}
public class ProgressInterceptor implements Interceptor { 

    ... 

    @Override 
    public Response intercept(Chain chain) throws IOException { 
        Request request = chain.request(); 
        Response response = chain.proceed(request); 
        String url = request.url().toString(); 
        ResponseBody body = response.body(); 
        Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
        return newResponse; 
    } 
}

11. 使用Generated API

使用和Glide 3一样的流式API接口

GlideApp.with(this)
        .load(url)
        .placeholder(R.drawable.loading)
        .error(R.drawable.error)
        .skipMemoryCache(true)
        .diskCacheStrategy(DiskCacheStrategy.NONE)
        .override(Target.SIZE_ORIGINAL)
        .circleCrop()
        .into(imageView);

对现有的API进行扩展,定制出任何属于你自己的API

import com.bumptech.glide.annotation.GlideExtension;
import com.bumptech.glide.annotation.GlideOption;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
@GlideExtension
public class MyGlideExtension {
    private MyGlideExtension() {

    }
    @GlideOption
    public static void cacheSource(RequestOptions options) {
        options.diskCacheStrategy(DiskCacheStrategy.DATA);
    }
}
GlideApp.with(this)
        .load(url)
        .cacheSource()
        .into(imageView);

有了这个强大的功能之后,我们使用Glide就能变得更加灵活了。

结束语

到此为止,Glide 4的用法我基本也总结完了。这篇博客也是我写博客以来不论是篇幅和用时都是最长的的一篇,如果你能从头看到这里你对Glide的用法也可以说是深度掌握了,基本上可以满足你项目上的所有要求了。同时你能坚持看下来你也是很棒的哦,加油吧,少年!

上一篇 下一篇

猜你喜欢

热点阅读