引言
本篇将介绍我在
阅读开源框架源码
时的一些笔记分享。
对于计算机编程开发的基础笔记可参阅:
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()
}
- running
Sync
Calls
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>()
- running
Async
Calls
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
TableStep 4: double check the available Route when we get the new Route after DNS request, else crete a
new RealConnectionStep 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 Objectsend the Request
Retrofit
Reference
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
toSend 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…