Some people say that now the client interview is more and more volume, the content that developers need to master is more and more, from the basic Java foundation, Android foundation, Android system principle, Android third party library, hybrid development, data structure, algorithm, no question, to want to get a good job opportunity, it is indeed such. Here’s a summary of the Android interview questions.
1. Java Fundamentals 2. Android Fundamentals 3. Advanced Knowledge 4. Android Performance Optimization
1. HTTP and caching theory
1.1 HTTP Caching Policy
The HTTP caching mechanism also depends on the request and response header parameter class implementation, the final response result from the cache or from the server has a complete set of mechanisms, the HTTP caching mechanism flow is shown as follows.
HTTP caching can be divided into two types: forced caching and contrast caching
1.2 Forced Caching
For the server to participate in determining whether to continue using the Cache, when the client first requests data, the server returns a Cache expiration time (Expires and Cache-Control). If the Cache does not expire, it can continue using the Cache. Otherwise, it does not apply and there is no need to ask the server.
Forcing the cache to use two identifiers:
- Expires: The value of Expires is the expiration time returned by the server, meaning that the next request time is less than the expiration time returned by the server, and the cached data is used directly. The expiration time is generated by the server, and there may be an error between the client and server time.
- Cache-Control: Expires has a time-checking problem, so HTTP1.1 uses Cache-Control instead.
The values of Cache-Control are as follows:
- Private: : Clients can be cached.
- Public: : Both client and proxy servers are cacheable.
- Max-age = XXX: The cached content will expire after XXX seconds
- No-cache: : A comparison cache is required to validate the cached data.
- No-store: Everything is not cached; Forced caching, contrast caching is not triggered.
1.3 Comparison Cache
The server is required to participate in determining whether to continue using the cache. When the client requests data for the first time, the server will return the cache identifier (Last-Modified/If-Modified-Since and ETag /If-None-Match) with the data, and the client will back up both of them to the cache. When requesting data again, the client sends the cache ID of the last backup to the server, and the server makes a judgment based on the cache ID. If 304 is returned, it notifies the client that it can continue to use the cache.
Compare the two identifiers of the cache:
- Last-Modified: Indicates the time the resource was Last Modified. When the client sends the first request, the server returns the time when the resource was last modified. Examples of return format are as follows:
Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT
. - The if-modified-since: Server receives the modification time from the client resources, and their current resources, comparing the modification time if their resources from the modification time is greater than the client resources, modified time, then resources change, it returns 200 say need to request resources, otherwise returns 304 indicates resource has not been modified, can continue to use the cache. Unlike if-unmodified-since, if-modified-since can only be used with GET or HEAD. If-None-Match is ignored when combined with, unless the server does not support If-None-Match.
2, OKHttp
OKHttp and Retrofit are the main Android web request frameworks, but the top layer of Retrofit also uses OKHttp. Before we dive into OkHttp, take a look at some common HTTP status codes:
- 100~199: Indicates that the request has been received and continues processing.
- 200~299: The request was successful, indicating that the request was successfully received and understood.
- 300~399: Redirection, further action must be taken to complete the request.
- 400~499: Client error, request syntax error or request cannot be implemented.
- 500~599: Server-side error, server failed to implement valid request.
2.1 OKHttp request flow
Below is a rough flow chart for making requests inside OKHttp, as shown in the figure below.
Here is the code to make a GET request using OKHttp.
//1. Create new OkHttpClient, okHttpClient = new OkHttpClient(); // Create a new Request object: Request Request = new Request.Builder().URL (url).build(); //2.Response = OkHttpResponse = Client.newCall (request).execute();
As you can see, to make a Request using OkHttp, all you need to do is create an OkHttpClient object and a Request object, and then call the execute() method and the enqueue() method. The execute() method is synchronous and the enqueue() method is asynchronous.
2.2 OKHttpClient
Before using OkHttpClient, you need to create an OkHttpClient client, which is constructed as follows.
OkHttpClient client = new OkHttpClient();
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
....
}
As you can see, OkHttpClient uses the Builder pattern, and the configurable parameters in the Builder are as follows.
public static final class Builder { Dispatcher dispatcher; // Distributor @nullable Proxy Proxy; List<Protocol> protocols; List<ConnectionSpec> connectionSpecs; Final List<Interceptor> Interceptors = new ArrayList<>(); // Final List<Interceptor> Interceptors = new ArrayList<>(); EventListener.Factory eventListenerFactory; ProxySelector proxySelector; CookieJar cookieJar; @Nullable Cache cache; @Nullable InternalCache internalCache; // internal cache SocketFactory SocketFactory; @Nullable SSLSocketFactory sslSocketFactory; // Secure Socket Layer Socket Factory for HTTP@Nullable CertificatechainCleaner CertificatechainCleaner; // Verify that the response certificate applies to the hostname of the HTTPS request connection. HostnameVerifier hostnameVerifier; // Verify that the response certificate applies to the hostname of the HTTPS request connection. CertificatePinner certificatePinner; // Certificate locking, which uses CertificatePinner to constrain which Certification Authorities are trusted. Authenticator proxyAuthenticator; // authenticate the Authenticator; // authenticate connectionPool connectionPool; // connection pool DNS DNS; boolean followSslRedirects; // POST (" POST "); // Local redirect Boolean retryonConnectionFailure; // retry connection failed int callTimeout; int connectTimeout; int readTimeout; int writeTimeout; int pingInterval; Public Builder() {dispatcher = new dispatcher (); protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; . } // This. Dispatcher = OkHttpClient. Dispatcher;} // This. this.proxy = okHttpClient.proxy; this.protocols = okHttpClient.protocols; this.connectionSpecs = okHttpClient.connectionSpecs; this.interceptors.addAll(okHttpClient.interceptors); this.networkInterceptors.addAll(okHttpClient.networkInterceptors); . }
2.3 Synchronization request
The synchronous request uses the execute() method as follows.
Response response = client.newCall(request).execute();
Here is some of the source code involved.
/** * Prepares the {@code request} to be executed at some point in the future. */ @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } static realCall newRealCall(okHttpClient, Request originalRequest, Request originalRequest) {} static realCall newRealCall(okHttpClient, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } @Override public Response execute() throws IOException {synchronized (this) {// Each Call can only be executed If (Executed) Throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); timeout.enter(); eventListener.callStart(this); Executed (this); execute {// Executed (this); / / through a series of interceptor request processing and Response processing to get the final returns the Response result = getResponseWithInterceptorChain (); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { e = timeoutExit(e); eventListener.callFailed(this, e); throw e; } finally {// Notify the dispatcher that it has finished executing client.dispatcher().finished(this); } } Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); // Interceptors that you set when you configure the OkHttpClient; interceptors.addAll(client.interceptors()); / / is responsible for the failure retry and redirect interceptors. Add (retryAndFollowUpInterceptor); Add (new BridgeInterceptor(client.cookieJar())); Add (new BridgeInterceptor(client.cookieJar())); // update the cache interceptors.add(new CacheInterceptor(Client.InternalCache ())); // Interceptors.add (new ConnectInterceptor(client)); if (! ForWebSocket) {/ / configuration Settings when OkHttpClient networkInterceptors interceptors. AddAll (client.net workInterceptors ()); } // Interceptors.add (new CallServerInterceptor(forWebSocket));} // Interceptors.add (new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); Return chain.proceed(originalRequest); StreamAllocation (streamAllocation) is a management class that maintains the relationship between server connections, concurrent streams, and requests. It also initializes a Socket connection object to retrieve the input/output stream objects. public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { ... // Call the next interceptor in the chain. // Instance the next interceptor in the chain RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); // return the current Interceptor, Interceptor, Interceptor = interceptor.get (index); Intercept (); // intercept(interceptor); // intercept(interceptor); // intercept(interceptor); . return response; }
2.4 Asynchronous Request
The asynchronous request uses enqueue(), and depending on the asynchronous writing, we can follow the Builder with a number of parameters, as shown below.
Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { ... } void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); } promoteAndExecute(); } private final Dque < AsyncAll > ReadyAsyncCalls = new ArrayDque <>(); // private final Dque < AsyncAll > runningAsyncCalls = new ArrayDque <>(); Private Final Dque <RealCall> RunningSyncCalls = new ArrayDque <>(); // Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs // them on the executor service. Must not be called with synchronization because executing calls // can call into user code. private boolean promoteAndExecute() { assert (! Thread.holdsLock(this)); List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) { for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); // If the number of RunningAsyncCalls is not satisfied and the number of Host calls is less than the maximum number, then the call is added to the RunningAsyncCalls. If not, add the Call to ReadyAsyncCalls. if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); asyncCall.executeOn(executorService()); } return isRunning; }
2.5 CacheInterceptor Network request caching
The caching principle of OkHHTTP is that the cache interceptor determines whether a cache is available based on the information about the request and the cached response, and returns the cache to the user if it is available, otherwise it continues to use the chain of responsibility pattern to retrieve the response from the server. When the response is retrieved, the response is cached on disk. The code involved is:
Throws IOException {// Override public Response intercept(Chain Chain) throws IOException cacheCandidate = cache ! = null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); // request determines whether the cache policy is to use the network, Cache or both using cacheStrategy = new cacheStrategy.factory (now, chain.request(), cachecandiDate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache ! = null) { cache.trackResponse(strategy); } if (cacheCandidate ! = null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, fail. if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // If we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; Try {// call the next interceptor, decide to get the response from the network networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate ! = null) { closeQuietly(cacheCandidate.body()); }} // If we have a cache response too, then we're doing a conditional get. // If there is a cache response, then we're doing a conditional get. Then compare it with the networkResponse that the network gets, and decide whether to update the cached CacherResponse if (CacherResponse! = null) { if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache ! = null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, NetworkRequest)) {// Offer this request to the cache. // cache uncached response cacherRequest cacherRequest cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }
2.6 ConnectInterceptor connection pooling
A ConnectInterceptor connection pool interceptor consists of a network interceptor that operates from a network connection perspective and an interceptor that operates from a connection pool perspective.
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = ! request.method().equals("GET"); // HttpCodec is an abstraction of HTTP protocol operations. There are two implementations: http1Codec and http2Codec, which, as the name implies, correspond to the HTTP/1.1 and HTTP/2 versions of the implementation, respectively. In this method the internal implementation of connection pool in the reuse treatment HttpCodec HttpCodec = streamAllocation. NewStream (client, chain, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); } // Returns a connection to host a new stream. This // prefers the existing connection if it exists, // then the pool, // When we call the newStream() method for streamAllocation, finally building a new Connection. Private realConnection findConnection(int connectTimeout) private realConnection findConnection(int connectTimeout, int connectTimeout) int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { ... // 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 Streams. // Attempts to use an assigned connection. ReleasedConnection = this.connection; ReleasedConnection = this.connection; ReleasedConnection = this.connection; ReleaseIFNewStreams (); // ReleaseIFNewStreams (); // ReleaseIFNewStreams (); if (this.connection ! = null) { // We had an already-allocated connection and it's good. result = this.connection; releasedConnection = null; } if (! reportedAcquired) { // If the connection was never reported acquired, don't report it as released! // If the connection has never been marked as acquired, do not mark it as published. ReportedAcquired changes ReleasedConnection = null through the acquire() method; } if (result == null) {// Attempt to get a connection from the pool Internal.instance.get(connectionPool, address, this, null); if (connection ! = null) { foundPooledConnection = true; result = connection; } else { selectedRoute = route; }}} // CloseQuietly (ToClose); if (releasedConnection ! = null) { eventListener.connectionReleased(call, releasedConnection); } if (foundPooledConnection) { eventListener.connectionAcquired(call, result); } if (result ! = LEOED) {// If we found an already-allocated or pooled connection, we're done. Return result; return result; } // 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; routeSelection = routeSelector.next(); } synchronized (connectionPool) { if (canceled) 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. // according to a series of List<Route> routes = RoutesElection.getAll (); for (int i = 0, size = routes.size(); i < size; i++) { Route route = routes.get(i); // Get a connection from the connectionPool intern.instance. get(connectionPool, address, this, route); if (connection ! = null) { foundPooledConnection = true; result = connection; this.route = route; break; } } } if (! foundPooledConnection) { if (selectedRoute == null) { selectedRoute = routeSelection.next(); } // 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. // Create a new connection and allocate it if it is not available in the connection pool. So we can do terminal route = selectedRoute before the handshake; refusedStreamCount = 0; result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } // If we found a pooled connection on the 2nd time around, If (foundPooledConnection) {// If we find a pool connection the second time around, Then we will return to eventListener. Its connectionAcquired (call, result); return result; } // Do TCP + TLS handshakes. This is a blocking operation. // Do TCP + TLS handshakes. readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); routeDatabase().connected(result.route()); Socket socket = null; synchronized (connectionPool) { reportedAcquired = true; // Pool the connection. // Pool the connection. // Pool the connection. // If another multiplexed connection to the same address was created concurrently, Then // create a multiplexed connection to acquire that one. // create a multiplexed connection to acquire that one. Release and obtain the connection if the connection (result) isMultiplexed ()) {socket = Internal. Instance. Deduplicate (connectionPool, address, this); result = connection; } } closeQuietly(socket); eventListener.connectionAcquired(call, result); return result; }
From the above source code analysis, we can know:
- Determine whether the current connection is available: whether the stream has been closed and has been restricted from creating new streams;
- If the current connection is not available, a connection is obtained from the connection pool.
- No connections are found available in the connection pool. Create a new connection, shake hands, and place it in the connection pool.
The Internal get() method is used when retrieving a connection from the connection pool. Internal has a static instance that is initialized in OkHttpClient’s static code. We call the pool’s get() method in Internal get() to get a connection. Also, we see that one of the benefits of connection multiplexing is the elimination of a TCP/TLS handshake. Since it takes some time to establish the connection itself, the reuse of the connection can improve the efficiency of our network access.
Let’s take a closer look at how ConnectionPool implements connection management.
OkHttp’s cache management consists of two steps: when we create a new connection, we put it in the cache; On the other side, we’re going to clean up the cache. In ConnectionPool, when we cache a connection to the pool, we simply add it to the two-ended queue by calling the add() method of the two-ended queue, while cleaning up the connection cache is left to the thread pool to perform periodically.
private final Deque<RealConnection> connections = new ArrayDeque<>(); void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (! cleanupRunning) { cleanupRunning = true; // Executor.execute (cleanupRunnable); } // Insert the new connection into the two-ended queue Connection.add (connection); } private final Runnable cleanuPrunnable = new Runnable() {@Override public void run() {while (true) {// Internal call Long WaitNanos = cleanup(System.nanoTime()); if (waitNanos == -1) return; if (waitNanos > 0) { long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); synchronized (ConnectionPool.this) { try { ConnectionPool.this.wait(waitMillis, (int) waitNanos); } catch (InterruptedException ignored) { } } } } }; long cleanup(long now) { int inUseConnectionCount = 0; int idleConnectionCount = 0; RealConnection longestIdleConnection = null; long longestIdleDurationNs = Long.MIN_VALUE; // Find either a connection to evict, Or the time that the next eviction is due. Synchronized (this) {for (Iterator<RealConnection> I = connections.iterator(); i.hasNext(); ) { RealConnection connection = i.next(); / / If the connection is in use, keep searching. / / to iterate through all the connection of the If (pruneAndGetAllocationCount (connection, now) > 0) { inUseConnectionCount++; continue; } idleConnectionCount++; // If the connection is ready to be evicted, we're done. // If a connection is found that can be cleaned, we're done. Long idleDurationNs = now - connection.idleatnanos; if (idleDurationNs > longestIdleDurationNs) { longestIdleDurationNs = idleDurationNs; longestIdleConnection = connection; }} // MaxIDleConnections represents the maximum number of idle connections allowed and KeepAliveurationNS represents the maximum time a connection is allowed to live. // The default maximum number of idle connections is 5, and the maximum keepAlive time is 5 minutes. if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) { // We've found a connection to evict. Remove it from the list, // The length of the connection exceeds the maximum active time or the number of idle connections exceeds the maximum allowable range. Remove Connections. Remove (LongestidleConnection); } else if (IDLEConnectionCount > 0) {// A connection will be ready to evict soon. // The number of idle connections is greater than 0. Not yet) Return KeepAliveDurationns - LongEtidleDurationns; } else if (inUseConnectionCount > 0) { // All connections are in use. It'll be at least the keep alive duration 'til we Run again. // All connections in use, clean up Return KeepAlivedUrationns after 5 minutes; } else {// No connections, idle or in use. // Cleanuprunning = false; return -1; }}
As you can see from the above source code analysis, connections in the cache are first traversed to find a connection that has been idle for the longest time, and then the connection should be cleaned based on parameters such as its idle time and the maximum number of connections allowed. Also note that the return value of the above method is a time, if the longest idle connection still needs some time to be cleaned, the time difference is returned, and the connection pool is cleaned again after this time.
3, the Retrofit
Retrofit is a encapsulation of a RESTful HTTP network request framework, which is essentially done by OkHttp, and Retrofit is only responsible for encapsulating the network request interface. The client uses Retrofit, which essentially uses the Retrofit interface layer to encapsulate the request parameters, headers, URLs, and so on, and then OkHttp does the rest of the request, and then when the server returns the data, OkHttp gives Retrofit the original result. Retrofit then parses the results based on the user’s needs.
3.1 Basic use
First, define an HTTP API for describing requests, such as a GET request below.
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Then, you create a Retrofit and generate an implementation of the API where the return type is the return value type of the request and the method parameters are the parameters of the request.
// 1. Build Retrofit Retrofit = new retrofit.builder ().baseUrl("https://api.github.com/").build(); GithubService = retrofit.create(githubService.class);
Finally, the API method is called to generate the Call completion request.
Call<List<Repo>> repos = service.listRepos("octocat"); repos.execute() or repos.enqueue()
The above is an example of a simple GET request, where the POST request simply changes the API definition to POST. The basic use process of Retrofit is simple, but simplicity does not mean simplicity. To achieve this simple use process, Retrofit internally used good architectural design and a large number of design patterns. A close look at the source code of the latest version of Retrofit reveals that there were a large number of design patterns involved. For example, the Retrofit build process uses the builder pattern, the factory method pattern, the network request interface instance uses the facade pattern, the proxy pattern, the singleton pattern, the policy pattern, the decorator pattern (the builder pattern), and the request process adapter pattern (the proxy pattern, the decorator pattern) that is generated and executed.
3.2 Source Code Analysis
3.2.1 The Retrofit build process
1, Retrofit core object resolution
First, one of the key global variables in Retrofit is that, prior to V2.5, it used LinkedHashMap(), which is a network request configuration object that is parsed by method annotations in the network request interface.
public final class Retrofit { private final Map<Method, ServiceMethod<? >> serviceMethodCache = new ConcurrentHashMap<>(); . }
Retrofit uses the Builder pattern to create an instance of Retrofit from the inner class Builder class, as shown below.
Public static final class Builder {// Platform-> Android); Private @Nullable OkHttp3.call. Factory CallFactory; private @Nullable OkHttp3.call. Factory CallFactory; private @Nullable OkHttp3.callFactory; Private @Nullable Httpurl baseUrl; private @Nullable Httpurl baseUrl; Private Final List<Converter.Factory> ConverterFactories = new ArrayList<>(); // A collection of network request adapter factories, The default is ExecutorCallAdapterFactory private final List < CallAdapter. Factory > callAdapterFactories = new ArrayList < > (); Callback method executor, which on Android is the mainThreadExecutor that encapsulates the handler, default to: Private @Nullable Executor CallbackExecutor; private @Nullable Executor CallbackExecutor private boolean validateEagerly;
2, Builder internal construction
public static final class Builder { ... Builder(Platform platform) { this.platform = platform; } public Builder() { this(Platform.get()); }... } class Platform { private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; } private static Platform findPlatform() {try {// Use the JVM to load an Android Class. ForName (" Android.os.build "); if (Build.VERSION.SDK_INT ! = 0) { return new Android(); }} catch (ClassNotFoundException) {} try {// Support Class.ForName ("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } return new Platform(); } static class Android extends Platform { ... @Override public Executor defaultCallbackExecutor() {return new MainThreadExecutor(); } // Create the default network request adapter factory, if it is on Android7.0 or Java8, The completableFuture that causes // to be used and distributed guarantees the synchronization of callbacks // In Retrofit there are four CallAdapterFactory(policy mode) : / / ExecutorCallAdapterFactory (default), GuavaCallAdapterFactory, / / va8CallAdapterFactory, RxJavaCallAdapterFactory @ Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories( @Nullable Executor callbackExecutor) { if (callbackExecutor == null) throw new AssertionError(); ExecutorCallAdapterFactory executorFactory = new ExecutorCallAdapterFactory(callbackExecutor); return Build.VERSION.SDK_INT >= 24 ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory) : singletonList(executorFactory); }... @Override List<? extends Converter.Factory> defaultConverterFactories() { return Build.VERSION.SDK_INT >= 24 ? singletonList(OptionalConverterFactory.INSTANCE) : Collections.<Converter.Factory>emptyList(); }... Static class MainThreadExecutor implements Executor {static class MainThreadExecutor implements Executor {private final Handler Handler = new Handler(Looper.getMainLooper()); Override public void execute(Runnable R) {// Handler.post (R); }}}
3. Add BaseURL
The basic function of BaseURL is the process of converting String URLs to OkHttpURl. The code involved is as follows.
public Builder baseUrl(String baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); return baseUrl(HttpUrl.get(baseUrl)); } public Builder baseUrl(HttpUrl baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); List<String> pathSegments = baseUrl.pathSegments(); if (!" ".equals(pathSegments.get(pathSegments.size() - 1))) { throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } this.baseUrl = baseUrl; return this; }
4. Build process
The main task of build() is to perform the creation of a Retrofit object, which involves the following code.
public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; If (CallFactory == null) {// By default OKHttpCallFactory = new OKHttpClient (); } Executor callbackExecutor = this.callbackExecutor; If (callbackExecutor = = null) {/ / Android default callbackExecutor callbackExecutor = platform. DefaultCallbackExecutor (); } // Make a defensive copy of the adapters and add the defaultCall adapter. List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); / / add default adapter factory callAdapterFactories. At the end of the collection addAll (platform. DefaultCallAdapterFactorisca llbackExecutor)); // 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 thatconsumeall types. converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); converterFactories.addAll(platform.defaultConverterFactories(); return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly); }
3.2.2 Create an instance of network request interface
Retrofit.create() uses the facade pattern and the proxy pattern to create an interface instance of a network request. Create () is done as follows.
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); If (ValidateStyle) {// Determes if the ServiceMethod object eagerlyValidateStyle (Service) needs to be cached in advance; Return (T) proxy.newProxyInstance (Service.getClassLoader (), new Class<? >[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; @Override public 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); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } return loadServiceMethod(method).invoke(args ! = null ? args : emptyArgs); }}); } private void eagerlyValidateMethods(Class<? > service) { Platform platform = Platform.get(); for (Method method : service.getDeclaredMethods()) { if (! platform.isDefaultMethod(method)) { loadServiceMethod(method); }}}
Then, let’s look at the loadServiceMethod() method.
ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method); if (result ! = null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); If (result = = null) {/ / parsing annotation configuration got ServiceMethod result = ServiceMethod. ParseAnnotations (this, method); // ServiceMethodCache is added to ConcurrentHashMap cache. Put (method, result); } } return result; } abstract class ServiceMethod<T> { static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method Method) {// Annotate the configuration (factory mode, internally using builder mode) requestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); Type returnType = method.getGenericReturnType(); if (Utils.hasUnresolvableType(returnType)) { throw methodError(method, "Method return type must not include a type variable or wildcard: %s", returnType); } if (returnType == void.class) { throw methodError(method, "Service methods cannot return void."); } / / in the end is the method HttpServiceMethod build request return HttpServiceMethod. ParseAnnotations (retrofit, method, requestFactory); } abstract T invoke(Object[] args); }
3.3 Retrofit
The latest version of Retrofit, 2.9.0, hasn’t been updated for over six months. Retrofit is just a RESTful, encapsulated library of HTTP network requests. However, it encapsulates OkHttp internally with a number of design patterns that make it very concise and understandable to users. It mainly uses the dynamic proxy inside, dynamically parses the network request interface annotation into HTTP request, and finally executes the request process. The complete flow of Retrofit is shown below.
4, Glide
4.1 Basic use
As an Android image loading framework, Glide has the advantages of full function, high performance, easy to use and so on. The following line of code is used to complete the loading and display of the image.
Glide.with(context).load(url).into(iv);
In addition, we can also specify a placeholder map during the loading of the image.
Glide.with(this)
.load(url)
.placeholder(R.drawable.noimage)
.into(iv);
4.2 Source code analysis
Below is an architectural diagram of the complete Glide framework.
2 with (context)
We use Glide, starting with the glide.with () method. The source code is as follows.
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
public static RequestManager with(Activity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
public static RequestManager with(android.app.Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
public static RequestManager with(Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
As you can see, with () method has a lot of, but basically the same content, is through RequestManagerRetriever. The get () to obtain retriever RequestManagerRetriever object, You then retrieve a RequestManager object through retriever.get(context) and return it. The key difference between these with() methods is that they pass in different arguments, which can be Context, Activity, Fragment, and so on.
The reason Glide is bound to the life cycle of the context passed in by the with(context) method when loading an image is that if the Activity is passed in, Glide will stop loading the image when the Activity is destroyed. The advantage of doing this is that it avoids consuming unnecessary resources and also avoids the null pointer problem of loading images after the Activity has been destroyed.
Next, let’s look at the source of the RequestManagerRetriever class.
Public class requestManagerRetriever implements Handler.Callback {// private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever(); Public static requestManagerRetriever get() {return INSTANCE; } // Based on the parameter passed in, Public RequestManager get(Context Context) {if (Context == null) {throw new IllegalArgumentException("You cannot start a load on a null Context"); } else if (Util.isOnMainThread() && ! (context instanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper) { return get(((ContextWrapper) context).getBaseContext()); } } return getApplicationManager(context); } // Omit extraneous code...... }
The above code is a Hungry Singleton pattern with the core of getting different RequestManagers based on the parameters passed in. Next, let’s look at the getApplicationManager method.
Private RequestManager getApplicationManager(Context Context) {// return a singleton if (ApplicationManager == null) {synchronized (this) { if (applicationManager == null) { applicationManager = new RequestManager(context.getApplicationContext(), new ApplicationLifecycle(), new EmptyRequestManagerTreeNode()); } } } return applicationManager; }
GetApplicationManager (Context Context) creates and returns the ApplicationManager using the double-checked singleton pattern. If the context passed in is an Activity, do the following.
Public RequestManager get(Activity Activity) {// If it is not on the main thread or the Android SDK version is lower than Honeycomb, Incoming or Application types of context if (Util. IsOnBackgroundThread () | | Build. VERSION. SDK_INT < Build. VERSION_CODES. HONEYCOMB) { return get(activity.getApplicationContext()); } else {// AssertNotDestroyed (activity); android.app.FragmentManager fm = activity.getFragmentManager(); // fetchRequestManager from fragmentGet(activity, FM) return fragmentGet(activity, FM); }}
If the context passed in is an Activity, do the following.
Public RequestManager get(Activity Activity) {// If it is not on the main thread or the Android SDK version is lower than Honeycomb, Incoming or Application types of context if (Util. IsOnBackgroundThread () | | Build. VERSION. SDK_INT < Build. VERSION_CODES. HONEYCOMB) { return get(activity.getApplicationContext()); } else {// AssertNotDestroyed (activity); android.app.FragmentManager fm = activity.getFragmentManager(); // fetchRequestManager from fragmentGet(activity, FM) return fragmentGet(activity, FM); }}
The following is a brief summary of the with() method.
- Through RequestManagerRetriever. The get () to obtain RequestManagerRetriever singleton.
- Retrieve the RequestManager from the retriever.get(context) method, and do different things in the get(context) method by determining the type of context.
- When the context is Application, getApplicationManager(Context Context) creates and returns a RequestManager object.
- When the context is an Activity, create and add a fragment with no interface to the current Activity using FragmentGet (Activity, FM) to load images bound to the Activity’s lifecycle. A RequestManager object is then created and returned.
4.2.2 the load (url)
The load(URL) is mainly used to load images from the network, as shown below.
Glide.with(context)
.load(url)
.placeholder(R.drawable.place_image)
.error(R.drawable.error_image)
.into(imageView);
The source code for the load(URL) method is as follows.
public DrawableTypeRequest<String> load(String string) { return (DrawableTypeRequest<String>) fromString().load(string); }
The load() method involves two methods, fromString() and load(String), which we’ll look at next.
fromString()
public DrawableTypeRequest<String> fromString() { return loadGeneric(String.class); } private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) { ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context); ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(modelClass, context); if (modelClass ! = null && streamModelLoader == null && fileDescriptorModelLoader == null) { throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for" + " which there is a registered ModelLoader, if you are using a custom model, you must first call" + " Glide#register with a ModelLoaderFactory for your custom model class"); } // This sentence is the core, Apply (new DrawableType <T>(ModelClass, StreamModelLoader, DrawableType, StreamModelLoader, DrawableType <T>) fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier)); }
The loadGeneric() method essentially creates and returns a DrawablePereRequest, a Drawable type request.
The code for the load(string) method is as follows.
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}
This method first calls the load() method of the parent class GenericRequestBuilder of DrawableRequestBuilder, and then returns itself. Next, take a look at the load() method in the DrawableRequestBuilder parent class, as shown below.
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) {
this.model = model;
isModelSet = true;
return this;
}
The parent class of DrawableRequestBuilder is GenericRequestBuilder. As the name shows, the former is the builder of Drawable requests, and the latter is the GenericRequestBuilder. They are the child parent relationship.
Glide.with(context). Load (url) will return a DrawableTypeRequest object whose parent class is DrawableRequestBuilder. The parent class of DrawableRequestBuilder is GenericRequestBuilder, and the placeHolder(), error(), and other image request configuration methods are defined in GenericRequestBuilder.
Holdings into (imageView)
The first two versions of Glide create a Request, which can be understood as a configuration Request to load an image. Note that the Request is simply created, but not executed. Only when the into() method is called will the request actually be executed. The source code for Into (ImageView) is as follows.
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
Notice that it calls the parent GenericRequestBuilder’s into() method, so let’s move on to the GenericRequestBuilder’s into() method.
Public Target< TransCodeType > into(ImageView View) {// Make sure that the main thread () is used in Util. AssertMainThread (); If (view == null) {throw new IllegalArgumentException("You must pass in a non null view "); } // Configure ScaleType if (! isTransformationSet && view.getScaleType() ! = null) { switch (view.getScaleType()) { case CENTER_CROP: applyCenterCrop(); break; case FIT_CENTER: case FIT_START: case FIT_END: applyFitCenter(); break; / / $$default CASES - OMITTED: / / Do nothing.}} / / core return into (glide. BuildImageViewTarget (view, transcodeClass)); }
As you can see, the above method is the core code for into(), which is defined in the GenericRequestBuilder, the GenericRequestBuilder. The core of the method is the last line: Into (glide. BuildImageViewTarget (view, transcodeClass)), First by glide. BuildImageViewTarget (view, transcodeClass) create a Target object of type, then the Target of afferent GenericRequestBuilder into () method.
We won’t go into the BuildieViewTarget (view, TranscoDeclass) method again.
6, EventBus
6.1 Basic use
6.1.1 Basic concepts
EventBus is an event publish-subscribe EventBus for Android. It simplifies the complexity of communication between various components in the application program, especially the problem of communication between fragments, and can avoid a lot of inconvenience caused by using broadcast communication.
EventBus consists of three roles: Publisher, Event, and Subscriber.
- Event: Event, which can be of any type. EventBus notifies you globally based on the Event type.
- The Subscriber: Event Subscriber Prior to EventBus 3.0 we had to define the onEvent methods that begin with OnEvent, OnEventMainThread, OnEventBackgroundThread, and OnEventAsync. After 3.0, you can take whatever you want, but you need to add the annotation @subscribe and specify the thread model. The default is POSTING.
- Publisher: An event Publisher that can publish events from any thread. In general, you can use EventBus.getDefault() to get an EventBus Object, and then call the POST (Object) method.
EventBus is a typical event publish-and-subscribe pattern. Events are delivered by the publisher to subscribers via evenentBus. The general framework is as follows.
EventBus provides four threading models:
- Posting: By default, the thread for an event handler is on the same thread as the thread that published the event.
- Main: The thread that represents the event handler is on the MAIN (UI) thread, so time consuming operations cannot take place here.
- Background: The thread that represents the event handler is a BACKGROUND thread, so it cannot perform UI operations. If the thread that publishes the event is the main thread (UI thread), then the event handler will start a background thread, and if the thread that publishes the event is in the background thread, then the event handler will use that thread.
- ASYNC: No matter which thread the event is published on, the event handler will always create a new child thread to run. Again, it cannot perform UI operations.
6.1.2 Basic use
EventBus is used in three steps. First, define an event class.
public class SayHelloEvent { private String message; public void sayHellow(String message) { this.message = message; }}
Then, prepare a subscriber to the event, and in order to prevent the performance cost of the event, unsubscribe to the event during the onStop lifecycle.
@Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(SayHelloEvent event) { String message = event.getMessage(); . } @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); super.onStop(); }
Finally, call the EventBus.getDefault().post method to send the event where it is needed.
EventBus.getDefault().post(New SayHelloEvent("Hello,EventBus!! ") ));
6.2 Source Code Analysis
6.2.1 EventBus. GetDefault (). The register (this)
First, let’s look at EventBus from the getDefault() development method that retrieves an instance of it.
public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }
As you can see, the double-checked and locked singleton pattern is used in getDefault() to create an EventBus instance, then let’s look at the constructor.
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
this(DEFAULT_BUILDER);
}
EventBus’s other constructor with arguments is called in its default constructor, passing in a DEFAULT_BUILDER object of type EventBusBuilder. Take a look at the constructor for EventBusBuilder.
public class EventBusBuilder {
...
EventBusBuilder() {
}
...
}
Basically nothing is done in there, so continue to look at the EventBus constructor with arguments.
private final Map<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; private final Map<Object, List<Class<? >>> typesBySubscriber; private final Map<Class<? >, Object> stickyEvents; EventBus(EventBusBuilder builder) { ... // 1 subscriptionsByEventType = new HashMap<>(); // 2 typesBySubscriber = new HashMap<>(); // 3 stickyEvents = new ConcurrentHashMap<>(); // 4 mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport ! = null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); . // 5 subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); // Subscribe to the Builder column for the assignment... // 6 executorService = builder.executorService; }
In Note 1, a SubscriptionsByEventType object is created. You can see that it is a SubscriptionsByEventType object of type HashMap, and its key is of type Event. Value is the Subscription linked list. Here, Subscription is a Subscription Object that holds two important fields: a Subscriber of type Object, which is a registered Object (typically an Activity Object in Android); The other is a SubscriberMethod of type SubscriberMethod, which is the subscription method annotated by @Subscribe and holds an important field EventType, which is Class<? >, which represents the type of Event. In Note 2, a new TypeBySubscriber object of type Map is created, whose key is the Subscriber object and value is the linked list of all the Event types in the Subscriber object. In daily use, it is only used to determine whether an object has been registered. In Note 3, I create a new StickyEvents object of type ConcurrentHashMap, which is a field dedicated to sticky event handling, with key being the Class object of the event and value being the current event.
For those of you who don’t know about sticky events, sticky events are relatively ordinary events. Ordinary events are registered first and then sent to receive the event; Sticky events, on the other hand, can be received if you subscribe to the event after sending it. In addition, sticky events are stored in memory, and each time you enter, you will look up the latest sticky events in memory, unless you manually unregister them.
Then, we look at the line in Note 5 that creates a new SubscriberMethodFinder object, which is queried from the subscription method pulled out of EventBus. In Note 6, a default thread pool object is taken out of Builder and created by Executors newCachedThreadPool() method. It is a free-for-all, free-for-all thread pool with an unlimited number of threads.
Next, let’s take a look at EventBus’s regist() method.
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); // 1 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { // 2 subscribe(subscriber, subscriberMethod); }}}
The main purpose of the above code is to get the SubscriberMethods list of subscription methods based on the current registered class, and then use the enhanced for loop to make the Subsciber object subscribe to each SubscriberMethods.
For deeper code, which we won’t analyze, regist() basically accomplishes the following things.
- Create an EventBus object according to the singleton design pattern, and create an EventBus. The Builder object initializes the EventBus, which has three important collections and a SubscriberMethodFinder object.
- The Register method is called to first retrieve the subscriber’s Class object through reflection.
- The SubscriberMethodFinder object gets the collection of all subscribed events in the subscriber. It gets them from the cache first, and returns them directly if there is one in the cache. If not, iterate through the annotated methods inside the subscriber by reflection, putting them into the collection for return.
- Iterate through the collection obtained in step 3 to bind subscribers to events.
- After binding, it determines whether the bound event is sticky, and if it is, the PosttoSubscription method is called directly to send the previously sent sticky event to the subscriber. And it makes sense, when we talked about sticky events, if a subscriber who registered before the sticky event was sent, when the sticky event was sent, would receive that event; Subscribers who sign up after the sticky event is sent can also receive the event.
6.2.2 EventBus. GetDefault (). The post ()
The code for the post() method is as follows.
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List <Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
The function of this method is to get a collection of events for the current thread and add the events to the collection. It then goes through the loop, sending as long as there are events in the event set. The currentPostingThreadState here is a ThreadLocal object of type, PostingThreadState was stored inside, PostingThreadState contains an EventQueue and some other flag bits, and the relevant source code is as follows.
private final ThreadLocal <PostingThreadState> currentPostingThreadState = new ThreadLocal <PostingThreadState> () { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); }}; final static class PostingThreadState { final List <Object> eventQueue = new ArrayList<>(); boolean isPosting; boolean isMainThread; Subscription subscription; Object event; boolean canceled; }
Then, let’s look at the postsingleEvent () method, which looks like this.
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { List<Class<? >> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h); subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (! subscriptionFound) { ... }}
The function of the above code is to get the Class object of the event and find the Class set of all the superclasses and interfaces implemented for the current event. The collection is traversed and sent by calling the method that sends the individual events.
As you can see, the code above first fetches the class type of Event and then evaluates the EventInheritance flag bit, which defaults to true. If it is true, it will determine if it is necessary to fire a parent Event when it is fired, and it will set it to false. Can improve some performance. Next, it calls the lookupAllEventTypes() method, which fetches the list of classes of the Event and its superclasses and interfaces. Of course, repeated fetching can affect performance, so it also makes a cache of EventTypeSache. This eliminates the need to call the getSuperClass () method repeatedly. Finally, call postSingleEventForEventType () method.
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class <? > eventClass) { CopyOnWriteArrayList <Subscription> subscriptions; synchronized(this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions ! = null && ! subscriptions.isEmpty()) { for (Subscription subscription: subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }
Gets the collection of all subscribers that subscribe to it based on the event, traverses the collection, and sends the event to the subscriber. The PostToSubscription () method is finally called.
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknow thread mode: " + subscription.subscriberMethod.threadMode);
}
}
You can see that ThreadMode is used to determine in which thread the subscription message is to be executed.
- Posting: Performs the invokeSubscriber() method with direct reflection calls internally.
- MAIN: If so, the call is reflected directly. Otherwise, MainThreadPoster’s enqueue() method is called, which puts the current method on the queue and sends a message through the handler. Execute the method in the handler’s handleMessage.
- Main_Ordered: Similar to MAIN, but ensure that it is executed in order.
- If it is not, the call is reflected directly. If it is, the method is added to a queue in the BACKGROUND using the backgroundPoster enqueue() method, and then executed by the thread pool. Notice that BackgroundPosters adds the synchronized keyword to the Executor’s execute() method and sets the control flag flag to ensure that only one and only one task will be executed by the thread pool at any one time.
- ASYNC: The logic implementation is similar to BACKGROUND in that tasks are added to a queue in the BACKGROUND and are eventually called by a thread pool in EventBus. The thread pool here is the same as the thread pool in BACKGROUND logic. A thread pool created using Executors newCachedThreadPool() method, which is a free-for-all, free-for-all thread pool with an unlimited number of threads. Unlike BackgroundPoster, which guarantees that only one task can be executed by the thread pool at any one time, AsyncPoster runs asynchronously and can receive multiple tasks simultaneously.
As you can see, eventBus.getDefault ().post() mainly does the following things:
- Gets the collection of events for the current thread, to which the events to be sent are added.
- Through a loop, the event is sent as long as there are events in the event set.
- Get the Class object of the event and find the Class collection of all the superclasses and interfaces implemented for the current event. The collection is traversed and sent by calling the method that sends the individual events.
- Gets the collection of all subscribers that subscribe to it based on the event, traverses the collection, and sends the event to the subscriber.
- When sent to the subscriber, the subscription method is called according to the thread mode of the subscription method, and if a thread switch is needed, the thread is switched to make the call. Otherwise, call directly.
6.2.3 EventBus. GetDefault (). The unregister (this)
Now that you’ve covered subscriber registration and message delivery, let’s take a look at message untying.
public synchronized void unregister(Object subscriber) { List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes ! = null) { for (Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); }}
As you can see, in the unsubscribeByEventType() method, all subscription information for this subscriber is removed from the SubscriptionsByEventType. Finally, the linked list of registered objects and their corresponding events is removed.
6.2.4 EventBus. GetDefault. PostSticky ()
If you want to fire a sticky event, you need to pass EventBus’s postSticky() method, as shown below.
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
post(event);
}
The main purpose of the above code is to first put the event into StickyEvents, and then use the post() method to send the event. When we analyzed the register() method earlier, we did a bit of an analysis of sticky events.
if (subscriberMethod.sticky) {
Object stickyEvent = stickyEvents.get(eventType);
if (stickyEvent != null) {
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
As you can see, when registering an event, the knowledge determines if the current event is a sticky event, and if so, it pulls the event out of StickyEvents and executes the posttoSubscription () method.