图片加载库-Glide源码分析

glide:3.7.0 使用

引入

  compile 'com.github.bumptech.glide:glide:3.7.0'

混淆

-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

使用

使用的方式十分简单,和Picasso差不多

Glide.with(this).load("https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1511148367&di=f2289ba7ce873fddd5e85dab9410ebeb&src=http%3A%2F%2Fi-1.yxdown.com%2F2013%2F11%2F6%2F94ba6d62-7676-4ced-abe0-0c6a279369f4.jpg")
.placeholder(R.drawable.ic_launcher_background)
.into(mIv);

同样也可以为他设置其他,例如 listener 、 centerCrop 、transform 、 sizeMultiplier

.listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    return false;
                }
            })

.centerCrop()

....

之类的,这些使用的方法和Picasso十分类似。

源码分析

RequestManager

Glide.with(context)

从 Glide.with 开始分析起。

public static RequestManager with(FragmentActivity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}

先看看 RequestManagerRetriever.get() 方法都干了什么

private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();

RequestManagerRetriever() {
    handler = new Handler(Looper.getMainLooper(), this /* Callback */);
}

上述的方其实就是 拿取了 RequestManagerRetriever 的单例对象。而这 RequestManagerRetriever 的构造函数所做的就只有 初始化 一个 handler ,这个handler 最终回调到主线程。

然后看看 retriever.get(activity)

public RequestManager get(Activity activity) {
    if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        return get(activity.getApplicationContext());
    } else {
        //判断activity是否已经是销毁状态
        assertNotDestroyed(activity);
        //获取FragmentManager 对象
        FragmentManager fm = activity.getSupportFragmentManager();
        //创建Fragment,RequestManager并将其绑定
        return supportFragmentGet(activity, fm);
    }
}

然后就 调用了 supportFragmentGet(activity, fm);

RequestManager supportFragmentGet(Context context, FragmentManager fm) {
    // 注 1 
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
    // 注 2
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

注 1 :

getSupportRequestManagerFragment 方法

SupportRequestManagerFragment getSupportRequestManagerFragment(final FragmentManager fm) {
    SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(
        FRAGMENT_TAG);
    // 判断是否能从 fm 里面拿到Tag为FRAGMENT_TAG的SupportRequestManagerFragment
    if (current == null) {
        //current 为空 那么就从 pendingSupportRequestManagerFragments 里面拿取。pendingSupportRequestManagerFragments 其实是一个Map<FragmentManager, SupportRequestManagerFragment> ,根据 fm 来获取 这个 创建过的 SupportRequestManagerFragment。
        current = pendingSupportRequestManagerFragments.get(fm);
         //如果没有就创建一个 SupportRequestManagerFragment ,并把他放在 pendingSupportRequestManagerFragments 里面。
        if (current == null) {
            current = new SupportRequestManagerFragment();
            pendingSupportRequestManagerFragments.put(fm, current);
            // 添加标记
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    }
    return current;
}

那么就看看 创建 SupportRequestManagerFragment 都干了什么

public SupportRequestManagerFragment() {
    this(new ActivityFragmentLifecycle());
}

@SuppressLint("ValidFragment")
public SupportRequestManagerFragment(ActivityFragmentLifecycle lifecycle) {
    this.lifecycle = lifecycle;
}

他会生成初始化一个 ActivityFragmentLifecycle 对象 。这个对象用于管理 Activity、Fragment 的生命周期的状态。而在SupportRequestManagerFragment类里面,我们可以看到有如下代码:

@Override
public void onStop() {
    super.onStop();
    lifecycle.onStop();
}

@Override
public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
}

他会和生命周期关联上。而生命周期的状态全部交给了 ActivityFragmentLifecycle 这个类来实现。

注 2 :

从 SupportRequestManagerFragment 里面获取 RequestManager 。 很明显 ,初始化的时候并没有创建 RequestManager 对象 ,那么就创建一个新的 RequestManager 对象 ,并设置给 SupportRequestManagerFragment 对象。

requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());

RequestManager 构造函数

RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
        RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
    this.context = context.getApplicationContext();
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    //获取 Glide 实例 ,这又是一个单例对象
    this.glide = Glide.get(context);
    this.optionsApplier = new OptionsApplier();

    ....
}

新创建的 RequestManager 也绑定了 生命周期。

DrawableTypeRequest

那么 RequestManager 对象得以生成。 接下来我们最常的操作就是使用 load 方法 创建DrawableTypeRequest 对象了。

DrawableTypeRequest<String> load = with.load("https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1511148367&di=f2289ba7ce873fddd5e85dab9410ebeb&src=http%3A%2F%2Fi-1.yxdown.com%2F2013%2F11%2F6%2F94ba6d62-7676-4ced-abe0-0c6a279369f4.jpg");

