Skip to content

Latest commit

 

History

History
477 lines (360 loc) · 19.3 KB

Glide简介(下).md

File metadata and controls

477 lines (360 loc) · 19.3 KB

Glide简介(下)

image

官网:GlideMaven Central

Glide的回调:Targets

假设我们不想将加载的图片显示到ImageView上,而是只想得到对应的BitmapGlide提供了一种可以通过Targets获取Bitmap的方法。 Targetcallback没什么不同,都是在通过Glide的异步线程下载和处理后返回结果。

Glide提供了多个不同目的的targets,我们先从BaseTarget开始。

BaseTarget
private BaseTarget target = new BaseTarget<BitmapDrawable>() {  
  @Override
  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {
    // do something with the bitmap
    // for demonstration purposes, let's set it to an imageview
    imageView1.setImageDrawable(bitmap);
  }

  @Override
  public void getSize(SizeReadyCallback cb) {
    cb.onSizeReady(SIZE_ORIGINAL, SIZE_ORIGINAL);
  }

  @Override
  public void removeCallback(SizeReadyCallback cb) {}
};

private void loadImageSimpleTarget() {  
  GlideApp
    .with(context) // could be an issue!
    .load(eatFoodyImages[0])
    .into(target);
}

需要重写getSize方法,并且调用cb.onSizeReady(SIZE_ORIGINAL, SIZE_ORIGINAL);来保证Glide使用最高质量的值。当然,也可以传递一个具体的值,例如 根据View的大小返回。

Target注意事项

除了刚介绍的Target的回调系统的一个简单版本,你要学会额外两件事。

第一个是BaseTarget对象的定义。Java/Android可以允许在.into()内匿名定义,但这会显著增加在Glide处理完图片请求前Android垃圾回收清理匿名target对象的可能性。最终,会导致图片被加载了,但是回调永远不会被调用。所以,请确保将你的回调定义为一个字段对象,防止被万恶的Android垃圾回收给清理掉。

第二个关键部分是Glide.with(context)。这个问题实际上是Glide一个特性问题:当你传递了一个context,例如当前appactivity,当activity停止后,Glide会自动停止当前的请求。这种整合到app生命周期内是非常有用的,但也是很难处理的。如果你的target是独立于app的生命周期。这里的解决方案是使用application的contextapp自己停止运行的时候,Glide会只取消掉图片的请求。请记住,再次提醒,如果你的请求需要在activity的生命周期以外,使用下面的代码:

