android图片缓存框架Glide解析

 

Glide 是基于 Picasso 的一套android图片缓存框架,它在 Picasso 的基础上做了大量优化与改进。

Glide 默认的 Bitmap 格式是 RGB_565 格式,而 Picasso 默认的是 ARGB_8888 格式,这个内存开销要小一半。

Glide的特点是能和activity、fragment的生命周期保持一致,这样应用退出后可以停止图片加载

Glide缓存分为MemoryCache和DiskCache,加载的时候可以设置,Glide整体框架如下图:

 android图片缓存框架Glide解析_第1张图片

 

可以看到所有加载图片的请求统一由RequestManager分发,Engine负责加载图片,接下来由实际的代码来分析这个开源框架:

 

String internetUrl = "http://i.imgur.com/DvpvklR.png";
Glide.with(context)
        .load(internetUrl)
        .thumbnail(0.1f)//用原图的十分之一作为缩略图
        .error(R.mipmap.ic_launcher)
        .into(targetImageView);

上面就是调用Glide加载图片的代码,很简洁,当然需要事先在build.gradle中添加依赖:

 

 

dependencies {
    compile 'com.github.bumptech.glide:glide:3.5.2'
}

 

然后mainfest.xml中声明权限:

 



 

使用就像上面这样就行了,那Glide整个加载流程是怎样的呢,虽然它只要一行代码就能加载好图片,但是中间的过程还是蛮多的

 

1、 Glide.with(context),看一下源码:

 

public static RequestManager with(FragmentActivity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();
    return retriever.get(activity);
}
 
public RequestManager get(FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        FragmentManager fm = activity.getSupportFragmentManager();
        return supportFragmentGet(activity, fm);
    }
}

 

可以看到Glide.with(context)的作用是获取到一个针对这个context的RequestManager,这里又分成两种情况,一个是当前线程是主线程,另一个是非主线程,分别来看一下两种情况下的处理:

1) 非主线程:

 

public RequestManager get(Context context) {
    if (context == null) {
        throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
        if (context instanceof FragmentActivity) {
            return get((FragmentActivity) context);
        } else if (context instanceof Activity) {
            return get((Activity) context);
        } else if (context instanceof ContextWrapper) {
            return get(((ContextWrapper) context).getBaseContext());
        }
    }

    return getApplicationManager(context);
} 

private RequestManager getApplicationManager(Context context) {
    // Either an application context or we're on a background thread.
    if (applicationManager == null) {
        synchronized (this) {
            if (applicationManager == null) {
                // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
                // However, in this case since the manager attached to the application will not receive lifecycle
                // events, we must force the manager to start resumed using ApplicationLifecycle.
                applicationManager = new RequestManager(context.getApplicationContext(),
                        new ApplicationLifecycle());
            }
        }
    }

    return applicationManager;
}

 

上面这种是加载图片的操作放在异步线程的情况,因为不是主线程所以无法监听onResume这种事件,只能在创建RequestManager的时候手动调用它的onStart来启动,接着看一下主线程情况下的处理:

 

RequestManager supportFragmentGet(Context context, final FragmentManager fm) {
    SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(TAG);
    if (current == null) {
        current = pendingSupportRequestManagerFragments.get(fm);
        if (current == null) {
            current = new SupportRequestManagerFragment();
            pendingSupportRequestManagerFragments.put(fm, current);
            fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    }
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, current.getLifecycle());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}

 

这种就能明显看出Glide和activity生命周期绑定的原理了,通过FragmentManger里面添加一个Fragment实例,这个实例在fragment的生命周期上调用RequestManager对应的生命周期处理方法,fragment的生命周期又和context所在的activity同步,因此实现了生命周期绑定

注意:每个context对应的RequestManager都是单实例的

 

2、 Glide.with(context).load(internetUrl),首先入口函数:

 

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

 

上面的DrawableTypeRequest是其中一种Request,我们拿它来举例,接着往下看:

 

@Override
public DrawableRequestBuilder load(ModelType model) {
    super.load(model);
    return this;
}
public GenericRequestBuilder load(ModelType model) {
    this.model = model;
    isModelSet = true;
    return this;
}

 

从上面可以看出最终返回一个DrawableTypeRequest,它继承DrawableRequestBuilder,DrawableRequestBuilder又继承GenericRequestBuilder,所以这个函数还是比较简单的

 

3、 Glide.with(context).load(internetUrl).into(targetImageView),还是先看一下入口函数:

 

