Retrofit
Basic Usage
A standard GET request workflow with Retrofit looks like this:
// Build Retrofit instance
Retrofit githubRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// Create API service implementation
GitHubApi gitHubService = githubRetrofit.create(GitHubApi.class);
// Generate and execute request
Call<List<Repository>> reposCall = gitHubService.listUserRepos("octocat");
// Synchronous execution
// reposCall.execute();
// Asynchronous execution
// reposCall.enqueue(new Callback<List<Repository>>() {
// @Override
// public void onResponse(Call<List<Repository>> call, Response<List<Repository>> response) {}
//
// @Override
// public void onFailure(Call<List<Repository>> call, Throwable t) {}
// });
POST requests only require updating the method annotations on your API interface. While the usage is simple, Retrofit uses advanced architectural design and dozens of design patterns internally.
Source Code Analysis
Retrofit Instance Construction
Retrofit uses the Builder pattern to create instances. The core fields of the Builder class include:
public static final class Builder {
private final Platform platform;
private @Nullable okhttp3.Call.Factory callFactory;
private @Nullable HttpUrl baseUrl;
private final List<Converter.Factory> converterFactories = new ArrayList<>();
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
private @Nullable Executor callbackExecutor;
private boolean validateEagerly;
}
The platform detection logic automatically identifies Android or Java environments:
static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {}
return new Platform();
}
The baseUrl method validates and converts string URLs to OkHttp's HttpUrl type:
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;
}
The final build() method initializes all required components:
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
List<Converter.Factory> converterFactories = new ArrayList<>(1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
Create API Service Instance
The create() method uses dynamic proxy and facade pattern to generate service implementations:
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
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 (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);
}
});
}
The loadServiceMethod() method caches parsed service method metadata:
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
Glide
Basic Usage
Glide is a popular Android image loading libray with simple API:
// Basic image loading
Glide.with(context).load("https://example.com/image.jpg").into(imageView);
// With placeholder and error drawable
Glide.with(this)
.load("https://example.com/image.jpg")
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.load_failed_image)
.into(imageView);
Source Code Analysis
with(context) Method
The with() method binds Glide's lifecycle to the passed context, to avoid resource waste and null pointer exceptions:
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);
}
The RequestManagerRetriever uses singleton pattern, and returns different RequestManager instences based on the context type:
public class RequestManagerRetriever implements Handler.Callback {
private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
public static RequestManagerRetriever get() {
return INSTANCE;
}
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);
}
}
load(url) Method
The load() method creates a request builder for image loading:
public DrawableTypeRequest load(String stringUrl) {
return (DrawableTypeRequest) fromString().load(stringUrl);
}
private DrawableTypeRequest loadGeneric(Class<?> modelClass) {
ModelLoader<InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(modelClass, context);
return optionsApplier.apply(
new DrawableTypeRequest<>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
into(imageView) Method
The into() method actually executes the image loading request:
public Target into(ImageView view) {
return super.into(view);
}
public Target into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
default:
break;
}
}
return into(glide.buildImageViewTarget(view, transcodeClass));
}
EventBus
Basic Usage
EventBus is an event publish-subscribe library for Android that simplifies component communication.
Core Concepts
- Event: Any type of object that carries event data
- Subscriber: A class that listens for events, with methods annotated with
@Subscribe - Publisher: A class that sends events using
EventBus.post()
EventBus supports four thread models:
POSTING: Runs in the same thread as the publisherMAIN: Runs on UI thread, no long-running operationsBACKGROUND: Runs on background thread, no UI operationsASYNC: Always runs on a new background thread
Standard Usage Flow
- Define an event class:
public class MessageEvent {
private String message;
public MessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
- Register and unregister subscribers:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageReceived(MessageEvent event) {
String receivedMsg = event.getMessage();
// Handle event
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
- Publish an event:
EventBus.getDefault().post(new MessageEvent("Hello EventBus!"));
Source Code Analysis
EventBus.getDefault()
Uses double-checked locking singleton pattern to create a single EventBus instance:
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
register(Object subscriber)
Parses annnotated subscriber methods and binds them to their event types:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
post(Object event)
Dispatches events to subscribed subscribers based on thread models:
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;
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
unregister(Object subscriber)
Removes all subscriptions for a given subscriber:
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);
}
}
Sticky Events
Sticky events persist in memory after being posted, allowing late subscribers to receive them:
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
post(event);
}