经验分享 五月 12, 2022

Source Code Review

文章字数 55k 阅读约需 1:40 阅读次数 0

引言

本篇将介绍我在阅读开源框架源码时的一些笔记分享。

对于计算机编程开发的基础笔记可参阅:


okHttp

okHttp

Reference


Intro

This Intro will show you how to create a okHttpClient and process a new Request base on Sync or Async.

// TODO 1: Create a okHttpClient
private OkHttpClient okHttpClient=new OkHttpClient.Builder()
        // .callTimeout(10, TimeUnit.SECONDS)
        // .readTimeout(10, TimeUnit.SECONDS)
        .addInterceptor(new LogInterceptor())
        .build();

// TODO 2: Create a Request
private Request request=new Request.Builder()
        .url("base url")
        .get()
        .build();

// TODO 3: Sync Request
        okHttpClient.newCall(request).execute();

// OR

// TODO 3: Async Request
        okHttpClient.newCall(request).enqueue(new Callback(){
@Override
public void onFailure(@NonNull Call call,@NonNull IOException e){
        System.out.println("Exception: "+e);
        }

@Override
public void onResponse(@NonNull Call call,@NonNull Response response)throws IOException{
        int code=response.code();
        String message=response.message();
        Headers headers=response.headers();
        ResponseBody body=response.body();

        if(code==200&&body!=null){
        System.out.println("responseCode: %d responseMessage: %s responseHeaders: %s responseBody:%s %n",code,message,headers,body);
        }
        }
        });

okHttp Dispatcher ( Kotlin Style)

This Dispatcher will been called if you want to start a Sync or Async Request.

Core Properties

  • maxRequests

The maximum number of requests to execute concurrently

// Dispatcher.kt

/**
 * The maximum number of requests to execute concurrently. Above this requests queue in memory,
 * waiting for the running calls to complete.
 *
 * If more than [maxRequests] requests are in flight when this is invoked, those requests will
 * remain in flight.
 */
@get:Synchronized
var maxRequests = 64
    set(maxRequests) {
        require(maxRequests >= 1) { "max < 1: $maxRequests" }
        synchronized(this) {
            field = maxRequests
        }
        promoteAndExecute()
    }

  • maxRequestsPerHost
// Dispatcher.kt

/**
 * The maximum number of requests for each host to execute concurrently. This limits requests by
 * the URL's host name. Note that concurrent requests to a single IP address may still exceed this
 * limit: multiple hostnames may share an IP address or be routed through the same HTTP proxy.
 *
 * If more than [maxRequestsPerHost] requests are in flight when this is invoked, those requests
 * will remain in flight.
 *
 * WebSocket connections to hosts **do not** count against this limit.
 */
@get:Synchronized
var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
        require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
        synchronized(this) {
            field = maxRequestsPerHost
        }
        promoteAndExecute()
    }

  • runningSyncCalls

Each Sync Call will be added into a ArrayList which contain with RealCall.

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()

  • runningAsyncCalls

Each Async Call will be added into a ArrayList which contain with RealCall.

This ArrayList will includes canceled calls that haven’t finished yet.

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()

Core Function

  • executed ( Sync )

add Each of Sync-Call into a ArrayList given above.

// Dispatcher.kt

/** Used by [Call.execute] to signal it is in-flight. */
@Synchronized
internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
}

  • enqueue ( Async )

add Each of Async-Call into a Request-Queue.

// Dispatcher.kt

internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
        // TODO 1: add current call into Request-Queue
        readyAsyncCalls.add(call)

        // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to the same host.
        if (!call.call.forWebSocket) {
            // TODO 2: change the AsyncCall with Singleton Mode

            val existingCall = findExistingCallWithHost(call.host)
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
        }
    }
    promoteAndExecute()
}

  • findExistingCallWithHost ( Async )

if there has a exist Async-Call that is Host want, return it directly.

// Dispatcher.kt

private fun findExistingCallWithHost(host: String): AsyncCall? {
    for (existingCall in runningAsyncCalls) {
        if (existingCall.host == host) return existingCall
    }
    for (existingCall in readyAsyncCalls) {
        if (existingCall.host == host) return existingCall
    }
    return null
}

okHttp Transmitter

Bridge between the application and network layers. supports asynchronous canceling.


Core Function

  • canRetry

  • cancel

This function lets you can Cancel current Connection manually.

// Transmitter.java

/**
 * Immediately closes the socket connection if it's currently held. Use this to interrupt an
 * in-flight request from any thread. It's the caller's responsibility to close the request body
 * and response body streams; otherwise resources may be leaked.
 *
 * This method is safe to be called concurrently, but provides limited guarantees. If a
 * transport layer connection has been established (such as a HTTP/2 stream) that is terminated.
 * Otherwise if a socket connection is being established, that is terminated.
 */
public void cancel(){
        Exchange exchangeToCancel;
        RealConnection connectionToCancel;
synchronized (connectionPool){
        canceled=true;
        exchangeToCancel=exchange;
        connectionToCancel=exchangeFinder!=null&&exchangeFinder.connectingConnection()!=null
        ?exchangeFinder.connectingConnection()
        :connection;
        }
        if(exchangeToCancel!=null){
        // TODO 1: Cancel Data Exchange
        exchangeToCancel.cancel();

        }else if(connectionToCancel!=null){
        // TODO 2: Cancel Connection
        connectionToCancel.cancel();

        }
        }

okHttp Interceptor

Interceptor is the most important Part in okHttp. Each Interceptor has its unique usage in the Internet Request. Such as Catch and Save the Log and Cache the Internet-Data.

Interceptor Priority

In okHttp, Interceptor can be called by Responsibility-Chain, each Call will has its own Chain and you can add you custom Interceptor into this Chain using addInterceptor() or addInterceptor().

  • UserInterceptor

  • RetryAndFollowUpInterceptor

  • BridgeInterceptor

  • CacheInterceptor

  • ConnectInterceptor

  • CallServerInterceptor

// RealCall.java

Response getResponseWithInterceptorChain()throws IOException{
        // Build a full stack of interceptors.
        List<Interceptor> interceptors=new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(new RetryAndFollowUpInterceptor(client));
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if(!forWebSocket){
        interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));

        Interceptor.Chain chain=new RealInterceptorChain(
        interceptors,                   // List<Interceptor> interceptors
        transmitter,                    // Transmitter transmitter
        null,                           // Exchange exchange
        0,                              // int index
        originalRequest,                // Request request
        this,                           // Call call,
        client.connectTimeoutMillis(),  // int connectTimeout
        client.readTimeoutMillis(),     // int readTimeout
        client.writeTimeoutMillis()     // int writeTimeout
        );

        ...
        }

RetryAndFollowUpInterceptor

This interceptor will be called by Try to recover the failure Connection.

Usage:

  • Retry Request

  • Re-direct


Core function:

  • recover

This function will follow this Execution-Order in order to recover the Connection.

  • Rule 1

  • Rule 2

  • Rule 3

  • Rule 4

  • Retry if all fail

