美文网首页Android技术知识Android开发Android开发经验谈
八年老Android开发谈:Glide都不会,如何面试拿高薪?

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

作者: Kepler_II | 来源:发表于2020-11-29 20:41 被阅读0次

    本文是开篇文章,将对开源项目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 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/ssakwktx.html