RequestManager 类 里面的 load 方法

load 实现了很多个重载 load(byte[] model) 、load(String string) ..... ,所以会根据不同的参数来返回不同的 DrawableTypeRequest<T> 这个泛型类。 下面就以 String 来分析。

public DrawableTypeRequest<byte[]> load(byte[] model) {
    return (DrawableTypeRequest<byte[]>) fromBytes().load(model);
}

public DrawableTypeRequest<String> load(String string) {
    return (DrawableTypeRequest<String>) fromString().load(string);
}

不同的参数 , 就会执行不同的方法来生成 DrawableTypeRequest 对象 ,然后执行 load 方法 。

DrawableRequestBuilder.java

public DrawableRequestBuilder<ModelType> load(ModelType model) {
    // 交给父类处理
    super.load(model);
    return this;
}

GenericRequestBuilder.java

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
    // model 赋值
    this.model = model;
    isModelSet = true;
    return this;
}

那么最后就生成了 DrawableTypeRequest 对象。利用这个对象我们可以设置 placeholder 、 listener 、error ...等

@Override
public DrawableRequestBuilder<ModelType> placeholder(Drawable drawable) {
    super.placeholder(drawable);
    return this;
}

@Override
public DrawableRequestBuilder<ModelType> listener(
        RequestListener<? super ModelType, GlideDrawable> requestListener) {
    super.listener(requestListener);
    return this;
}

@Override
public DrawableRequestBuilder<ModelType> error(Drawable drawable) {
    super.error(drawable);
    return this;
}

最后查看父类的代码,可以知道设置这些属性都是为 GenericRequestBuilder 的属性赋值。

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
        Drawable drawable) {
    //为 placeholderDrawable 赋值
    this.placeholderDrawable = drawable;
    return this;
}

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> listener(
        RequestListener<? super ModelType, TranscodeType> requestListener) {
    //为 requestListener 赋值
    this.requestListener = requestListener;
    return this;
}

public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> error(
        Drawable drawable) {
    //为 errorPlaceholder 赋值
    this.errorPlaceholder = drawable;
    return this;
}

into

为 GenericRequestBuilder 的属性设置的差不多了 ,那么最后所要调用的 就是 into 这个方法。

DrawableRequestBuilder.java

@Override
public Target<GlideDrawable> into(ImageView view) {
    return super.into(view);
}

GenericRequestBuilder.java

public Target<TranscodeType> into(ImageView view) {
    Util.assertMainThread();
    if (view == null) {
        throw new IllegalArgumentException("You must pass in a non null View");
    }

    if (!isTransformationSet && view.getScaleType() != null) {
        switch (view.getScaleType()) {
            case CENTER_CROP:
                applyCenterCrop();
                break;
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
                applyFitCenter();
                break;
            //$CASES-OMITTED$
            default:
                // Do nothing.
        }
    }

    return into(glide.buildImageViewTarget(view, transcodeClass));
}

最后实际调用到这个方法里面

public <Y extends Target<TranscodeType>> Y into(Y target) {
    // 判断是否在主线程 , 如果不在主线程那么就抛出异常    
    Util.assertMainThread();
    .....
    //获取之前的请求
    Request previous = target.getRequest();
    // requestTracker是请求跟踪类对象,主要管理请求的发起,暂停,清除 
    // 如果之前的请求已存在 ,那么就清空这个,并从 requestTracker 移除
    if (previous != null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }
    //建立新的 请求
    Request request = buildRequest(target);
    // 设置 target 的request
    target.setRequest(request);
    //将target加入lifecycle
    lifecycle.addListener(target);
    //执行请求  
    requestTracker.runRequest(request);

    return target;
}

buildRequest 创建请求

先看看 创建请求 吧

private Request buildRequest(Target<TranscodeType> target) {
    if (priority == null) {
        priority = Priority.NORMAL;
    }
    return buildRequestRecursive(target, null);
}

private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
    if (thumbnailRequestBuilder != null) {

        ....
        // ①
        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        isThumbnailBuilt = true;
        Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
        isThumbnailBuilt = false;
        coordinator.setRequests(fullRequest, thumbRequest);
        return coordinator;
    } else if (thumbSizeMultiplier != null) { // ②
        ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
        Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
        Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
        coordinator.setRequests(fullRequest, thumbnailRequest);
        return coordinator;
    } else { // ③
        return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
    }
}