// RetryAndFollowUpInterceptor.java

/**
 * Report and attempt to recover from a failure to communicate with a server. Returns true if
 * {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
 * be recovered if the body is buffered or if the failure occurred before the request has been
 * sent.
 */
private boolean recover(IOException e,Transmitter transmitter,
        boolean requestSendStarted,Request userRequest){

        // TODO Rule 1: The application layer has forbidden retries.
        if(!client.retryOnConnectionFailure())return false;

        // TODO Rule 2: We can't send the request body again.
        if(requestSendStarted&&requestIsOneShot(e,userRequest))return false;

        // TODO Rule 3: This exception is fatal.
        if(!isRecoverable(e,requestSendStarted))return false;

        // TODO Rule 4: No more routes to attempt.
        if(!transmitter.canRetry())return false;

        // TODO 5: if all the rules are fail, retry current request.

        // For failure recovery, use the same route selector with a new connection.
        return true;
        }

BridgeInterceptor

This Interceptor will be called when Packing Request, Response…

Usage:

  • Packing Request

  • Packing Response

  • Setting up: Content-Length, Content-Encoding, GZip-Compressing, User-Agent, Host, Keep-Alive

  • Add Cookie


CacheInterceptor

This Interceptor will be called if you want to Caching Data.

Usage:

  • Create a Caching strategies base on request, response, time.

  • check if already Cached the Request and Response on the disk

if Cache exist, return it and close request, this function is unsupported default ( default
value: false ).

if Cache exist and the Caching strategies is “Don’t use the Internet”, return the Cache
directly.


ConnectInterceptor

This Interceptor will be called when sending the DNS Request and Try to Establish TCP Connection.

Usage:

  • DNS resolve, Socket Connect ( TLS included )

Source Code of DNS resolve which located in ExchangeFinder.java:

// ExchangeFinder.java

/**
 * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
 * until a healthy connection is found.
 */
private RealConnection findHealthyConnection(
        int connectTimeout,
        int readTimeout,
        int writeTimeout,
        int pingIntervalMillis,
        boolean connectionRetryEnabled,
        boolean doExtensiveHealthChecks
        )throws IOException{
        while(true){
        RealConnection candidate=findConnection(connectTimeout,readTimeout,writeTimeout,pingIntervalMillis,connectionRetryEnabled);

// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool){
        if(candidate.successCount==0&&!candidate.isMultiplexed()){
        return candidate;
        }
        }

        // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
        // isn't, take it out of the pool and start again.
        if(!candidate.isHealthy(doExtensiveHealthChecks)){
        candidate.noNewExchanges();
        continue;
        }

        return candidate;
        }
        }

Source Code of Socket ( TLS ) which located in ExchangeFinder.java:

// ExchangeFinder.java

/**
 * Returns a connection to host a new stream. This prefers the existing connection if it exists,
 * then the pool, finally building a new connection.
 */
private RealConnection findConnection(
        int connectTimeout,
        int readTimeout,
        int writeTimeout,
        int pingIntervalMillis,
        boolean connectionRetryEnabled
        )throws IOException{
        boolean foundPooledConnection=false;
        RealConnection result=null;
        Route selectedRoute=null;
        RealConnection releasedConnection;
        Socket toClose;

// TODO 1. Check if the connection should be release.
synchronized (connectionPool){
        if(transmitter.isCanceled())throw new IOException("Canceled");
        hasStreamFailure=false; // This is a fresh attempt.

        // Attempt to use an already-allocated connection. We need to be careful here because our
        // already-allocated connection may have been restricted from creating new exchanges.
        releasedConnection=transmitter.connection;
        toClose=transmitter.connection!=null&&transmitter.connection.noNewExchanges
        ?transmitter.releaseConnectionNoEvents()
        :null;

        if(transmitter.connection!=null){
        // We had an already-allocated connection and it's good.
        result=transmitter.connection;
        releasedConnection=null;
        }

        if(result==null){

        // TODO 2: Try to get the RealConnection in the ConnectionPool

        // Attempt to get a connection from the pool.
        if(connectionPool.transmitterAcquirePooledConnection(address,transmitter,null,false)){
        foundPooledConnection=true;
        result=transmitter.connection;
        }else if(nextRouteToTry!=null){
        selectedRoute=nextRouteToTry;
        nextRouteToTry=null;
        }else if(retryCurrentRoute()){

        // TODO 3: if already setting to use current Route to retry, reuse current Route
        selectedRoute=transmitter.connection.route();
        }
        }
        }
        closeQuietly(toClose);

        if(releasedConnection!=null){
        eventListener.connectionReleased(call,releasedConnection);
        }
        if(foundPooledConnection){
        eventListener.connectionAcquired(call,result);
        }
        if(result!=null){
        // TODO 4: 

        // If we found an already-allocated or pooled connection, we're done.
        return result;
        }

        // TODO 5:

        // If we need a route selection, make one. This is a blocking operation.
        boolean newRouteSelection=false;
        if(selectedRoute==null&&(routeSelection==null||!routeSelection.hasNext())){
        newRouteSelection=true;

        // TODO 6: Await DNS resolve until we get the result
        routeSelection=routeSelector.next();
        }

        List<Route> routes=null;
synchronized (connectionPool){
        if(transmitter.isCanceled())throw new IOException("Canceled");

        if(newRouteSelection){
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        routes=routeSelection.getAll();

        // TODO 7: if we use routeSelector() and get the new Route(a set of new IP), check if we have reuseable Connection in ConnectionPool
        if(connectionPool.transmitterAcquirePooledConnection(
        address,transmitter,routes,false)){
        foundPooledConnection=true;
        result=transmitter.connection;
        }
        }

        if(!foundPooledConnection){
        if(selectedRoute==null){

        // TODO 8: use routeSelection() to get one of IP
        selectedRoute=routeSelection.next();
        }

        // TODO 9: use the new Route to create a new Connection with connect

        // Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        result=new RealConnection(connectionPool,selectedRoute);
        connectingConnection=result;
        }
        }

        // TODO 10:

        // If we found a pooled connection on the 2nd time around, we're done.
        if(foundPooledConnection){
        eventListener.connectionAcquired(call,result);
        return result;
        }

        // TODO 11:

        // Do TCP + TLS handshakes. This is a blocking operation.
        result.connect(connectTimeout,readTimeout,writeTimeout,pingIntervalMillis,connectionRetryEnabled,call,eventListener);

        // TODO 12: put the RealConnection into ConnectionPool if connect successful

        connectionPool.routeDatabase.connected(result.route());

        Socket socket=null;
synchronized (connectionPool){
        connectingConnection=null;
        // Last attempt at connection coalescing, which only occurs if we attempted multiple
        // concurrent connections to the same host.
        if(connectionPool.transmitterAcquirePooledConnection(address,transmitter,routes,true)){
        // We lost the race! Close the connection we created and return the pooled connection.
        result.noNewExchanges=true;
        socket=result.socket();
        result=transmitter.connection;

        // It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
        // that case we will retry the route we just successfully connected with.
        nextRouteToTry=selectedRoute;
        }else{
        connectionPool.put(result);
        transmitter.acquireConnectionNoEvents(result);
        }
        }
        closeQuietly(socket);

        eventListener.connectionAcquired(call,result);
        return result;
        }