private void loadImageSimpleTargetApplicationContext() {  
    GlideApp
        .with(context.getApplicationContext() ) // safer!
        .load(eatFoodyImages[1] 
        .asBitmap()
        .into(target2);
}
设置具体尺寸的Target

通过Target的另一个问题就是它没有具体的尺寸。如果在.into()方法的参数中传递一个ImageView,Glide会根据ImageView的大小来控制图片的尺寸。例如在加载一个1000x1000的 图片时,但是要显示到的ImageView的大小是250x250Glide会把该图片裁剪到小的尺寸来节省处理时间和内存。显示,在使用Targets时,这种方式并没有效果,因为根本不知道所需要的图片的大小。但是,如果你知道图片的最终所需要的尺寸时,你可以在callback中指定图片的尺寸来节省内存。

private BaseTarget target2 = new BaseTarget<BitmapDrawable>() {  
  @Override
  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {
    // do something with the bitmap
    // for demonstration purposes, let's set it to an imageview
    imageView2.setImageDrawable(bitmap);
  }

  @Override
  public void getSize(SizeReadyCallback cb) {
    cb.onSizeReady(250, 250);
  }

  @Override
  public void removeCallback(SizeReadyCallback cb) {}
};

private void loadImageSimpleTargetApplicationContext() {  
  GlideApp
    .with(context.getApplicationContext()) // safer!
    .load(eatFoodyImages[1])
    .into(target2);
}

正如上面通过cb.onSizeReady(250, 250);来指定尺寸。

ViewTarget

我们不直接使用ImageView的原因会有很多。我们可以通过上面的方式直接获取Bitmap。假如现在有一个自定义ViewGlide并不支持加载图片到自定义View中,因为它并不知道要 将图片设置到该View中的哪个位置。然而,Glide提供了ViewTargets来让这种问题变的非常简单。 下面是我们的自定义view:

public class FutureStudioView extends FrameLayout {  
    ImageView iv;
    TextView tv;

    public void initialize(Context context) {
        inflate( context, R.layout.custom_view_futurestudio, this );

        iv = (ImageView) findViewById( R.id.custom_view_image );
        tv = (TextView) findViewById( R.id.custom_view_text );
    }

    public FutureStudioView(Context context, AttributeSet attrs) {
        super( context, attrs );
        initialize( context );
    }

    public FutureStudioView(Context context, AttributeSet attrs, int defStyleAttr) {
        super( context, attrs, defStyleAttr );
        initialize( context );
    }

    public void setImage(Drawable drawable) {
        iv = (ImageView) findViewById( R.id.custom_view_image );

        iv.setImageDrawable( drawable );
    }
}

因为该View并没有继承ImageView所以不能直接使用.into()方法。我们可以创建一个ViewTarget来替代.into()方法进行使用:

FutureStudioView customView = (FutureStudioView) findViewById( R.id.custom_view );

viewTarget = new ViewTarget<FutureStudioView, BitmapDrawable>(customView) {  
  @Override
  public void onResourceReady(BitmapDrawable bitmap, Transition<? super BitmapDrawable> transition) {
    this.view.setImage(bitmap);
  }
};

GlideApp  
    .with(context.getApplicationContext()) // safer!
    .load(eatFoodyImages[2])
    .into(viewTarget);

调试及Debug

可以通过adb命令来开启Glide的调试log adb shell setprop log.tag.GenericRequest DEBUG

获取异常信息

Glide不能直接通过GenericRequest类获取日志,但是我们可以获取异常信息。例如,当一个图片获取不到时,glide会抛出一个异常并且显示.error() 方法设置的错误图片。如果想明确哪里出了异常,可以通过.listener()方法传递一个listener对象进去。

首先,创建一个listener对象作为一个成员变量,避免被垃圾回收:

private RequestListener<Bitmap> requestListener = new RequestListener<Bitmap>() {  
  @Override
  public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
    // todo log exception to central service or something like that

    // important to return false so the error placeholder can be placed
    return false;
  }

  @Override
  public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
    // everything worked out, so probably nothing to do
    return false;
  }
};

GlideApp  
    .with(context)
    .asBitmap()
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .listener(requestListener)
    .error(R.drawable.cupcake)
    .into(imageViewPlaceholder);

配置第三方网络库

  • OkHttp
// image loading library Glide
compile 'com.github.bumptech.glide:glide:4.1.1'  
annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'

// Glide's OkHttp2 Integration 
compile 'com.github.bumptech.glide:okhttp-integration:4.1.1@aar'  
compile 'com.squareup.okhttp:okhttp:2.7.5'  
  • Volley
// image loading library Glide
compile 'com.github.bumptech.glide:glide:4.1.1'  
annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'

// Glide's Volley Integration 
compile 'com.github.bumptech.glide:volley-integration:4.1.1@aar'  
compile 'com.android.volley:volley:1.0.0'  
  • OkHttp3
// image loading library Glide
compile 'com.github.bumptech.glide:glide:4.1.1'  
annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'

// Glide's OkHttp3 Integration 
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1@aar'  
compile 'com.squareup.okhttp3:okhttp:3.8.1'  

Modules定制Glide

Glide modules是一个全局改变Glide行为的抽象的方式。你需要创建Glide的实例,来访问GlideBuilder。可以通过创建一个公共的类,实现AppGlideModule的接口来定制Glide,这个接口提供了两个调整不同参数的方法,大多数情况下我们会用第一个方法pplyOptions(Context context, GlideBuilder builder).

@GlideModule
public class FutureStudioAppGlideModule extends AppGlideModule {  
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {

    }

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

    }
}

在上面的applyOptions方法的参数中有GlideBuilder对象,我们来看一下他的方法:

  • .setMemoryCache(MemoryCache memoryCache)
  • .setBitmapPool(BitmapPool bitmapPool)
  • .setDiskCache(DiskCache.Factory diskCacheFactory)
  • .setDiskCacheService(ExecutorService service)
  • .setResizeService(ExecutorService service)
  • .setDecodeFormat(DecodeFormat decodeFormat)

由于Glide默认使用将bitmap使用RGB565解析,下面我们就通过GlideModule来将图片设置为ARGB8888

@GlideModule
public class FutureStudioAppGlideModule extends AppGlideModule {  
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
    }

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

    }
}

接受自签名Https证书

Glide内部使用标准的HTTPUrlConnection去下载图片。Glide也提供两个集成库。这三个方法优点是在安全设置上都是相当严格的。唯一的不足之处是当你从一个使用HTTPS,还是self-signed的服务器下载图片时,Glide并不会下载或者显示图片,因为self-signed认证会被认为存在安全问题。

