八年老Android开发谈:Glide都不会,如何面试拿高薪?

本文是开篇文章,将对开源项目Glide图片加载库进行介绍。如果老铁们看完还是忘了,就 回来揍我一顿 点赞收藏加关注,多看两遍~

概览

基于Glide最新版本4.11.0,未迁AndroidX的项目只能使用4.9.0,简单使用:

引入依赖,app/build.gradle:

implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'` 

一句代码,完成图片加载:

Glide.with(this) //指定上下文,可以是app、activity、fragment
    .load(url)  //网络图片地址
    .into(img);  //用于展示的imageView` 

用起来简洁优雅,然后我们先大致预览下Glide的一些职能,

树干:核心流程

Glide.with(this).load(url).into(img)为起点,拆成with购车load上牌into发车三个环节来分析。

with:购车

简单来说,with的功能就是根据传入的上下文context来获取图片请求管理器RequestManager,他用来管理和启动图片请求,

context可以传入app、activity、fragment,这决定了图片请求的生命周期。通常是使用粒度较细的context,即使用当前页面的context而不是全局的app。这样做的好处是,打开一个页面开启图片加载,然后退出页面,图片请求就会跟随页面销毁而被取消,而不是继续加载而浪费资源。

当context是app时,通过RequestManagerRetriever获得的RequestManager是一个全局单例,这类图片请求的生命周期将会跟随整个app,

class RequestManagerRetriever implements Handler.Callback {
    volatile RequestManager applicationManager;

    RequestManager getApplicationManager(Context context) {
        //双重检查锁,获取单例的RequestManager
        if (applicationManager == null) {
            synchronized (this) {
                if (applicationManager == null) {
                    Glide glide = Glide.get(context.getApplicationContext());
                    applicationManager = factory.build(glide,new ApplicationLifecycle(),
                        new EmptyRequestManagerTreeNode(),context.getApplicationContext());
                }
            }
        }
        //返回应用级别的RequestManager单例
        return applicationManager;
    }
}

当context是Activity时,创建一个SupportRequestManagerFragment(无界面的空fragment)添加到Activity,从而感知Activity的生命周期,同时创建RequestManager给空fragment持有,

class RequestManagerRetriever implements Handler.Callback {
    RequestManager supportFragmentGet(Context context,FragmentManager fm,
                                      Fragment parentHint,boolean isParentVisible) {
        //获取空fragment,无则创建
        SupportRequestManagerFragment current =
            getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            Glide glide = Glide.get(context);
            //如果空fragment没有RequestManager,就创建一个
            requestManager = factory.build(glide, current.getGlideLifecycle(), 
                                           current.getRequestManagerTreeNode(), context);
            //让空fragment持有RequestManager
            current.setRequestManager(requestManager);
        }
        //返回页面级别的RequestManager
        return requestManager;
    }
}

然后看到getSupportRequestManagerFragment方法,

class RequestManagerRetriever implements Handler.Callback {
    Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments =
      new HashMap<>();

    SupportRequestManagerFragment getSupportRequestManagerFragment(
        final FragmentManager fm, Fragment parentHint, boolean isParentVisible) {
        //通过tag找到Activity中的空fragment
        SupportRequestManagerFragment current =
            (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
            //findFragmentByTag没找到空fragment,有可能是延迟问题?再从Map中找一下
            current = pendingSupportRequestManagerFragments.get(fm);
            if (current == null) {
                //确实没有空fragment,就创建一个
                current = new SupportRequestManagerFragment();
    //...
                //缓存进Map
                pendingSupportRequestManagerFragments.put(fm, current);
                //空fragment添加到Activity,使其能感知Activity的生命周期
                fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
                //...
            }
        }
        return current;
    }
}

综上,通过with操作,

当context是app时,得到应用级别RequestManager全局单例;

当context是Activity时,每个页面都会被添加一个空fragment,由空fragment持有页面级别RequestManager

注意:如果with发生在子线程,不管context是谁,都返回应用级别RequestManager单例。

发散:添加空fragment来感知页面生命周期的思想,在Lifecycle的实现中也可以看到,见ReportFragment的injectIfNeededIn方法。(不过这个方法在Lifecycle的2.2.0版本中有所改动,Android 10开始的设备改成了使用Application.ActivityLifecycleCallbacks来感知,感兴趣可以康康)

至此,我们根据存款context买到了心仪的车RequestManager,下面开始上牌~

load:上牌

load方法得到了一个RequestBuilder图片请求构建器,见名知意猜一下,是用来创建图片请求的,

class RequestManager
    implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>> {

    RequestBuilder<Drawable> load(String string) {
        return asDrawable().load(string);
    }

    RequestBuilder<Drawable> asDrawable() {
        //需要加载的类型为Drawable
        return as(Drawable.class);
    }

    <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
        //创建一个请求构建器
        return new RequestBuilder<>(glide, this, resourceClass, context);
    }
}