Conclusion of findConnection :

  • Step 1: check the saved Connection if qualify in current ExchangeFinder

  • Step 2: check the saved Connection if qualify in current ConnectionPool

  • Step 3: check the Route (sealed Object which has proxy and IP) if qualify in current RouteSelector
    Table

  • Step 4: double check the available Route when we get the new Route after DNS request, else crete a
    new RealConnection

  • Step 5: use the RealConnection to proceed TCP and TLS Connect and save it if connect successful


CallServerInterceptor

This is the last Interceptor will be called by okHttpClient.

Usage:

  • send Request-body to Remote-Server if we have

  • Create a Response Object after read Response-Header, if we have Request-body, Recreate a new
    Response Object

  • send the Request


Retrofit

Retrofit

Reference

Retrofit Official

Retrofit 源码解析


Intro

This Intro will show you how to create a Retrofit Client and process a new Request.

// TODO 1: Create a interface
interface NewsAPi {
  @Headers("apikey:you key")
  @GET("word/word")
  Call<News> getNews(@Query("num") String num,@Query("page") String page)
}

// TODO 2: Create a Retrofit ( which more like the okHttpClient)
val retrofit = Retrofit.Builder()
  .addConverterFactory(GsonConverterFactory.create())
  .baseUrl("BASE_URL")
  .build()

// TODO 3: use create() function to create a Request, this function has use Dynamic-Proxy
newsApi = retrofit.create(
  NewsAPi.class)

// TODO 4: Use the API to get Data from Remote-Server
          Call < News > news = newsApi . getNews ("1", "10"
)
news.enqueue(new Callback < News > {
  @Override
  public void onResponse(Call<News> call, Response<News> response) {
    // Handle Response
  }

  @Override
  public void onFailure(Call<News> call, Throwable t) {
    // Handle Error
  }
})

Annotation

Request Method

Request Method Info
@GET Get Request
@POST Post Request
@PUT Put Request
@DELETE Delete Request
@HEAD Head Request
@OPTIONS Option Request
@PATCH Patch Request

Request Annotation

Request Annotation Info
@Headers Add Request Header
@Path Replace Path
@Query Query Parameters combine with @GET
@FormUrlEncoded Commit Table Data
@Field Parameters combine with @POST
@Body Upload file or Commit Json Data

Core Function

Priority

create -> loadServiceMethod -> enqueue -> createRawCall -> constructor of OkHttpCall ->
parseAnnotations -> build -> parseMethodAnnotations

  • create

Use this function to create a Retrofit Client.

// Retrofit.java

public<T> T create(final Class<T> service){
        validateServiceInterface(service);

        // TODO 1: Dynamic-Proxy
        return(T)
        // Java Reflect usage
        Proxy.newProxyInstance(
        // TODO 2: Obtain a ClassLoader
        service.getClassLoader(),
        new Class<?>[]{service},
        new InvocationHandler(){
private final Platform platform=Platform.get();
private final Object[]emptyArgs=new Object[0];

// TODO 3: parameters-table: 
// 
// proxy: which object need to be proxy 
// method: which method need to be call by proxy-object
// args: which parameter should be pass

@Override
public @Nullable Object invoke(Object proxy,Method method,@Nullable Object[]args)throws Throwable{
        // If the method is a method from Object then defer to normal invocation.
        if(method.getDeclaringClass()==Object.class){
        return method.invoke(this,args);
        }

        args=args!=null?args:emptyArgs;

        return platform.isDefaultMethod(method)
        ?platform.invokeDefaultMethod(method,service,proxy,args)
        :loadServiceMethod(method).invoke(args);
        }
        });
        }
  • loadServiceMethod

Use this method to resolve the Method-Annotation.

Recommend: read this function with parseAnnotations() together.

// Retrofit.java

ServiceMethod<?> loadServiceMethod(Method method){
        // TODO 1: get Method from ConcurrentHashMap which call serviceMethodCache 
        ServiceMethod<?> result=serviceMethodCache.get(method);
        if(result!=null)return result;

synchronized (serviceMethodCache){
        result=serviceMethodCache.get(method);

        if(result==null){

        // TODO 2: Create a ServiceMethod object
        result=ServiceMethod.parseAnnotations(this,method);

        // TODO 3: Caching ServiceMethod object to optimize the performance
        serviceMethodCache.put(method,result);
        }
        }
        return result;
        }
  • build

As above we have just call Retrofit Instance is a client, in this function, a Retrofit Instance
will contain a okHttpClient.

// Retrofit.java

public Retrofit build(){
        if(baseUrl==null){
        throw new IllegalStateException("Base URL required.");
        }

        okhttp3.Call.Factory callFactory=this.callFactory;
        if(callFactory==null){
        // TODO 1: Same usage with okHttpClient
        callFactory=new OkHttpClient();
        }

        Executor callbackExecutor=this.callbackExecutor;
        if(callbackExecutor==null){
        callbackExecutor=platform.defaultCallbackExecutor();
        }

        // Make a defensive copy of the adapters and add the default Call adapter.
        List<CallAdapter.Factory>callAdapterFactories=new ArrayList<>(this.callAdapterFactories);
        callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

        // TODO3: Create a converter

        // Make a defensive copy of the converters.
        List<Converter.Factory>converterFactories=new ArrayList<>(1+this.converterFactories.size()+platform.defaultConverterFactoriesSize());

        // Add the built-in converter factory first. This prevents overriding its behavior but also
        // ensures correct behavior when using converters that consume all types.
        converterFactories.add(new BuiltInConverters());
        converterFactories.addAll(this.converterFactories);
        converterFactories.addAll(platform.defaultConverterFactories());

        return new Retrofit(
        callFactory,
        baseUrl,
        unmodifiableList(converterFactories),
        unmodifiableList(callAdapterFactories),
        callbackExecutor,
        validateEagerly);
        }

  • enqueue

This function will use okHttpClient to process a Async-Request.

// OkHttpClient.java