根据 DrawableTypeRequest 的设置 ,如果对 DrawableTypeRequest 对象调用了 thumbnail 方法 ,那么就会执行 ① 或 ② ,否则执行 ③

   private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
        RequestCoordinator requestCoordinator) {
    return GenericRequest.obtain(
            loadProvider,
            model,
            signature,
            context,
            priority,
            target,
            sizeMultiplier,
            placeholderDrawable,
            placeholderId,
            errorPlaceholder,
            errorId,
            fallbackDrawable,
            fallbackResource,
            requestListener,
            requestCoordinator,
            glide.getEngine(),
            transformation,
            transcodeClass,
            isCacheable,
            animationFactory,
            overrideWidth,
            overrideHeight,
            diskCacheStrategy);
}

GenericRequest.java

public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
        参数 ..... 
        DiskCacheStrategy diskCacheStrategy) {
    @SuppressWarnings("unchecked")
    GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
    if (request == null) {
        request = new GenericRequest<A, T, Z, R>();
    }
    //初始化 request
    request.init(loadProvider,
            参数 ....
            diskCacheStrategy);
    return request;
}

runRequest 执行请求

public void runRequest(Request request) {
    //将request对象添加到请求集合中
    requests.add(request);
    //如果当前状态是非暂停的,调用begin方法发送请求
    if (!isPaused) {
        request.begin();
    } else {
        //将请求添加到挂起的请求集合
        pendingRequests.add(request);
    }
}

GenericRequest.java

@Override
public void begin() {
    startTime = LogTime.getLogTime();
    if (model == null) {
        // 加载错误
        onException(null);
        return;
    }

    status = Status.WAITING_FOR_SIZE;
    // 验证宽高是否合法
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        // 注 1
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }
    //设置加载前的默认图片(placeholder)
    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    ....
}

注 1

public void onSizeReady(int width, int height) {
    ....
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    // 将请求状态更新为运行状态
    status = Status.RUNNING;
    
    //根据 DrawableRequestBuilder 类的 sizeMultiplier 方法设置的  izeMultiplier 
    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);

    ....

    loadedFromMemoryCache = true;
    // 注 1
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
    loadedFromMemoryCache = resource != null;
    ...
}

注 1 : engine.load 这个 方法 就进入了图片的加载

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
        DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    // 判断是否在主线程
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    final String id = fetcher.getId();
    // 创建 EngineKey 对象
    EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
            loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
            transcoder, loadProvider.getSourceEncoder());
    // 从内存缓存中查找
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        //缓存中存在,则回调 onResourceReady 
        cb.onResourceReady(cached);
        ...
        return null;
    }
    // 尝试从活动Resources 中获取,它表示的是当前正在使用的Resources,与内存缓存不同之处是clear缓存时不会clear它。
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        //存在,则回调 onResourceReady 
        cb.onResourceReady(active);
        ...
        return null;
    }
    // 从 jobs 中获取是不是已经存在了这个任务,如果current != null ,那么之前已经提交了这个任务
    EngineJob current = jobs.get(key);
    if (current != null) {
        //为这个任务增加回调
        current.addCallback(cb);
        ....
        return new LoadStatus(cb, current);
    }
    // 缓存中没有,也没有提交过任务,那么就创建新的 EngineJob 
    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
            transcoder, diskCacheProvider, diskCacheStrategy, priority);
    //创建一个 EngineRunnable 。这个是任务执行的入口 ,实现了 Runnable 接口
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    // 加入 jobs 
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    //任务开始
    engineJob.start(runnable);

    .....
    return new LoadStatus(cb, engineJob);
}

任务 开始 : engineJob.start(runnable);

public void start(EngineRunnable engineRunnable) {
    this.engineRunnable = engineRunnable;
    // diskCacheService 是线程池 , 那么就把 runable 提交给线程池了
    future = diskCacheService.submit(engineRunnable);
}

那么就看看 engineRunnable 的 run 方法都实现了什么吧

@Override
public void run() {
    if (isCancelled) {
        return;
    }

    Exception exception = null;
    Resource<?> resource = null;
    try {
        // 注 1
        resource = decode();
    } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "Exception decoding", e);
        }
        exception = e;
    }
    // 如果为取消,则回收各种资源
    if (isCancelled) {
        if (resource != null) {
            resource.recycle();
        }
        return;
    }

    if (resource == null) {
        // 加在失败
        onLoadFailed(exception);
    } else {
        // 加载成功
        onLoadComplete(resource);
    }
}

注 1

decode 方法就用来获取资源了

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
        // 从缓存中获取数据
        return decodeFromCache();
    } else {
        //从其他途径获取资源 (网络、file等)
        return decodeFromSource();
    }
}

decodeFromCache() 从缓存中获取数据