这样,你会需要去实现能够接受self-signed认证的网络栈。幸运地,我们已经实现并用过一个“不安全的”OkHttpClient。由于它提供给了一个需要集成的常规OkHttpClient,我们只需要拷贝并粘贴这个类:

public class UnsafeOkHttpClient {  
    public static OkHttpClient getUnsafeOkHttpClient() {
        try {
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[] {
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

该类关闭了所有SSL认证检查。
接下来我们需要将上面的方法集成到GlideModule中。Glide使用一个ModelLoader去链接到数据模型创建一个具体的数据类型。我们的例子中,我们需要创建一个ModelLoader,它连接到一个URL,通过GlideUrl类响应并转化为输入流。Glide需要能够创建我们的新ModelLoader的实例,所以我们在.register()方法中传入一个工厂:

@GlideModule
public class UnsafeOkHttpGlideModule extends LibraryGlideModule {  
    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        OkHttpClient client = UnsafeOkHttpClient.getUnsafeOkHttpClient();
        registry.replace(GlideUrl.class, InputStream.class,
                new OkHttpUrlLoader.Factory(client));
    }
}

自定义缓存

Glide使用MemorySizeCalculator类来设置内存缓存的大小和bitmap poolbitmap pool会把图片保存到应用的栈内存中。合适的bitmap pool尺寸是非常必要的,因为要避免 太大会导致内存频繁的被回收。
我们可以很简单的获取到GlideMemorySizeCalculator类和默认的值:

MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();  
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();  
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();  

那么如何自定义缓存大小呢?

@GlideModule
public class CustomCachingGlideModule extends AppGlideModule {  
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).build();
        int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
        int defaultBitmapPoolSize = calculator.getBitmapPoolSize();

        int customMemoryCacheSize = (int) (1.2 * defaultMemoryCacheSize);
        int customBitmapPoolSize = (int) (1.2 * defaultBitmapPoolSize);

        builder.setMemoryCache(new LruResourceCache(customMemoryCacheSize));
        builder.setBitmapPool(new LruBitmapPool(customBitmapPoolSize));
    }

    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        // nothing to do here
    }
}
  • 自定义硬盘缓存

Glide提供了两种硬盘缓存方式InternalCacheDiskCacheFactoryExternalCacheDiskCacheFactory

@GlideModule
public class CustomCachingGlideModule extends AppGlideModule {  
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // set disk cache size & external vs. internal
        int cacheSize100MegaBytes = 104857600;

        builder.setDiskCache(
                new InternalCacheDiskCacheFactory(context, cacheSize100MegaBytes));

        //builder.setDiskCache(
        //        new ExternalCacheDiskCacheFactory(context, cacheSize100MegaBytes));
    }

    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        // nothing to do here
    }
}

上面的配置只是更改硬盘缓存的大小,并不会改变缓存目录,如果需要将缓存文件位置修改为指定位置,需要使用DiskLruCacheFactory类。

// or any other path
String downloadDirectoryPath = Environment.getDownloadCacheDirectory().getPath(); 

builder.setDiskCache(  
        new DiskLruCacheFactory( downloadDirectoryPath, cacheSize100MegaBytes )
);

// In case you want to specify a cache sub folder (i.e. "glidecache"):
//builder.setDiskCache(
//    new DiskLruCacheFactory( downloadDirectoryPath, "glidecache", cacheSize100MegaBytes ) 
//);

缓存总结

通过上面的设置可以发现,Glide一共使用了三种缓存方式:

  • Memory cache needs to implement: MemoryCache
  • Bitmap pool needs to implement BitmapPool
  • Disk cache needs to implement: DiskCache

Let's take Glide as an example. To optimize memory usage and use less memory, Glide does downsampling.

Downsampling means scaling the bitmap(image) to a smaller size which is actually required by the view.

Assume that we have an image of size 20002000, but the view size is 400400. So why load an image of 20002000, Glide down-samples the bitmap to 400400, and then show it into the view.

We use Glide like this:

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

As we are passing the imageView as a parameter to the Glide, it knows the dimension of the imageView.

Glide down-samples the image without loading the whole image into the memory.

This way, the bitmap takes less memory, and the out-of-memory error is solved. Similarly, other Image Loading libraries like Fresco also do it.

This was all about how the Android Image Loading library optimizes memory usage.

参考