@Override
public void enqueue(final Callback<T> callback){
        Objects.requireNonNull(callback,"callback == null");

        // TODO 1: Define a object of okhttp3.Call to request from internet 
        okhttp3.Call call;
        Throwable failure;

synchronized (this){
        if(executed)throw new IllegalStateException("Already executed.");
        executed=true;

        call=rawCall;
        failure=creationFailure;
        if(call==null&&failure==null){

        try{
        // TODO 2: assign the value of okHttp3.Call
        call=rawCall=createRawCall();

        }catch(Throwable t){
        throwIfFatal(t);
        failure=creationFailure=t;
        }
        }
        }

        if(failure!=null){
        callback.onFailure(this,failure);
        return;
        }

        if(canceled){
        call.cancel();
        }

        // TODO 3: call the enqueue function to process the perform the real Internet request
        call.enqueue(
        new okhttp3.Callback(){
@Override
public void onResponse(okhttp3.Call call,okhttp3.Response rawResponse){
        Response<T> response;
        try{

        // TODO 4: parse the Response
        response=parseResponse(rawResponse);
        }catch(Throwable e){
        throwIfFatal(e);
        callFailure(e);
        return;
        }

        try{
        // TODO 5: Success callback
        callback.onResponse(OkHttpCall.this,response);

        }catch(Throwable t){
        throwIfFatal(t);
        t.printStackTrace(); // TODO this is not great
        }
        }

@Override
public void onFailure(okhttp3.Call call,IOException e){
        callFailure(e);
        }

private void callFailure(Throwable e){
        try{
        // TODO 6: Failure Callback
        callback.onFailure(OkHttpCall.this,e);

        }catch(Throwable t){
        throwIfFatal(t);
        t.printStackTrace(); // TODO this is not great
        }
        }
        });
        }

  • createRawCall

This function will be use to create a new Call ( RealCall ) in okHttp by use CallFactory.

// OkHttpClient.java

private okhttp3.Call createRawCall()throws IOException{
        okhttp3.Call call=callFactory.newCall(requestFactory.create(args));
        if(call==null){
        throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
        }

  • constructor of OkHttpCall
// HttpServiceMethod.java

HttpServiceMethod(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter
        ){
        this.requestFactory=requestFactory;

        // TODO: initialize the callFactory in HttpServiceMethod construct
        this.callFactory=callFactory;
        this.responseConverter=responseConverter;
        }

  • parseAnnotations
static<ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
        Retrofit retrofit,
        Method method,
        RequestFactory requestFactory
        ){
        boolean isKotlinSuspendFunction=requestFactory.isKotlinSuspendFunction;
        boolean continuationWantsResponse=false;
        boolean continuationBodyNullable=false;

        ...

        // TODO 1: get the object by Retrofit
        okhttp3.Call.Factory callFactory=retrofit.callFactory;

        // TODO 2: check if Kotlin-Suspend-Function Environment
        if(!isKotlinSuspendFunction){

        // return if non-Kotlin Env
        return new CallAdapted<>(requestFactory,callFactory,responseConverter,callAdapter);

        }else if(continuationWantsResponse){
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return(HttpServiceMethod<ResponseT, ReturnT>)
        new SuspendForResponse<>(
        requestFactory,
        callFactory,
        responseConverter,
        (CallAdapter<ResponseT, Call<ResponseT>>)callAdapter);
        }else{
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return(HttpServiceMethod<ResponseT, ReturnT>)
        new SuspendForBody<>(
        requestFactory,
        callFactory,
        responseConverter,
        (CallAdapter<ResponseT, Call<ResponseT>>)callAdapter,
        continuationBodyNullable);
        }
        }

  • build()
// Retrofit.java
public Retrofit build(){
        if(baseUrl==null){
        throw new IllegalStateException("Base URL required.");
        }

        okhttp3.Call.Factory callFactory=this.callFactory;

        if(callFactory==null){
        // TODO: link to okHttp
        callFactory=new OkHttpClient();

        }

        ...

        return new Retrofit(
        callFactory,
        baseUrl,
        unmodifiableList(converterFactories),
        unmodifiableList(callAdapterFactories),
        callbackExecutor,
        validateEagerly);
        }

  • parseMethodAnnotation()
// RequestFactory.java

static RequestFactory parseAnnotations(Retrofit retrofit,Method method){
        return new Builder(retrofit,method).build();
        }

  • build()
// RequestFactory.java

// focus on build()
RequestFactory build(){
        for(Annotation annotation:methodAnnotations){
        parseMethodAnnotation(annotation);
        }

        ...

        return new RequestFactory(this);
        }

  • parseMethodAnnotation()

using this function to resolve the Annotation like: @GET, @POST …

// RequestFactory.java
private void parseMethodAnnotation(Annotation annotation){
        if(annotation instanceof DELETE){
        parseHttpMethodAndPath("DELETE",((DELETE)annotation).value(),false);
        }else if(annotation instanceof GET){
        parseHttpMethodAndPath("GET",((GET)annotation).value(),false);
        }else if(annotation instanceof HEAD){
        parseHttpMethodAndPath("HEAD",((HEAD)annotation).value(),false);
        }else if(annotation instanceof PATCH){
        parseHttpMethodAndPath("PATCH",((PATCH)annotation).value(),true);
        }else if(annotation instanceof POST){
        parseHttpMethodAndPath("POST",((POST)annotation).value(),true);
        }else if(annotation instanceof PUT){
        parseHttpMethodAndPath("PUT",((PUT)annotation).value(),true);
        }else if(annotation instanceof OPTIONS){
        parseHttpMethodAndPath("OPTIONS",((OPTIONS)annotation).value(),false);
        }else if(annotation instanceof HTTP){
        HTTP http=(HTTP)annotation;
        parseHttpMethodAndPath(http.method(),http.path(),http.hasBody());
        }else if(annotation instanceof retrofit2.http.Headers){
        String[]headersToParse=((retrofit2.http.Headers)annotation).value();
        if(headersToParse.length==0){
        throw methodError(method,"@Headers annotation is empty.");
        }
        headers=parseHeaders(headersToParse);
        }else if(annotation instanceof Multipart){
        if(isFormEncoded){
        throw methodError(method,"Only one encoding annotation is allowed.");
        }
        isMultipart=true;
        }else if(annotation instanceof FormUrlEncoded){
        if(isMultipart){
        throw methodError(method,"Only one encoding annotation is allowed.");
        }
        isFormEncoded=true;
        }
        }

Core Object

Call.java

Same as the okHttpCall ( RealCall ).

// Call.java

/**
 * An invocation of a Retrofit method that sends a request to a webserver and returns a response.
 * Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple
 * calls with the same parameters to the same webserver; this may be used to implement polling or to
 * retry a failed call.
 *
 * <p>Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link
 * #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that
 * is busy writing its request or reading its response may receive a {@link IOException}; this is
 * working as designed.
 *
 * @param <T> Successful response body type.
 */
public interface Call<T> extends Cloneable {
    /**
     * Synchronously send the request and return its response.
     *
     * @throws IOException if a problem occurred talking to the server.
     * @throws RuntimeException (and subclasses) if an unexpected error occurs creating the request or
     *     decoding the response.
     */
    Response<T> execute() throws IOException;

    /**
     * Asynchronously send the request and notify {@code callback} of its response or if an error
     * occurred talking to the server, creating the request, or processing the response.
     */
    void enqueue(Callback<T> callback);

    /**
     * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
     * #enqueue(Callback) enqueued}. It is an error to execute or enqueue a call more than once.
     */
    boolean isExecuted();

    /**
     * Cancel this call. An attempt will be made to cancel in-flight calls, and if the call has not
     * yet been executed it never will be.
     */
    void cancel();

    /** True if {@link #cancel()} was called. */
    boolean isCanceled();

