每日一题:Glide源码

****每日一题****: Glide

glide图片缓存
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全局配置相关的类.
  • 如可以设置缓存策略.
  • 更多的配置如下


    img-w400
  • 可以通过实现GlideModule接口来自定义一个Glide图库,可以通过他改变Glide的行为和基础配置.

在自定义GlideModule时需要注意什么?

要全局的去声明这个类,让 Glide 知道它应该在哪里被加载和使用。Glide 会扫描 AndroidManifest.xml 为 Glide module 的 meta 声明。具体可以查看源码在Glide.get(Context),通过ManifestParser对象获取GlideModule集合.
因此,你必须在 AndroidManifest.xml 的 < application> 标签内去声明这个刚刚创建的 Glide module。

  1. 创建GlideModel
public class MyGlideModule implements GlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {

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

  }
}
  1. 在AndroidManifest.xml的meta-data配置GlideModule
<meta-data android:name="com.branch.glidedemo.MyGlideModule"
           android:value="GlideModule"/>
  1. 解决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的已经写的不错了****,****有没对他做些进阶的优化/使用?

  1. Glide.with(context).resumeRequests()和 Glide.with(context).pauseRequests()
    当列表在滑动的时候,调用pauseRequests()取消请求,滑动停止时,调用resumeRequests()恢复请求。这样是不是会好些呢?

  2. Glide.clear()
    当你想清除掉所有的图片加载请求时,这个方法可以帮助到你。

  3. ListPreloader
    如果你想让列表预加载的话,不妨试一下ListPreloader这个类。

可以提升图片加载速度。首先是在图片展示前预读取数据,
它提供了一个 ListPreloader,通过预加载 item 的数量初始化。接着通过
setOnScrollListener(OnScrollListener) 把 ListPreloader 设置给 ListView。如果你想在 ListView 之外预载图片,只要调用上面 DrawableRequestBuilder 对象的 downloadOnly() 方法就好,像这样 builder.downloadOnly();

demo代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容