****每日一题****: Glide
面试率: ★★★☆☆
面试技巧与建议
图库在Android实际项目中基本上都会使用到,这种概率跟网络库几乎一样的,因此出去面试前准备好一套图库面试技巧是不可避免的.
面试建议
在最近的面试中很多应聘者都存在如下几个问题:
基本都使用/了解过
你去面试一家公司,别人问这个问题你说使用过,但是面试官一天面试7-8个人,其他应聘者也会使用,那么你们区别在哪里?不知道其底层网络协议
一个网路底层使用socket还是http这个是最基本的了解.不知道为什么使用它
图库有很多种如Fresco,Picasso,imgLoader等,然而为什么使用Glide呢,你是否知道他的特点?不知道如何封装
使用过那如何使用?做了什么封装?如何自定义Glide?在什么地方用过
项目中哪个模块使用过这个库呢,是全局使用,还是局部使用?
对于上述问题可以了解并答出,才能证明你有这方面相关开发经验,否则只能说你只是看过Glide.
面试技巧
一般开发中迭代速度比较快的情况下,不会有很多时间去深入的探讨开源库中的全部源码,但是如果跟业务有关联的话,我们才会去做研究,而研究的也是全部源码中的某块功能或者模块.
- Glide Module自定义缓存
图片框架中很多自定义的实现,而缓存也是框架中最为常见的行为之一。因此在开发使用中有些项目需求不凡试下自定义Glide的缓存.
下面是一篇自定义Glide的缓的文章:
Glide Module
面试题
下面是我从源码中提取出来的一些问题,简单而实用.
你为什么使用Glide?
Glide特点
- 使用简单
- 可配置度高,自适应程度高
- 支持常见图片格式
Jpg png gif webp
- 支持多种数据源
网络、本地、资源、Assets 等
- 高效缓存策略
支持Memory和Disk图片缓存 默认Bitmap格式采用RGB_565内存使用至少减少一半
- 生命周期集成
根据Activity/Fragment生命周期自动管理请求
- 高效处理Bitmap
使用Bitmap Pool使Bitmap复用,主动调用recycle回收需要回收的Bitmap,减小系统回收压力
Glide是如何提高加载图片的性能?
总共有两点:
通过控制图片大小.
.override(200,200)可以设置加载图片大小,但是实际大小不一定是200x200,通过源码分析下:
在BitmapRequestBuilder中的private Downsampler downsampler = Downsampler.AT_LEAST
默认就是设置了尺寸优化,超过最大比例的就会对图片进行等比例缩放,如何缩放见下面;在Downsampler中的decode方法中,获取的Bitmap大小变成1/sampleSize,倍数通过getSampleSize计算所得,
inSampleSize 是 BitmapFactory.Options的属性,应该大家都知道。然后再看看怎么生成.override(200,200),如果没有设置Glide默认是FitCenter,查看FitCenter可以看到图片截取方式。
举个例子:
加载的图片大小为1080x540,如果使用了.override(200,200)默认缓存一张200x100的图片,也就是默认存储结果RESULT.
通过设置Bitmap Format的图片类型
为了降低内存消耗,Glide默认配置的Bitmap Format 为 RGB_565,修改GlideBuilder's setDecodeFormat设置.
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
Glide为何要使用额外的无界面的Fragment/activity?
根据传入Context的类型有不同的实现,这里以FragmentActivity为例(现在常用的MD样式Activity类AppCompatActivity是FragmentActivity的子类)。方法get(FragmentActivity activity)调用了方法supportFragmentGet(activity, fm),后者返回的对象类型是SupportRequestManagerFragment 。SupportRequestManagerFragment 是一个无界面的Fragment类,起到把请求和Activity生命周期同步的作用。
Glide.with() 不仅仅只是Context还可以是Activity,Fragment等,传入后自动适配,Glide加载图片是会随着Activity,Fragment 的生命周期,具体可以参考LifecycleListener,所以推荐使用Activity,Fragment.
Glide的缓存策略是怎么做的?
通过该方法设置策略.diskCacheStrategy(DiskCacheStrategy.ALL)
DiskCacheStrategy 分别有以下几种选择,ALL缓存原图和截取后的图,NONE 不缓存,SOURCE 只缓存原图,RESULT缓存截取后的图.
因此如果图片需要分享或需要原图的建议缓存ALL,否则只缓存RESULT.
Glide的核心GlideModule
的作用是什么?
- GlideModule是对glide全局配置相关的类.
- 如可以设置缓存策略.
-
更多的配置如下
- 可以通过实现GlideModule接口来自定义一个Glide图库,可以通过他改变Glide的行为和基础配置.
在自定义GlideModule时需要注意什么?
要全局的去声明这个类,让 Glide 知道它应该在哪里被加载和使用。Glide 会扫描 AndroidManifest.xml 为 Glide module 的 meta 声明。具体可以查看源码在Glide.get(Context),通过ManifestParser对象获取GlideModule集合.
因此,你必须在 AndroidManifest.xml 的 < application> 标签内去声明这个刚刚创建的 Glide module。
- 创建GlideModel
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
- 在AndroidManifest.xml的meta-data配置GlideModule
<meta-data android:name="com.branch.glidedemo.MyGlideModule"
android:value="GlideModule"/>
- 解决GlideModel冲突有可能加入的library中也同样配置了GlideModule,如果配置了多个会出现冲突,无法编译运行,解决方式可在AndroidManifest.xml移除
<meta-data android:name=”com.mypackage.MyGlideModule” tools:node=”remove” />
实际开发中的一些问题
为什么 有的图片第一次加载的时候只显示占位图,第二次才显示正常的图片呢?
如果你刚好使用了这个圆形Imageview库或者其他的一些自定义的圆形Imageview,而你又刚好设置了占位的话,那么,你就会遇到第一个问题。如何解决呢?方案一: 不设置占位;方案二:使用Glide的Transformation API自定义圆形Bitmap的转换。这里是一个已有的例子;方案三:使用下面的代码加载图片:
Glide.with(mContext)
.load(url)
.placeholder(R.drawable.loading_spinner)
.into(new SimpleTarget<Bitmap>(width, height) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
// setImageBitmap(bitmap) on CircleImageView
}
});
该方法在listview上复用有问题的bug,如果在listview中加载CircleImageView,请不要使用该方法。
方案四:不使用Glide的默认动画:
Glide.with(mContext)
.load(url)
.dontAnimate()
.placeholder(R.drawable.loading_spinner)
.into(circleImageview);
为什么 我总会得到类似You cannot start a load for a destroyed activity
这样的异常呢?
请记住一句话:不要再非主线程里面使用Glide加载图片,如果真的使用了,请把context参数换成getApplicationContext。
提示:这个问题主要是context对应的生命周期引起的.当 Glide 检测到 Activity 被销毁时,会自动取消等待中的请求.如果你传递的是一个
getApplicationContext,Glide就不能对其进行优化,所以适当选择。
为什么 我不能给加载的图片setTag()呢?
首先Glide的全局tag只是为了保证你可以正常的使用view.setTag方法,和错位没关系的。因为Glide内部就是通过view.setTag来保证不错位的.
进一步说明:
当图片从ListView 中移出屏幕时,Glide 也会取消其对应的请求。
由于大多数开发者在 adapter 中重用 View,Glide 会给在请求数据时给对应的ImageView 附加一个 tag,然后再载入其他图片时检查这个 tag,如果存在的话取消第一个请求,进而形成的优化。
使用setTag(int,object)方法设置tag,具体用法如下:Java代码是酱紫的:
Glide.with(context).load(urls.get(i).getUrl()).fitCenter().into(imageViewHolder.image);
imageViewHolder.image.setTag(R.id.image_tag, i);
imageViewHolder.image.setOnClickListener(new View.OnClickListener() {
@Override
int position = (int) v.getTag(R.id.image_tag);
Toast.makeText(context, urls.get(position).getWho(), Toast.LENGTH_SHORT).show();
}
});
同时在values文件夹下新建ids.xml,添加
<item name="image_tag" type="id"/>
方案二:从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" />
方案三:写一个继承自ImageViewTaget的类,复写它的get/setRequest方法
Glide.with(context).load(urls.get(i).getUrl()).fitCenter().into(new ImageViewTarget<GlideDrawable>(imageViewHolder.image) {
@Override
protected void setResource(GlideDrawable resource) {
imageViewHolder.image.setImageDrawable(resource);
}
@Override
public void setRequest(Request request) {
imageViewHolder.image.setTag(i);
imageViewHolder.image.setTag(R.id.glide_tag_id,request);
}
@Override
public Request getRequest() {
return (Request) imageViewHolder.image.getTag(R.id.glide_tag_id);
}
});
imageViewHolder.image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (int) v.getTag();
Toast.makeText(context, urls.get(position).getWho(), Toast.LENGTH_SHORT).show();
}
});
Glide常见错误
java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
解决办法 在使用Glide的那段代码加是否在主线程判断
if(Util.isOnMainThread()) {
Glide.with(MyActivity.this).load(strURL).into(imageView);
}
在onDestory加
@Override
protected void onDestroy() {
super.onDestroy();
if(Util.isOnMainThread()) {
Glide.with(this).pauseRequest();
}
并且所有的this 都要写成getApplicationContext ,这个主要针对于在子线程使用Glide.
Glide的已经写的不错了****,****有没对他做些进阶的优化/使用?
Glide.with(context).resumeRequests()和 Glide.with(context).pauseRequests()
当列表在滑动的时候,调用pauseRequests()取消请求,滑动停止时,调用resumeRequests()恢复请求。这样是不是会好些呢?Glide.clear()
当你想清除掉所有的图片加载请求时,这个方法可以帮助到你。ListPreloader
如果你想让列表预加载的话,不妨试一下ListPreloader这个类。
可以提升图片加载速度。首先是在图片展示前预读取数据,
它提供了一个 ListPreloader,通过预加载 item 的数量初始化。接着通过
setOnScrollListener(OnScrollListener) 把 ListPreloader 设置给 ListView。如果你想在 ListView 之外预载图片,只要调用上面 DrawableRequestBuilder 对象的 downloadOnly() 方法就好,像这样 builder.downloadOnly();