    /**
     * Create a new, identical call to this one which can be enqueued or executed even if this call
     * has already been.
     */
    Call<T> clone();

    /** The original HTTP request. */
    Request request();

    /**
     * Returns a timeout that spans the entire call: resolving DNS, connecting, writing the request
     * body, server processing, and reading the response body. If the call requires redirects or
     * retries all must complete within one timeout period.
     */
    Timeout timeout();
}

Glide

Reference

Intro

Glide.with(context).load("Image_URL").into(imageView);

Core Function

  • with

This function will judge the Lifecycle owner by Activity / Fragment / Context / View

// Glide.java ( Activity Usage )

/**
 * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle
 * and that uses the given {@link Activity}'s default options.
 *
 * @param activity The activity to use.
 * @return A RequestManager for the given activity that can be used to start a load.
 */
@NonNull
public static RequestManager with(@NonNull Activity activity){
        return getRetriever(activity).get(activity);
        }

  • with -> get ( Foreground Usage)

This function will check current state of the Image ( Foreground / Background)

// RequestManagerRetriever.java

@NonNull
public RequestManager get(@NonNull FragmentActivity activity){
        // TODO 1: Check current Thread first
        if(Util.isOnBackgroundThread()){
        // Current Thread is on Background
        // TODO 2: Call the get function again and pass the ApplicationContext in order to let Glide auto-change the current Thread into Background
        return get(activity.getApplicationContext());
        }else{
        // Current Thread is on Foreground
        assertNotDestroyed(activity);
        frameWaiter.registerSelf(activity);
        FragmentManager fm=activity.getSupportFragmentManager();

        return supportFragmentGet(activity,fm, /*parentHint=*/ null,isActivityVisible(activity));
        }
        }

  • with -> get ( Background Usage)

This function will check current state of the Image ( Foreground / Background)

// RequestManagerRetriever.java

@NonNull
public RequestManager get(@NonNull 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
        // Only unwrap a ContextWrapper if the baseContext has a non-null application context.
        // Context#createPackageContext may return a Context without an Application instance,
        // in which case a ContextWrapper may be used to attach one.
        &&((ContextWrapper)context).getBaseContext().getApplicationContext()!=null){
        return get(((ContextWrapper)context).getBaseContext());
        }
        }

        return getApplicationManager(context);
        }

  • with -> get -> getApplicationManager

This function will be Called when you pass Application Context into Glide.

// RequestManagerRetriever.java

@NonNull
private RequestManager getApplicationManager(@NonNull 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.

        // TODO(b/27524013): Factor out this Glide.get() call.
        Glide glide=Glide.get(context.getApplicationContext());
        applicationManager=
        factory.build(
        glide,
        // if we have load the image background, Bind the Glide Lifecycle with Application Lifecycle
        new ApplicationLifecycle(),
        new EmptyRequestManagerTreeNode(),
        context.getApplicationContext()
        );
        }
        }
        }

        return applicationManager;
        }

  • with -> get -> getApplicationManager -> supportFragmentGet | getSupportRequestManagerFragment

This two function will be called when current Lifecycle-Owner of Glide is Fragment, use this
function to check it.

// RequestManagerRetriever.java

/* ======================== supportFragmentGet ======================== */

@NonNull
private RequestManager supportFragmentGet(
@NonNull Context context,
@NonNull FragmentManager fm,
@Nullable Fragment parentHint,
        boolean isParentVisible){
        SupportRequestManagerFragment current=getSupportRequestManagerFragment(fm,parentHint);
        RequestManager requestManager=current.getRequestManager();
        if(requestManager==null){
        // TODO(b/27524013): Factor out this Glide.get() call.
        Glide glide=Glide.get(context);
        requestManager=factory.build(glide,current.getGlideLifecycle(),current.getRequestManagerTreeNode(),context);
        // This is a bit of hack, we're going to start the RequestManager, but not the
        // corresponding Lifecycle. It's safe to start the RequestManager, but starting the
        // Lifecycle might trigger memory leaks. See b/154405040
        if(isParentVisible){
        requestManager.onStart();
        }
        current.setRequestManager(requestManager);
        }
        return requestManager;
        }

/* ======================== getSupportRequestManagerFragment ======================== */

@NonNull
private SupportRequestManagerFragment getSupportRequestManagerFragment(
@NonNull final FragmentManager fm,@Nullable Fragment parentHint){
        // If we have a pending Fragment, we need to continue to use the pending Fragment. Otherwise
        // there's a race where an old Fragment could be added and retrieved here before our logic to
        // add our pending Fragment notices. That can then result in both the pending Fragmeng and the
        // old Fragment having requests running for them, which is impossible to safely unwind.
        SupportRequestManagerFragment current=pendingSupportRequestManagerFragments.get(fm);
        if(current==null){
        current=(SupportRequestManagerFragment)fm.findFragmentByTag(FRAGMENT_TAG);
        if(current==null){
        current=new SupportRequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        pendingSupportRequestManagerFragments.put(fm,current);
        fm.beginTransaction().add(current,FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER,fm).sendToTarget();
        }
        }
        return current;
        }

  • load ( RequestManager ) -> asDrawable -> as

This function will be called when you load a Image from the internet

// RequestManager.java

/* ======================== load ======================== */

/**
 * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(String)}.
 *
 * @return A new request builder for loading a {@link Drawable} using the given model.
 */
@NonNull
@CheckResult
@Override
// This function will
public RequestBuilder<Drawable> load(@Nullable String string){
        return asDrawable().load(string);
        }

/* ======================== asDrawable ======================== */

/**
 * Attempts to always load the resource using any registered {@link
 * com.bumptech.glide.load.ResourceDecoder}s that can decode any subclass of {@link Drawable}.
 *
 * <p>By default, may return either a {@link android.graphics.drawable.BitmapDrawable} or {@link
 * GifDrawable}, but if additional decoders are registered for other {@link Drawable} subclasses,
 * any of those subclasses may also be returned.
 *
 * @return A new request builder for loading a {@link Drawable}.
 */
@NonNull
@CheckResult
public RequestBuilder<Drawable> asDrawable(){
        return as(Drawable.class);
        }

/* ======================== as ======================== */

/**
 * Attempts to load the resource using any registered {@link
 * com.bumptech.glide.load.ResourceDecoder}s that can decode the given resource class or any
 * subclass of the given resource class.
 *
 * @param resourceClass The resource to decode.
 * @return A new request builder for loading the given resource class.
 */
@NonNull
@CheckResult
public<ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass){
        return new RequestBuilder<>(glide,this,resourceClass,context);
        }
  • load ( RequestBuilder: overloading ) -> loadGeneric
// RequestBuilder.java

/* ======================== load ======================== */