然后跟进asDrawable().load(string)

class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
    implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {

    RequestBuilder<TranscodeType> load(String string) {
        return loadGeneric(string);
    }

    RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
        //只是简单地赋值
        this.model = model;
        isModelSet = true;
        return this;
    }
}

到这里,我们就完成了上牌,得到了一个RequestBuilder图片请求构建器。没错,上牌就这么简单,毕竟摇号已经够艰难了对吧?

into:发车

阶段一

来到into了,

class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
    implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {

    ViewTarget<ImageView, TranscodeType> into(ImageView view) {
        //...
        BaseRequestOptions<?> requestOptions = this;
        if (!requestOptions.isTransformationSet()
            && requestOptions.isTransformationAllowed()
            && view.getScaleType() != null) {
            //根据ImageView的ScaleType,来配置参数
            switch (view.getScaleType()) {
                case CENTER_CROP:
                    requestOptions = requestOptions.clone().optionalCenterCrop();
                    break;
                case CENTER_INSIDE:
                    requestOptions = requestOptions.clone().optionalCenterInside();
                    break;
                    //...
            }
        }
        return into(
            //前面提到,Target是展示图片的载体,这里他封装了ImageView
            glideContext.buildImageViewTarget(view, transcodeClass),null,
            requestOptions,Executors.mainThreadExecutor());
    }
}

继续跟进into重载方法,

class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
    implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {

    <Y extends Target<TranscodeType>> Y into(Y target,RequestListener<TranscodeType> targetListener,
                                             BaseRequestOptions<?> options,Executor callbackExecutor) {
        //...
        //创建图片请求
        Request request = buildRequest(target, targetListener, options, callbackExecutor);
        //获取Target载体已有的请求
        Request previous = target.getRequest();
        //如果两个请求等效,并且xxx(先忽略)
        if (request.isEquivalentTo(previous)
            && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
            if (!Preconditions.checkNotNull(previous).isRunning()) {
                //启动异步请求
                previous.begin();
            }
            return target;
        }
        requestManager.clear(target);
        //图片载体绑定图片请求,即imageView setTag为request
        target.setRequest(request);
        //启动异步请求
        requestManager.track(target, request);
        return target;
    }
}

跟进requestManager.track(target, request)

//RequestManager.java
void track(Target<?> target,Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
}

//RequestTracker.java
void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
        //开启图片请求
        request.begin();
    } else {
        request.clear();
        //如果处于暂停状态,就把请求存起来,晚些处理
        pendingRequests.add(request);
    }
}

到这里,就启动了图片请求,

阶段二

那么,接下来重点关注的就是request.begin()了,

class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {

    void begin() {
        synchronized (requestLock) {
            //...
            if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
                //如果已经有了明确的尺寸,开始加载
                onSizeReady(overrideWidth, overrideHeight);
            } else {
                //没有的话先去获取尺寸,最终还是走onSizeReady
                target.getSize(this);
            }
            //...
        }
    }

    void onSizeReady(int width, int height) {
        synchronized (requestLock) {
            //...
            //engine.load,传了很多参数
            loadStatus =
                engine.load(glideContext,model,
                requestOptions.getSignature(),
                this.width,this.height,
                requestOptions.getResourceClass(),
                transcodeClass,priority,
                requestOptions.getDiskCacheStrategy(),
    //...
                this,callbackExecutor);
            //...
        }
    }
}

跟进engine.load

class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {

    <R> LoadStatus load(
        GlideContext glideContext,
        //...
        Executor callbackExecutor) {
        //...
        EngineResource<?> memoryResource;
        synchronized (this) {
            //从内存加载
            memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
            if (memoryResource == null) {
                //如果内存里没有缓存,则加载
                return waitForExistingOrStartNewJob(
                    glideContext,
                    model,
                    //...
                    key,
                    startTime);
            }
        }
        cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
        return null;
    }

    <R> LoadStatus waitForExistingOrStartNewJob(...) {
        //...
        EngineJob<R> engineJob =engineJobFactory.build(...);
        DecodeJob<R> decodeJob =decodeJobFactory.build(...);
        jobs.put(key, engineJob);
        //添加回调,这个cb就是SingleRequest自己,todo1
        engineJob.addCallback(cb, callbackExecutor);
        //engineJob开启decodeJob
        engineJob.start(decodeJob);
        return new LoadStatus(cb, engineJob);
    }
}

DecodeJob是一个Runable,看看他的run方法,调用链如下:

DecodeJob.run -> DecodeJob.runWrapped -> DecodeJob.runGenerators ->