private Resource<?> decodeFromCache() throws Exception {
    Resource<?> result = null;
    try {
        result = decodeJob.decodeResultFromCache();
    } catch (Exception e) {
        ....
    }

    if (result == null) {
        result = decodeJob.decodeSourceFromCache();
    }
    return result;
}

那么就看看 decodeJob.decodeResultFromCache(); 里面干了什么

public Resource<Z> decodeResultFromCache() throws Exception {
    if (!diskCacheStrategy.cacheResult()) {
        return null;
    }

    long startTime = LogTime.getLogTime();
    //继续调用了 loadFromCache
    Resource<T> transformed = loadFromCache(resultKey);
    ...
    startTime = LogTime.getLogTime();
    Resource<Z> result = transcode(transformed);
    ...
    return result;
}

private Resource<T> loadFromCache(Key key) throws IOException {
    // 根据key从DiskCache获取文件
    File cacheFile = diskCacheProvider.getDiskCache().get(key);
    if (cacheFile == null) {
        return null;
    }

    Resource<T> result = null;
    try {
        // 把缓存文件读取解码生成Resource
        result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
    } finally {
        if (result == null) {
            diskCacheProvider.getDiskCache().delete(key);
        }
    }
    return result;
}

decodeFromSource 从其他途径获取资源 (网络、file等)

private Resource<?> decodeFromSource() throws Exception {
    //还是调用了decodeJob来获取与解码数据
    return decodeJob.decodeFromSource();
}

DecodeJob.java

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    return transformEncodeAndTranscode(decoded);
}

private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
        long startTime = LogTime.getLogTime();  
        // 注 1
        final A data = fetcher.loadData(priority);
        ....
        if (isCancelled) {
            return null;
        }
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}

读取数据

fetcher.loadData(priority);

fetcher 是 DataFetcher 接口 ,实现 DataFetcher 的接口 就可以改变数据获取的方式(网络、文件) 或者 途径(OkHttp,Volley) 了 。我们以原有的 HttpUrlFetcher 来分析。

HttpUrlFetcher.java

@Override
public InputStream loadData(Priority priority) throws Exception {
    return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
        throws IOException {
    .....
    // 工厂创建 HttpURLConnection 对象
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    // 设置 网络连接参数
    urlConnection.setConnectTimeout(2500);
    urlConnection.setReadTimeout(2500);
    urlConnection.setUseCaches(false); // 不使用网络缓存
    urlConnection.setDoInput(true);

    urlConnection.connect();
    if (isCancelled) {
        return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
        // 请求成功
        return getStreamForSuccessfulRequest(urlConnection);
    } else  {
        ...... 其他状态码判断
    }
}

gilde 的网络请求用到了 HttpURLConnection ,我么可以自定义 DataFetcher ,实现 DataFetcher 接口 ,从而使用其他库进行网络请求 ,如OkHttp,Volley。

加载完图片后自然就是进行缓存,留给下一次请求使用。省略缓存分析......

接下来就是回调显示

那么 回到 engineRunnable 里面的 run 方法

@Override
public void run() {
    ...
        resource = decode();
    ...
    if (resource == null) {
        onLoadFailed(exception);
    } else {
        onLoadComplete(resource);
    }
}

成功就调用 onLoadComplete ,失败就调用 onLoadFailed 。

private void onLoadComplete(Resource resource) {
    manager.onResourceReady(resource);
}

EngineJob.java

@Override
public void onResourceReady(final Resource<?> resource) {
    this.resource = resource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}

上面会发送一条 Message ,随后的操作就会回到了主线程处理。

接受消息

private static class MainThreadCallback implements Handler.Callback {

    @Override
    public boolean handleMessage(Message message) {
        if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
            EngineJob job = (EngineJob) message.obj;
            if (MSG_COMPLETE == message.what) {
                job.handleResultOnMainThread();
            } else {
                job.handleExceptionOnMainThread();
            }
            return true;
        }

        return false;
    }
}


private void handleResultOnMainThread() {
    if (isCancelled) {
        resource.recycle();
        return;
    } else if (cbs.isEmpty()) {
        throw new IllegalStateException("Received a resource without any callbacks to notify");
    }
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
    // synchronously released by one of the callbacks.
    engineResource.acquire();
    listener.onEngineJobComplete(key, engineResource);

    //注 1
    for (ResourceCallback cb : cbs) {
        if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            cb.onResourceReady(engineResource);
        }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();
}

注 1 :

方法中通过一个循环,调用了所有ResourceCallback的 onResourceReady()方法。

而 onResourceReady() 方法最后的调用就会用于刷新相对应的UI。

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

推荐阅读更多精彩内容