/**
 * Returns a request builder to load the given {@link java.lang.String}.
 *
 * <p>Note - this method caches data using only the given String as the cache key. If the data is
 * a Uri outside of your control, or you otherwise expect the data represented by the given String
 * to change without the String identifier changing, Consider using {@link
 * com.bumptech.glide.request.RequestOptions#signature(com.bumptech.glide.load.Key)} to mixin a
 * signature you create that identifies the data currently at the given String that will
 * invalidate the cache if that data changes. Alternatively, using {@link
 * com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or {@link
 * com.bumptech.glide.request.RequestOptions#skipMemoryCache(boolean)} may be appropriate.
 *
 * @see #load(Object)
 * @param string A file path, or a uri or url handled by {@link
 *     com.bumptech.glide.load.model.UriLoader}.
 */
@NonNull
@Override
@CheckResult
public RequestBuilder<TranscodeType> load(@Nullable String string){
        return loadGeneric(string);
        }


/* ======================== loadGeneric ======================== */

@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model){
        if(isAutoCloneEnabled()){
        return clone().loadGeneric(model);
        }
        this.model=model;
        isModelSet=true;
        return selfOrThrowIfLocked();
        }

  • into ( RequestBuilder: overloading ) -> into ( RequestBuilder: overloading ) -> buildRequest

This is the most important function in Glide.

// RequestBuilder.java

/* ======================== load ======================== */