@Override
public Target into(ImageView view) {
    return super.into(view);
}
 
public Target 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 into(Y target) {
    Util.assertMainThread();
    if (target == null) {
        throw new IllegalArgumentException("You must pass in a non null Target");
    }
    if (!isModelSet) {
        throw new IllegalArgumentException("You must first set a model (try #load())");
    }

    Request previous = target.getRequest();

    if (previous != null) {
        previous.clear();
        requestTracker.removeRequest(previous);
        previous.recycle();
    }

    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    requestTracker.runRequest(request);

    return target
}

 

可以看出最终调用了GenericRequestBuilder类的into方法,主要方法就是最后的runRequest方法了,来看一下:

 

public void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}

 

 这里又调用了begin方法,这个是接口,真正实现在GenericRequest里:

 

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

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {
        target.getSize(this);
    }

    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
}

 

主要函数是onSizeReady,再看一下:

 

@Override
public void onSizeReady(int width, int height) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
        return;
    }
    status = Status.RUNNING;

    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);

    ModelLoader modelLoader = loadProvider.getModelLoader();
    final DataFetcher dataFetcher = modelLoader.getResourceFetcher(model, width, height);

    if (dataFetcher == null) {
        onException(new Exception("Got null fetcher from model loader"));
        return;
    }
    ResourceTranscoder transcoder = loadProvider.getTranscoder();
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    loadedFromMemoryCache = true;
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
    loadedFromMemoryCache = resource != null;
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
}

 

看到这边我们才看到Glide加载图片的异步性体现在哪里,可以看到调用了engine.load这个函数,我们继续往下看:

 

public  LoadStatus load(Key signature, int width, int height, DataFetcher fetcher,
        DataLoadProvider loadProvider, Transformation transformation, ResourceTranscoder transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    final String id = fetcher.getId();
    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) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }

    EngineResource active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
    }

    EngineJob current = jobs.get(key);
    if (current != null) {
        current.addCallback(cb);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Added to existing load", startTime, key);
        }
        return new LoadStatus(cb, current);
    }

    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob decodeJob = new DecodeJob(key, width, height, fetcher, loadProvider, transformation,
            transcoder, diskCacheProvider, diskCacheStrategy, priority);
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
}

 

看到这边就真相大白了,EngineRunnable是一个实现Runnable的线程类,engineJob.start(runnable)会把runnable放到线程池里面执行,执行过程就是调用decodeJob内的函数,执行完毕会调用回调函数在ui上显示

 

4、图片缓存机制

Glide在每次从LruCache里获取到图片都会放到activeResources里,activeResources里面的对象都是弱引用对象,也就是垃圾回收扫描到立刻回收,但是回收的对象会被放到引用队列里,为了处理引用队列里被回收的对象,Glide为引用队列添加了一个listener:

 

private ReferenceQueue> getReferenceQueue() {
        if (resourceReferenceQueue == null) {
            resourceReferenceQueue = new ReferenceQueue>();
            MessageQueue queue = Looper.myQueue();
            queue.addIdleHandler(new RefQueueIdleHandler(activeResources, resourceReferenceQueue));
        }
        return resourceReferenceQueue;
 }

 

 

 private static class RefQueueIdleHandler implements MessageQueue.IdleHandler {
        private final Map>> activeResources;
        private final ReferenceQueue> queue;

        public RefQueueIdleHandler(Map>> activeResources,
                ReferenceQueue> queue) {
            this.activeResources = activeResources;
            this.queue = queue;
        }

        @Override
        public boolean queueIdle() {
            ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
            if (ref != null) {
                activeResources.remove(ref.key);
            }

            return true;
        }
    }

这个listener任务就是在线程空闲的时候会把引用队列里所有被回收的对象从activeResources里清除,这样的话就能把被回收对象从activeResources里清除,不会占用空间

 

 

总结

经过上面的分析可以看到Glide一行代码加载图片,中间的流程却是一个完整的MVC过程,这个过程实现了

1、 获取context对应的全局唯一的RequestManager实例

2、 主线程内RequestManager和activity等ui的生命周期保持一致,非主线程和application一致

3、 通过RequestManager分发request给Engine,Engine把每个request扔到线程池里运行,结束后回调

4、 RequestManager还会监听网络变化,网络连接恢复时会自动执行那些执行失败但是没有被取消的任务

 

 

 

你可能感兴趣的:(android图片缓存框架Glide解析)