SourceGenerator.startNext -> SourceGenerator.startNextLoad ->

MultiModelLoader#MultiFetcher.loadData ->

HttpUrlFetcher.loadData

来到HttpUrlFetcher

class HttpUrlFetcher implements DataFetcher<InputStream> {

    void loadData(Priority priority, DataCallback<? super InputStream> callback) {
        try {
            //获取输入流,没有引入okhttp,则使用HttpURLConnection
            InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
            //回调出去
            callback.onDataReady(result);
        } catch (IOException e) {
            callback.onLoadFailed(e);
        } finally {
        }
    }
}

到这里,网络请求就完成了,下面看看图片是怎么设置上去的,前边留了个todo1,

//添加回调,这个cb就是SingleRequest自己,todo1

callback就是SingleRequest,被回调前有一些对象包装、解码操作,暂不深究,来到SingleRequest

class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback {

    void onResourceReady(Resource<?> resource, DataSource dataSource) {
        Resource<?> toRelease = null;
        try {
            synchronized (requestLock) {
                //...
                Object received = resource.get();
                //...
                //这里
                onResourceReady((Resource<R>) resource, (R) received, dataSource);
            }
        } finally {
            if (toRelease != null) {
                engine.release(toRelease);
            }
        }
    }

    void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
        //...
        try {
            //...
            //这里,回调给Target载体
            target.onResourceReady(result, animation);
        } finally {
        }
        notifyLoadSuccess();
    }
}

跟进target.onResourceReady,最终来到DrawableImageViewTarget

class DrawableImageViewTarget extends ImageViewTarget<Drawable> {
    void setResource(Drawable resource) {
        //ImageView设置图片
        view.setImageDrawable(resource);
    }
}

结合阶段一和二,

终于,汽车发动起来了~

with购车load上牌into发车三个环节汇总起来,

可以看到,整个图片加载过程,就是with得到RequestManager,load得到RequestBuilder,然后into开启加载:

创建Request、开启Engine、运行DecodeJob线程、HttpUrlFetcher加载网络数据、回调给载体Target、载体为ImageView设置展示图片。

细枝:补充

线程切换

在子线程下载完图片后,如何回调主线程设置图片?在Executors类里,

private static final Executor MAIN_THREAD_EXECUTOR = new Executor() {
    //主线程Handler
    private final Handler handler = new Handler(Looper.getMainLooper());

    @Override
    public void execute(Runnable command) {
        //切回主线程
        handler.post(command);
    }
};

调用栈如下:

线程池

GlideBuilder类里会初始化一些线程池:

Glide build(Context context) {
    private GlideExecutor sourceExecutor; //加载图片
    private GlideExecutor diskCacheExecutor; //管理磁盘缓存
    private GlideExecutor animationExecutor; //管理动画
}

缓存

有内存缓存和磁盘缓存,在Engine.load时会去取,篇幅原因后面单独开篇来写。

webp动图

Fresco支持解析webp动图,Glide不支持,不过已经有了开源的方案,见GitHub - GlideWebpDecoder。

选型

FrescoGlide怎么选?

Fresco具有一定侵入性,需要继承SimpleDraweeView

Fresco调用繁琐,没有Glide的链式调用优雅,当然这个可以包一层来解决;

Fresco在5.0以下的系统进行了内存优化(Ashmem区),这个优势在当下的环境已经不值一提,因为这些系统占比已经非常低了,一些App的minSDK都已经设置成21了。

所以,更推荐使用Glide(个人拙见,仅供参考)

尾声

作为《看完不忘系列》的文章,本文删减了很多源码,重点在于理清Glide图片加载流程,大家看的时候最好能跟着思路去阅读源码~然后,Glide还有解码、缓存的流程没有分析,后面会单独开篇来写。

参考资料

  • 掘金 - 面试官:简历上最好不要写Glide,不是问源码那么简单
  • 掘金 - 锦囊篇|一文摸懂Glide
  • 掘金 - Android主流三方库源码分析(三、深入理解Glide源码)
  • 官方文档 & GitHub
  • GitHub - GlideWebpDecoder

本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...

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

推荐阅读更多精彩内容

  • 序言 在Android开发过程中,图片处理是必不可少的,图片处理可繁可简。目前有也出现了很多优秀的图片加载库,如G...
    左大人阅读 562评论 1 4
  • 由于图片加载是应用开发中非常常见,但是有非常容易消耗资源甚至出现问题的场景,因此出现了很多第三方图片加载框架,...
    熠闲阅读 13,478评论 6 23
  • Glide是一个图片加载框架,其他的图片加载框架还有UniversalImageLoader,Picasso,Fr...
    xiasuhuei321阅读 2,125评论 1 18
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,482评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11