/**
 * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
 * the view, and frees any resources Glide may have previously loaded into the view so they may be
 * reused.
 *
 * @see RequestManager#clear(Target)
 * @param view The view to cancel previous loads for and load the new resource into.
 * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link
 *     ImageView}.
 */
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view){
        // TODO 1: check current Thread if we are on MainThread
        Util.assertMainThread();

        ...

        return into(
        glideContext.buildImageViewTarget(view,transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
        }

/* ======================== load ======================== */

private<Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
        BaseRequestOptions<?> options,
        Executor callbackExecutor
        ){
        Preconditions.checkNotNull(target);
        if(!isModelSet){
        throw new IllegalArgumentException("You must call #load() before calling #into()");
        }

        // TODO 1: Build a Request object
        Request request=buildRequest(target,targetListener,options,callbackExecutor);

        Request previous=target.getRequest();

        // TODO 2: Check if Request is Failure or New-Create-One
        if(request.isEquivalentTo(previous)&&!isSkipMemoryCacheWithCompletePreviousRequest(options,previous)){
        // If the request is completed, beginning again will ensure the result is re-delivered,
        // triggering RequestListeners and Targets. If the request is failed, beginning again will
        // restart the request, giving it another chance to complete. If the request is already
        // running, we can let it continue running without interruption.
        if(!Preconditions.checkNotNull(previous).isRunning()){
        // Use the previous request rather than the new one to allow for optimizations like skipping
        // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
        // that are done in the individual Request.
        previous.begin();
        }
        return target;
        }

        // TODO 3: Normal Request
        requestManager.clear(target);
        target.setRequest(request);
        requestManager.track(target,request);

        return target;
        }

/* ======================== buildRequest ======================== */

private Request buildRequest(
        Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
        BaseRequestOptions<?> requestOptions,
        Executor callbackExecutor
        ){

        return buildRequestRecursive(
        /*requestLock=*/ new Object(),
        target,
        targetListener,
        /*parentCoordinator=*/ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions,
        callbackExecutor);
        }

  • buildRequestRecursive -> buildThumbnailRequestRecursive -> obtainRequest -> obtain

This function will Create a Branch to Send Thumbnail Request to the Internet.

// RequestBuilder.java

/* ======================== buildRequestRecursive ======================== */

private Request buildRequestRecursive(
        Object requestLock,
        Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
        TransitionOptions<?, ? super TranscodeType>transitionOptions,
        Priority priority,
        int overrideWidth,
        int overrideHeight,
        BaseRequestOptions<?> requestOptions,
        Executor callbackExecutor){

        // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
        ErrorRequestCoordinator errorRequestCoordinator=null;

        if(errorBuilder!=null){
        errorRequestCoordinator=new ErrorRequestCoordinator(requestLock,parentCoordinator);
        parentCoordinator=errorRequestCoordinator;
        }

        // TODO 1: if Request Successful, build a ThumbnailRequest
        Request mainRequest=buildThumbnailRequestRecursive(
        requestLock,
        target,
        targetListener,
        parentCoordinator,
        transitionOptions,
        priority,
        overrideWidth,
        overrideHeight,
        requestOptions,
        callbackExecutor
        );

        // TODO 2: if we don't have failure Request, return the Request ( ThumbnailRequest ) directly
        if(errorRequestCoordinator==null){
        return mainRequest;
        }

        int errorOverrideWidth=errorBuilder.getOverrideWidth();
        int errorOverrideHeight=errorBuilder.getOverrideHeight();

        if(Util.isValidDimensions(overrideWidth,overrideHeight)&&!errorBuilder.isValidOverride()){
        errorOverrideWidth=requestOptions.getOverrideWidth();
        errorOverrideHeight=requestOptions.getOverrideHeight();
        }

        // TODO 3: Handle Error Request
        Request errorRequest=errorBuilder.buildRequestRecursive(
        requestLock,
        target,
        targetListener,
        errorRequestCoordinator,
        errorBuilder.transitionOptions,
        errorBuilder.getPriority(),
        errorOverrideWidth,
        errorOverrideHeight,
        errorBuilder,
        callbackExecutor
        );

        errorRequestCoordinator.setRequests(mainRequest,errorRequest);

        return errorRequestCoordinator;
        }

/* ======================== buildThumbnailRequestRecursive ======================== */

private Request buildThumbnailRequestRecursive(
        Object requestLock,
        Target<TranscodeType> target,
        RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
        TransitionOptions<?, ? super TranscodeType>transitionOptions,
        Priority priority,
        int overrideWidth,
        int overrideHeight,
        BaseRequestOptions<?> requestOptions,
        Executor callbackExecutor
        ){

        ...

        // TODO 1: Normal State Request
        Request fullRequest=obtainRequest(
        requestLock,
        target,
        targetListener,
        requestOptions,
        coordinator,
        transitionOptions,
        priority,
        overrideWidth,
        overrideHeight,
        callbackExecutor
        );

        isThumbnailBuilt=true;

        // TODO 2: Request Thumbnail
        // Recursively generate thumbnail requests.
        Request thumbRequest=thumbnailBuilder.buildRequestRecursive(
        requestLock,
        target,
        targetListener,
        coordinator,
        thumbTransitionOptions,
        thumbPriority,
        thumbOverrideWidth,
        thumbOverrideHeight,
        thumbnailBuilder,
        callbackExecutor
        );

        isThumbnailBuilt=false;

        coordinator.setRequests(fullRequest,thumbRequest);

        return coordinator;

        }else if(thumbSizeMultiplier!=null){

        ...

        return coordinator;

        }else{
        // Base case: no thumbnail.
        return obtainRequest(
        requestLock,
        target,
        targetListener,
        requestOptions,
        parentCoordinator,
        transitionOptions,
        priority,
        overrideWidth,
        overrideHeight,
        callbackExecutor
        );
        }

/* ======================== obtainRequest ======================== */

private Request obtainRequest(
        Object requestLock,
        Target<TranscodeType> target,
        RequestListener<TranscodeType> targetListener,
        BaseRequestOptions<?> requestOptions,
        RequestCoordinator requestCoordinator,
        TransitionOptions<?, ? super TranscodeType>transitionOptions,
        Priority priority,
        int overrideWidth,
        int overrideHeight,
        Executor callbackExecutor
        ){
        return SingleRequest.obtain(
        context,
        glideContext,
        requestLock,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory(),
        callbackExecutor
        );
        }

// SingleRequest.java

/* ======================== obtain ======================== */

// This is the End of the branch that Send Request to the Internet
public static<R> SingleRequest<R> obtain(
        Context context,
        GlideContext glideContext,
        Object requestLock,
        Object model,
        Class<R> transcodeClass,
        BaseRequestOptions<?> requestOptions,
        int overrideWidth,
        int overrideHeight,
        Priority priority,
        Target<R> target,
        RequestListener<R> targetListener,
@Nullable List<RequestListener<R>>requestListeners,
        RequestCoordinator requestCoordinator,
        Engine engine,
        TransitionFactory<? super R>animationFactory,
        Executor callbackExecutor
        ){
        return new SingleRequest<>(
        context,
        glideContext,
        requestLock,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        engine,
        animationFactory,
        callbackExecutor
        );
        }

After SingleRequest, Glide will return to the Main Branch successively.


  • track -> track( TargetTracker ) -> runRequest ( RequestTracker ) -> begin ( SingleRequest ) ->
    onSizeReady ( SingleRequest )

This function will be called when Glide are tracking the Normal Request. Glide will add current
Request into a Set of Target<?>.

// RequestManager.java

/* ======================== track ( RequestManager ) ======================== */

synchronized void track(@NonNull Target<?> target,@NonNull Request request){
        targetTracker.track(target);
        requestTracker.runRequest(request);
        }

// TargetTracker.java

/* ======================== track ( TargetTracker ) ======================== */

private final Set<Target<?>>targets=Collections.newSetFromMap(new WeakHashMap<Target<?>,Boolean>());

public void track(@NonNull Target<?> target){
        targets.add(target);
        }

// RequestTracker.java

/* ======================== runRequest ( RequestTracker ) ======================== */

// There are two Sets of Request are respectively Being performed and Pending 
private final Set<Request> requests=Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
private final Set<Request> pendingRequests=new HashSet<>();

public void runRequest(@NonNull Request request){
        requests.add(request);

        // TODO 1: Check Glide whether in a suspended state
        if(!isPaused){

        // TODO 2: Glide is running
        request.begin();
        }else{

        // TODO 3: Glide is pause
        request.clear();

        if(Log.isLoggable(TAG,Log.VERBOSE)){
        Log.v(TAG,"Paused, delaying request");
        }

        // TODO 4: add current Request into PendingRequest Queue
        pendingRequests.add(request);
        }
        }

// SingleRequest.java

/* ======================== begin ( SingleRequest ) ======================== */

@Override
public void begin(){
synchronized (requestLock){
        assertNotCallingCallbacks();
        stateVerifier.throwIfRecycled();
        startTime=LogTime.getLogTime();

        ...

        // TODO 1: Current Image is RUNNING
        if(status==Status.RUNNING){
        throw new IllegalArgumentException("Cannot restart a running request");
        }

        // TODO 2: Current Image is loading COMPLETE
        if(status==Status.COMPLETE){
        onResourceReady(
        resource,DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
        return;
        }

        ...

        // TODO 3: Set current State to WAITING
        status=Status.WAITING_FOR_SIZE;

        // TODO 4: Check Width and Height if > 0
        if(Util.isValidDimensions(overrideWidth,overrideHeight)){
        onSizeReady(overrideWidth,overrideHeight);
        }else{
        target.getSize(this);
        }

        ...

        }
        }

/* ======================== onSizeReady ( SingleRequest ) ======================== */

/** A callback method that should never be invoked directly. */
@Override
public void onSizeReady(int width,int height){
        stateVerifier.throwIfRecycled();

synchronized (requestLock){
        if(IS_VERBOSE_LOGGABLE){
        logV("Got onSizeReady in "+LogTime.getElapsedMillis(startTime));
        }

        // TODO 1: Check current Status if is WAITING_FOR_SIZE, this status represent the Request is Complete
        if(status!=Status.WAITING_FOR_SIZE){
        return;
        }

        // TODO 2: if current Status is not WAITING_FOR_SIZE, set the status to RUNNING
        status=Status.RUNNING;

        float sizeMultiplier=requestOptions.getSizeMultiplier();

        this.width=maybeApplySizeMultiplier(width,sizeMultiplier);
        this.height=maybeApplySizeMultiplier(height,sizeMultiplier);

        if(IS_VERBOSE_LOGGABLE){
        logV("finished setup for calling load in "+LogTime.getElapsedMillis(startTime));
        }

        // TODO 3: Calculate the Width and Height of the Image according to the Scale 
        loadStatus=engine.load(
        glideContext,
        model,
        requestOptions.getSignature(),
        this.width,
        this.height,
        requestOptions.getResourceClass(),
        transcodeClass,
        priority,
        requestOptions.getDiskCacheStrategy(),
        requestOptions.getTransformations(),
        requestOptions.isTransformationRequired(),
        requestOptions.isScaleOnlyOrNoTransform(),
        requestOptions.getOptions(),
        requestOptions.isMemoryCacheable(),
        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
        requestOptions.getUseAnimationPool(),
        requestOptions.getOnlyRetrieveFromCache(),
        this,
        callbackExecutor
        );

        // This is a hack that's only useful for testing right now where loads complete synchronously
        // even though under any executor running on any thread but the main thread, the load would
        // have completed asynchronously.
        if(status!=Status.RUNNING){
        loadStatus=null;
        }

        if(IS_VERBOSE_LOGGABLE){
        logV("finished onSizeReady in "+LogTime.getElapsedMillis(startTime));
        }
        }
        }
  • load ( Engine ) -> loadFromMemory -> waitForExistingOrStartNewJob
// Engine.java

/* ======================== load ======================== */

/**
 * Starts a load for the given arguments.
 *
 * <p>Must be called on the main thread.
 *
 * <p>The flow for any request is as follows:
 *
 * <ul>
 *   <li>Check the current set of actively used resources, return the active resource if present,
 *       and move any newly inactive resources into the memory cache.
 *   <li>Check the memory cache and provide the cached resource if present.
 *   <li>Check the current set of in progress loads and add the cb to the in progress load if one
 *       is present.
 *   <li>Start a new load.
 * </ul>
 *
 * <p>Active resources are those that have been provided to at least one request and have not yet
 * been released. Once all consumers of a resource have released that resource, the resource then
 * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to
 * the active resources. If the resource is evicted from the cache, its resources are recycled and
 * re-used if possible and the resource is discarded. There is no strict requirement that
 * consumers release their resources so active resources are held weakly.
 *
 * @param width The target width in pixels of the desired resource.
 * @param height The target height in pixels of the desired resource.
 * @param cb The callback that will be called when the load completes.
 */
public<R> LoadStatus load(
        GlideContext glideContext,
        Object model,
        Key signature,
        int width,
        int height,
        Class<?> resourceClass,
        Class<R> transcodeClass,
        Priority priority,
        DiskCacheStrategy diskCacheStrategy,
        Map<Class<?>,Transformation<?>>transformations,
        boolean isTransformationRequired,
        boolean isScaleOnlyOrNoTransform,
        Options options,
        boolean isMemoryCacheable,
        boolean useUnlimitedSourceExecutorPool,
        boolean useAnimationPool,
        boolean onlyRetrieveFromCache,
        ResourceCallback cb,
        Executor callbackExecutor
        ){

        long startTime=VERBOSE_IS_LOGGABLE?LogTime.getLogTime():0;

        // TODO 1: generate Cache-Key according to the Request Method
        EngineKey key=keyFactory.buildKey(
        model,
        signature,
        width,
        height,
        transformations,
        resourceClass,
        transcodeClass,
        options
        );

        EngineResource<?> memoryResource;

synchronized (this){

        memoryResource=loadFromMemory(key,isMemoryCacheable,startTime);

        if(memoryResource==null){
        return waitForExistingOrStartNewJob(
        glideContext,
        model,
        signature,
        width,
        height,
        resourceClass,
        transcodeClass,
        priority,
        diskCacheStrategy,
        transformations,
        isTransformationRequired,
        isScaleOnlyOrNoTransform,
        options,
        isMemoryCacheable,
        useUnlimitedSourceExecutorPool,
        useAnimationPool,
        onlyRetrieveFromCache,
        cb,
        callbackExecutor,
        key,
        startTime
        );
        }

        }

        // TODO 2: if we can get the Data form Cache successful, call this function to return the Cache to upper Callback. 

        // Avoid calling back while holding the engine lock, doing so makes it easier for callers to deadlock.
        cb.onResourceReady(memoryResource,DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);

        return null;
        }

/* ======================== loadFromMemory ======================== */

private EngineResource<?> loadFromMemory(
        // TODO 1: Check if we are using Cache
        EngineKey key,boolean isMemoryCacheable,long startTime){
        if(!isMemoryCacheable){
        return null;
        }

        // TODO 2: Read the Active Resource
        EngineResource<?> active=loadFromActiveResources(key);
        if(active!=null){
        if(VERBOSE_IS_LOGGABLE){
        logWithTimeAndKey("Loaded resource from active resources",startTime,key);
        }
        return active;
        }

        // TODO 3: Load Cache from System Memory
        EngineResource<?> cached=loadFromCache(key);
        if(cached!=null){
        if(VERBOSE_IS_LOGGABLE){
        logWithTimeAndKey("Loaded resource from cache",startTime,key);
        }
        return cached;
        }

        return null;
        }

/* ======================== waitForExistingOrStartNewJob ======================== */

private<R> LoadStatus waitForExistingOrStartNewJob(
        GlideContext glideContext,
        Object model,
        Key signature,
        int width,
        int height,
        Class<?> resourceClass,
        Class<R> transcodeClass,
        Priority priority,
        DiskCacheStrategy diskCacheStrategy,
        Map<Class<?>,Transformation<?>>transformations,
        boolean isTransformationRequired,
        boolean isScaleOnlyOrNoTransform,
        Options options,
        boolean isMemoryCacheable,
        boolean useUnlimitedSourceExecutorPool,
        boolean useAnimationPool,
        boolean onlyRetrieveFromCache,
        ResourceCallback cb,
        Executor callbackExecutor,
        EngineKey key,
        long startTime
        ){

        EngineJob<?> current=jobs.get(key,onlyRetrieveFromCache);
        if(current!=null){

        current.addCallback(cb,callbackExecutor);

        if(VERBOSE_IS_LOGGABLE){
        logWithTimeAndKey("Added to existing load",startTime,key);
        }

        return new LoadStatus(cb,current);
        }

        EngineJob<R> engineJob=engineJobFactory.build(
        key,
        isMemoryCacheable,
        useUnlimitedSourceExecutorPool,
        useAnimationPool,
        onlyRetrieveFromCache
        );

        DecodeJob<R> decodeJob=decodeJobFactory.build(
        glideContext,
        model,
        key,
        signature,
        width,
        height,
        resourceClass,
        transcodeClass,
        priority,
        diskCacheStrategy,
        transformations,
        isTransformationRequired,
        isScaleOnlyOrNoTransform,
        onlyRetrieveFromCache,
        options,
        engineJob
        );

        jobs.put(key,engineJob);

        engineJob.addCallback(cb,callbackExecutor);
        engineJob.start(decodeJob);

        if(VERBOSE_IS_LOGGABLE){
        logWithTimeAndKey("Started new load",startTime,key);
        }

        return new LoadStatus(cb,engineJob);
        }

  • start
// EngineJob.java

public synchronized void start(DecodeJob<R> decodeJob){
        this.decodeJob=decodeJob;

        GlideExecutor executor=decodeJob.willDecodeFromCache()?diskCacheExecutor:getActiveSourceExecutor();

        executor.execute(decodeJob);
        }

Awaiting…


Core Object

  • RequestManagerFragment

This class will auto-Bind the Lifecycle of Glide with current Activity / Fragment.

// RequestManagerFragment

public class RequestManagerFragment extends Fragment {
...

    @Override
    public void onDetach() {
        super.onDetach();
        unregisterFragmentWithRoot();
    }

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

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

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

...

}

  • Engine

Awaiting…


Glide Cache

  • L1: ActiveCache

  • L2: Lru ( Least Recently Used ) Cache/MemoryCache

  • L3: DiskLruCache

Core Function

  • notifyEncodeAndRelease -> notifyComplete
// DecodeJob.class

/* ======================== notifyEncodeAndRelease ======================== */

private void notifyEncodeAndRelease(Resource<R> resource,DataSource dataSource,boolean isLoadedFromAlternateCacheKey){
        GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
        try{

        // TODO 1: inform the upper Callback
        ...

        notifyComplete(result,dataSource,isLoadedFromAlternateCacheKey);

        stage=Stage.ENCODE;
        try{
        if(deferredEncodeManager.hasResourceToEncode()){

        // TODO 2: Use DiskLruCache
        deferredEncodeManager.encode(diskCacheProvider,options);
        }
        }finally{
        if(lockedResource!=null){
        lockedResource.unlock();
        }
        }

        // Call onEncodeComplete outside the finally block so that it's not called if the encode
        // process
        // throws.
        onEncodeComplete();
        }finally{
        GlideTrace.endSection();
        }
        }

/* ======================== notifyComplete ======================== */

private void notifyComplete(Resource<R> resource,DataSource dataSource,boolean isLoadedFromAlternateCacheKey){
        setNotifiedOrThrow();
        callback.onResourceReady(resource,dataSource,isLoadedFromAlternateCacheKey);
        }

Awaiting…


0%