catalogue
1. Source code mind map
2. Runtime source code analysis
3. Compile time principle
3. Summary
1. Thinking map of arouter source code
This figure is deducted from the recent summary of componentization!

2. Arouter source code analysis
1. Init phase
We find the entrance of arouter, that is, the initialization place:
If (isdebug()) {// these two lines must be written before init, otherwise these configurations will be invalid during init
ARouter. openLog(); // Print log
ARouter. openDebug(); // Enable the debugging mode (if running in the instantrun mode, the debugging mode must be enabled! The online version needs to be closed, otherwise there is a security risk)
}
ARouter. init(mApplication); // As early as possible, it is recommended to initialize in the application
Let’s look directly at arouter Init method
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
If (! Hasinit) {// make sure to initialize only once
logger = _ ARouter. logger;// Log class
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
Then we go to the implementation class and take a look: continue to look_ ARouter. Java implementation
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter. init(mContext, executor); // Actual initialization place
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
return true;
}
We only focus on the key points,LogisticsCenter.init(mContext, executor);
, the executor is a thread pool.
The main implementations are in logisticscenter In the init method, continue to view
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
...
Set<String> routerMap;// Generate a collection of class names for a class
//If it is in debug mode or a new version, load the class from the package generated by apt
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
// ClassUtils. Getfilenamebypackagename is to find the class under the corresponding registration according to the registration, so the code will not be pasted
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
If (! Routermap. Isempty()) {// add to sp cache
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils. updateVersion(context); // Updated version
}Else {// otherwise, read the class name from the cache
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
//Judge the type, instantiate the object using reflection, and call method // to traverse the obtained class
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
`ROUTE_ ROOT_ Pakcage ` is a constant:
```java
public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
andClassUtils.getFileNameByPackageName
Method is to find the DEX of the app, and then traverse itcom.alibaba.android.arouter.routes
All class names under the package are packaged into a collection and returned. You can imagine the workload of traversing the whole DEX to find the specified class name. What should I do?[arouter gradle plugin] ASM pile insertion is requiredTo solve this very expensive performance problem
You can see that initialization is to find com alibaba. android. arouter. The class under the routes package gets the instance and coercion it into IRouteRoot, IInterceptorGroup, IProviderGroup, and then calls the loadInto method.
Through the code search of the demo, you can see that there are
com.alibaba.android.arouter.routes.ARouter$$Root$$app
Such a class
// ARouter$$Root$$app.java
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
//Take the group as the key and cache it to routes
//Routes refers to the warehouse Reference to groupsindex
routes.put("service", ARouter$$Group$$service.class);
routes.put("test", ARouter$$Group$$test.class);
}
}
You can see that this is the code generated by analyzing annotations at compile timeARouter$$Group$$service.class
It is also generated
// ARouter$$Group$$service.java
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$service implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
...
}
}
Here you can see the helloserviceimpl defined in the demo code Java and so on appeared In fact, arouter automatically generates some associated code at compile time by analyzing annotations In addition, interceptors and providers are logically similar
Interceptors is a class that registers and declares interceptor annotation and implements iinterceptor interface. Providers is a class that registers and declares route annotation and implements iprovider interface
Init process summary:
The initialization work has been completed. To sum up, arouter will automatically generate some injection code according to the annotation declaration analysis during compilation. The initialization work is mainly to cache the annotated objects and the annotation configuration into the static objects of the warehouse
2. Jump
//Call to jump activity
ARouter.getInstance().build("/test/activity2").navigation();
// ARouter.java
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
// _ARouter.java
//Group is the first part of the path passed in by default For example, if path = / test / activity1, the group will default to test
//If it is declared manually, it must be passed manually, otherwise it will not be found
protected Postcard build(String path, String group) {
return new Postcard(path, group);
}
Here, a postcard object is returned directly and path and group are saved Postcard inherits routemetal
The navigation method is called in the end_ ARouter. In Java, the intermediate procedure is omitted Let’s look directly at the core code
// _ARouter.java
//Postcard is the object instance constructed with the build method
//Requestcode is the way to distinguish startactivity If it is not - 1, it is started in the way of startactivityforresult
//Navigationcallback is a callback to various states
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//Verify whether the corresponding postcard can be found path
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
//If the configuration of postcard is not found, call the onlost callback method or the degradeservice callback configured by the system
if (null != callback) {
callback.onLost(postcard);
} else {
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
...
}
Navigation called logisticscenter For verification of the completion method, let’s take a look at logisticscenter How does the Java method verify postcard, and then continue to look at the logic under the navigation method
// LogisticsCenter.java
public synchronized static void completion(Postcard postcard) {
//Find the corresponding routemetas through the path of postcard
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
//If it is not found, it may not have been loaded. You need to find the corresponding groups according to the group
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
if (null == groupMeta) {
//If not found, a noroutefoundexception error is thrown and the method ends
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
//Return the iroutegroup object and call the loadinto method
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
//Delete group cache
Warehouse.groupsIndex.remove(postcard.getGroup());
//Call the completion method again, and the corresponding group has been cached
completion(postcard); // Reload
}
} else {
//You can find routemetas and copy the original data of routemetas to postcard
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
switch (routeMeta.getType()) {
case PROVIDER:
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
//Initialize the provider object, call the initialization method, and cache it to the warehouse In providers
if (null == instance) {
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
//Set a provider reference
postcard.setProvider(instance);
//Provider defaults to skip interceptors
postcard.greenChannel();
break;
case FRAGMENT:
//Fragment defaults to skip interceptors
postcard.greenChannel();
default:
break;
}
}
}
The completion method is mainly used to lazy load the group. Let’s continue to check the following contents of the navigation method:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
...
//This is where you find postcard Execute corresponding callback
if (null != callback) {
callback.onFound(postcard);
}
//If it is "green channel", execute directly_ At present, there is only a provider for the navigation method, and the fragment takes the green channel by default
if (!postcard.isGreenChannel()) {
//Interceptorservice is the default interception service configured by arouter
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
//Green channel
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
Interceptorservice is the default interceptor configured by arouter. Com alibaba. android. arouter. core. InterceptorServiceImpl. java
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
//Asynchronous execution with thread pool
LogisticsCenter.executor.execute(new Runnable() {
public void run() {
//Initialize a synchronous counting class and use the size of the interceptor
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
//Execute the interception operation and see that this is a recursive call Exit recursion until index satisfies the condition
_excute(0, interceptorCounter, postcard);
//Thread wait count complete, wait 300 seconds
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
//Judge some states after exiting the wait
if (interceptorCounter.getCount() > 0) {
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) {
callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
} else {
//No problem, continue
callback.onContinue(postcard);
}
} catch (Exception e) {
//Interrupt
callback.onInterrupt(e);
}
}
});
}
Let’s keep looking_ Excute method
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
//Recursive exit condition
if (index < Warehouse.interceptors.size()) {
//Gets the interceptor to execute
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
//Execute interception
iInterceptor.process(postcard, new InterceptorCallback() {
public void onContinue(Postcard postcard) {
//Counter minus 1
counter.countDown();
//Continue with the next intercept
_excute(index + 1, counter, postcard);
}
public void onInterrupt(Throwable exception) {
//Exit recursion when intercepted
postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());
counter.cancel();
}
});
}
}
As we guessed, a standard recursive call, when all interceptors are executed (assuming no interception), finally returns to_ Navigation method
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
//Let's just analyze the logic of activity
switch (postcard.getType()) {
case ACTIVITY:
//Initialize intent and add parameters
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
//Start activity in UI thread
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
//Animation settings
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
}
return null;
}
``````java
//Call to jump activity
ARouter.getInstance().build("/test/activity2").navigation();
// ARouter.java
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
// _ARouter.java
//Group is the first part of the path passed in by default For example, if path = / test / activity1, the group will default to test
//If it is declared manually, it must be passed manually, otherwise it will not be found
protected Postcard build(String path, String group) {
return new Postcard(path, group);
}
Here, a postcard object is returned directly and path and group are saved Postcard inherits routemetal
The navigation method is called in the end_ ARouter. In Java, the intermediate procedure is omitted Let’s look directly at the core code
// _ARouter.java
//Postcard is the object instance constructed with the build method
//Requestcode is the way to distinguish startactivity If it is not - 1, it is started in the way of startactivityforresult
//Navigationcallback is a callback to various states
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//Verify whether the corresponding postcard can be found path
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
//If the configuration of postcard is not found, call the onlost callback method or the degradeservice callback configured by the system
if (null != callback) {
callback.onLost(postcard);
} else {
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
...
}
Navigation called logisticscenter For verification of the completion method, let’s take a look at logisticscenter How does the Java method verify postcard, and then continue to look at the logic under the navigation method
// LogisticsCenter.java
public synchronized static void completion(Postcard postcard) {
//Find the corresponding routemetas through the path of postcard
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
//If it is not found, it may not have been loaded. You need to find the corresponding groups according to the group
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
if (null == groupMeta) {
//If not found, a noroutefoundexception error is thrown and the method ends
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
//Return the iroutegroup object and call the loadinto method
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
//Delete group cache
Warehouse.groupsIndex.remove(postcard.getGroup());
//Call the completion method again, and the corresponding group has been cached
completion(postcard); // Reload
}
} else {
//You can find routemetas and copy the original data of routemetas to postcard
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
switch (routeMeta.getType()) {
case PROVIDER:
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
//Initialize the provider object, call the initialization method, and cache it to the warehouse In providers
if (null == instance) {
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
//Set a provider reference
postcard.setProvider(instance);
//Provider defaults to skip interceptors
postcard.greenChannel();
break;
case FRAGMENT:
//Fragment defaults to skip interceptors
postcard.greenChannel();
default:
break;
}
}
}
The completion method is mainly used to lazy load the group. Let’s continue to check the following contents of the navigation method:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
...
//This is where you find postcard Execute corresponding callback
if (null != callback) {
callback.onFound(postcard);
}
//If it is "green channel", execute directly_ At present, there is only a provider for the navigation method, and the fragment takes the green channel by default
if (!postcard.isGreenChannel()) {
//Interceptorservice is the default interception service configured by arouter
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
//Green channel
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
Interceptorservice is the default interceptor configured by arouter. Com alibaba. android. arouter. core. InterceptorServiceImpl. java
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
//Asynchronous execution with thread pool
LogisticsCenter.executor.execute(new Runnable() {
public void run() {
//Initialize a synchronous counting class and use the size of the interceptor
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
//Execute the interception operation and see that this is a recursive call Exit recursion until index satisfies the condition
_excute(0, interceptorCounter, postcard);
//Thread wait count complete, wait 300 seconds
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
//Judge some states after exiting the wait
if (interceptorCounter.getCount() > 0) {
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) {
callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
} else {
//No problem, continue
callback.onContinue(postcard);
}
} catch (Exception e) {
//Interrupt
callback.onInterrupt(e);
}
}
});
}
Let’s keep looking_ Excute method
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
//Recursive exit condition
if (index < Warehouse.interceptors.size()) {
//Gets the interceptor to execute
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
//Execute interception
iInterceptor.process(postcard, new InterceptorCallback() {
public void onContinue(Postcard postcard) {
//Counter minus 1
counter.countDown();
//Continue with the next intercept
_excute(index + 1, counter, postcard);
}
public void onInterrupt(Throwable exception) {
//Exit recursion when intercepted
postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());
counter.cancel();
}
});
}
}
As we guessed, a standard recursive call, when all interceptors are executed (assuming no interception), finally returns to_ Navigation method
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
//Let's just analyze the logic of activity
switch (postcard.getType()) {
case ACTIVITY:
//Initialize intent and add parameters
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
//Start activity in UI thread
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
//Animation settings
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
}
return null;
}
3. Navigate iprovider
The main implementation is in logisticscenter The iprovider is branched in the completion method
switch (routeMeta.getType()) {
case PROVIDER:
//Returns the class that implements the iprovider interface
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
//Find in cache
IProvider instance = Warehouse.providers.get(providerMeta);
//Initialize the provider object, call the initialization method, and cache it to the warehouse In providers
if (null == instance) {
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
//Set a provider reference
postcard.setProvider(instance);
//Provider defaults to skip interceptors
postcard.greenChannel();
break;
//Can see_ The navigation method directly returns the reference set in the completion method
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
case PROVIDER:
return postcard.getProvider();
}
return null;
}
When initializing, inject the content marked by the annotation into the cache, and then start the jump to find the implementation of the corresponding class according to the cache It also looks simple,
3. Compiler summary
Arouter summary
We have analyzed most of the code of arouter, and then summarize the working principle of arouter
During compilation, arouter compiler is responsible for generating some injected code:
-Arouter $$group $$group name injects the information of @ route declared under the group with group name as the file name
-Arouter $$root $$app injects arouter $$group $$group name by group
-Arouter $$providers $$app injects information that implements the iprovider interface
-Arouter $$interceptors $$app injection implements iinterceptor interface information
-Implementation of test1activity $$arouter $$Autowired @ Autowired automatic injection
During arouter initialization, the injected information will be cached
During navigation, lazy loading is performed according to the cache, and then the actual object is obtained or jump to activity
Automatic injection is to call the corresponding test1activityThe Autowired instance copies the fields declaring @ Autowired