directory

1. Source mind mapping 2. Runtime source analysis 3. Compilation principles 3. Summary

1.ARouter source mind map

This diagram is the most recent summary of componentization buckle out!

image.png

2.ARouter source code analysis

1. The init phase

We find the entrance to Arouter, where we initialized it:

If (isDebug()) {// These two lines must be written before init, otherwise these configurations will not be valid during init arouter.openlog (); // Prints the log arouter.openDebug (); // Enable debug mode (if running in InstantRun mode, debug must be enabled! } ARouter. Init (mApplication); // As early as possible, it is recommended to initialize in ApplicationCopy the code

Let’s go straight to the arouter.init method

/** * Init, it must be call before used router. */ public static void init(Application application) { if (! HasInit) {// Make sure to initialize 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."); }}Copy the code

Next, let’s go to the implementation class: continue with the _arouter.java implementation

protected static synchronized boolean init(Application application) { mContext = application; LogisticsCenter.init(mContext, executor); Logger. info(consts. TAG, "ARouter init success!") ); hasInit = true; return true; }Copy the code

Let’s just focus on the key, logisticScenter.init (mContext, executor); Executor is a thread pool. The main implementation is in the logisticScenter. init method, keep looking

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; executor = tpe; . Set<String> routerMap; // If it is debug mode or a new version, Loading class if generated from apt package (ARouter. Debuggable () | | PackageUtils. IsNewVersion (context)) {/ / ClassUtils getFileNameByPackageName Is according to the registration to find the corresponding registration under the class, will not post the code.. routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); if (! RouterMap. IsEmpty ()) {/ / to join sp cache context. GetSharedPreferences (AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); } PackageUtils.updateVersion(context); / / update version} else {/ / read the class name or from the cache routerMap = new HashSet < > (context. GetSharedPreferences (AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())); } // Use reflection to instantiate the object, and call the method // iterate over 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";Copy the code

While ClassUtils. GetFileNameByPackageName methods do is to find the app dex, and then traverse out of them belong to the com. Alibaba. Android. Arouter. All routes packets under the name of the class, packaged into set back. You can imagine how much work it would take to traverse the dex looking for a given class name. [Arouter-gradle-plugin] ASM piling is needed to solve this very costly performance problem

Can see the initialization is find com. Alibaba. Android. Arouter. Routes packets of class, for instance and forced into IRouteRoot, IInterceptorGroup, IProviderGroup, The loadInto method is then called.

Via the demo code can see com. Alibaba. Android. Arouter. Routes. Arouter $$$$Root app such classes

// 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<? Routes. Put ("service", "new routes "," new routes ") { ARouter$$Group$$service.class); routes.put("test", ARouter$$Group$$test.class); }}Copy the code

You can see that this is the code generated by analyzing the annotations at compile time. ARouter$$Group$$service.class 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)); . }}Copy the code

Here you can see helloServiceImp.java defined in the demo code appear. In fact, ARouter automatically generates some associated code at compile time by analyzing annotations. The other Interceptors are logically similar. Interceptors are classes that register the Route annotation and implement the IProvider interface. Providers are classes that register the Route annotation and implement the IProvider interface

Summary of init process:

With all the initialization done, ARouter will automatically generate some injection code at compile time based on the annotation declaration analysis. The initialization process mainly caches the annotation object and the configuration of the annotation into Warehouse’s static object.

2. Jump

Arouter.getinstance ().build("/test/activity2").navigation();Copy the code
// ARouter.java 
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

Copy the code
// _ARouter. Java // group is the first part of the path passed in by default. For example, path = /test/activity1, the group will default to test/ / If it is declared manually, you must pass it manually otherwise you may not find protected Postcard build(String Path, String group) { return new Postcard(path, group); }Copy the code

The Postcard object is returned directly and the path, group. The Postcard is in _ARouter. Java which is called after the RouteMeta navigation method. Let’s go straight to the core code

// _ARouter. Java // Postcard is an object instance constructed by the build method above. // requestCode is the way to differentiate startAcitivity. If it's not negative 1, // NavigationCallback is a callback to various states. final Postcard postcard, final int requestCode, Final NavigationCallback callback) {try {/ / verify to find it. The corresponding path LogisticsCenter.com pletion (it); } Catch (NoRouteFoundException ex) {// If you can't find the configuration of the Postcard, call the onLost callback or the DegradeService callback if (null! = callback) { callback.onLost(postcard); } else { DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class); if (null ! = degradeService) { degradeService.onLost(context, postcard); } } return null; }... }Copy the code

Navigation called LogisticsCenter.com pletion methods validation, we see the LogisticsCenter. Java the method how to validate it, and then continue to look at the following navigation method logic

// logisticScenter. Java public synchronized static void completion(Postcard) {// Find the Postcard's path RouteMeta RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); If (null == routeMeta) {// If (null == routeMeta) { extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); If (null == groupMeta) { Throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]"); } else {// returns the IRouteGroup object and calls the loadInto method. IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance(); iGroupInstance.loadInto(Warehouse.routes); / / delete group cache Warehouse. GroupsIndex. Remove (it) getGroup ()); // Call the Completion method again when the corresponding group has cached Completion (postcard); // Reload}} else {// routeMeta, 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); Providers (null == instance) {IProvider provider; // Initialize the provider object and call the initialization method to the Warehouse. 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 to wonders. SetProvider (instance); // Provider default Settings skip the interceptor Postcard.greenchannel (); break; Case FRAGMENT: // FRAGMENT default Settings skip the interceptor Postcard.greenChannel (); default: break; }}}Copy the code

The completion method is essentially a lazy load of the group, and we continue with the navigation method below:

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { ... // This callback is about the postcard. If (null! = callback) { callback.onFound(postcard); } // If the fragment is in the "green channel ", execute _navigation method directly. It. IsGreenChannel ()) {/ / interceptorService is ARouter configuration default intercept service interceptorService doInterceptions (it, 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; }Copy the code

InterceptorService ARouter is configured default interceptor com. Alibaba. Android. ARouter. Core. InterceptorServiceImpl. Java

public void doInterceptions(final Postcard postcard, Final InterceptorCallback callback) {/ / thread pool asynchronous execution LogisticsCenter. The executor, execute (new Runnable () {public void the run () { // Initialize a synchronization count class, Using interceptor size CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch (Warehouse. Interceptors. The size ()); Try {// perform interception operation, see this guess is recursive call. _excute(0, interceptorCounter, postcard); // Thread wait count completed, wait 300 seconds... interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS); // Determine 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, proceed with callback.oncontinue (postcard); }} catch (Exception e) {// Interrupt callback.oninterrupt (e); }}}); }Copy the code

Let’s move on to the _excute method

private static void _excute(final int index, final CancelableCountDownLatch counter, Final it it) {/ / recursive exit condition if (index < Warehouse. Interceptors. The size ()) {/ / get to perform interceptor IInterceptor IInterceptor  = Warehouse.interceptors.get(index); // Execute iInterceptor.process(postcard, New InterceptorCallback() {public void onContinue(Postcard) {// Counter reduces 1 counter.countdown (); // Continue with the next intercept _excute(index + 1, counter, postcard); } public void onInterrupt(Throwable exception) {// Exit the recursive postcard.settag (null == exception? new HandlerException("No message.") : exception.getMessage()); counter.cancel(); }}); }}Copy the code

As we guessed, a standard recursive call will return to the _navigation method after all interceptors have executed (assuming none of them are intercepting)

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 Activity: 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 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 set 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 // Jump activity call arouter.getInstance ().build("/test/activity2").navigation();Copy the code
// ARouter.java 
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

Copy the code
// _ARouter. Java // group is the first part of the path passed in by default. For example, path = /test/activity1, the group will default to test/ / If it is declared manually, you must pass it manually otherwise you may not find protected Postcard build(String Path, String group) { return new Postcard(path, group); }Copy the code

The Postcard object is returned directly and the path, group. The Postcard is in _ARouter. Java which is called after the RouteMeta navigation method. Let’s go straight to the core code

// _ARouter. Java // Postcard is an object instance constructed by the build method above. // requestCode is the way to differentiate startAcitivity. If it's not negative 1, // NavigationCallback is a callback to various states. final Postcard postcard, final int requestCode, Final NavigationCallback callback) {try {/ / verify to find it. The corresponding path LogisticsCenter.com pletion (it); } Catch (NoRouteFoundException ex) {// If you can't find the configuration of the Postcard, call the onLost callback or the DegradeService callback if (null! = callback) { callback.onLost(postcard); } else { DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class); if (null ! = degradeService) { degradeService.onLost(context, postcard); } } return null; }... }Copy the code

Navigation called LogisticsCenter.com pletion methods validation, we see the LogisticsCenter. Java the method how to validate it, and then continue to look at the following navigation method logic

// logisticScenter. Java public synchronized static void completion(Postcard) {// Find the Postcard's path RouteMeta RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); If (null == routeMeta) {// If (null == routeMeta) { extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); If (null == groupMeta) { Throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]"); } else {// returns the IRouteGroup object and calls the loadInto method. IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance(); iGroupInstance.loadInto(Warehouse.routes); / / delete group cache Warehouse. GroupsIndex. Remove (it) getGroup ()); // Call the Completion method again when the corresponding group has cached Completion (postcard); // Reload}} else {// routeMeta, 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); Providers (null == instance) {IProvider provider; // Initialize the provider object and call the initialization method to the Warehouse. 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 to wonders. SetProvider (instance); // Provider default Settings skip the interceptor Postcard.greenchannel (); break; Case FRAGMENT: // FRAGMENT default Settings skip the interceptor Postcard.greenChannel (); default: break; }}}Copy the code

The completion method is essentially a lazy load of the group, and we continue with the navigation method below:

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { ... // This callback is about the postcard. If (null! = callback) { callback.onFound(postcard); } // If the fragment is in the "green channel ", execute _navigation method directly. It. IsGreenChannel ()) {/ / interceptorService is ARouter configuration default intercept service interceptorService doInterceptions (it, 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; }Copy the code

InterceptorService ARouter is configured default interceptor com. Alibaba. Android. ARouter. Core. InterceptorServiceImpl. Java

public void doInterceptions(final Postcard postcard, Final InterceptorCallback callback) {/ / thread pool asynchronous execution LogisticsCenter. The executor, execute (new Runnable () {public void the run () { // Initialize a synchronization count class, Using interceptor size CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch (Warehouse. Interceptors. The size ()); Try {// perform interception operation, see this guess is recursive call. _excute(0, interceptorCounter, postcard); // Thread wait count completed, wait 300 seconds... interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS); // Determine 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, proceed with callback.oncontinue (postcard); }} catch (Exception e) {// Interrupt callback.oninterrupt (e); }}}); }Copy the code

Let’s move on to the _excute method

private static void _excute(final int index, final CancelableCountDownLatch counter, Final it it) {/ / recursive exit condition if (index < Warehouse. Interceptors. The size ()) {/ / get to perform interceptor IInterceptor IInterceptor  = Warehouse.interceptors.get(index); // Execute iInterceptor.process(postcard, New InterceptorCallback() {public void onContinue(Postcard) {// Counter reduces 1 counter.countdown (); // Continue with the next intercept _excute(index + 1, counter, postcard); } public void onInterrupt(Throwable exception) {// Exit the recursive postcard.settag (null == exception? new HandlerException("No message.") : exception.getMessage()); counter.cancel(); }}); }}Copy the code

As we guessed, a standard recursive call will return to the _navigation method after all interceptors have executed (assuming none of them are intercepting)

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 Activity: 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 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 set 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; }Copy the code
3. Perform navigation on the IProvider

Main implementation is in LogisticsCenter.com pletion methods to some branch IProvider processing

Switch (routemeta.getType ()) {case PROVIDER: // Return the Class that implements the IProvider interface. extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination(); IProvider instance = warehouse.provider. get(providerMeta); Providers (null == instance) {IProvider provider; // Initialize the provider object and call the initialization method to the Warehouse. 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 to wonders. SetProvider (instance); // Provider default Settings skip the interceptor Postcard.greenchannel (); break;Copy the code
// You can see that the _navigation method simply returns the reference set in the completion method. 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; }Copy the code
The contents of the annotation are injected into the cache during initialization, and the corresponding class implementation is looked up from the cache when the jump is started. It looks pretty simple,

3. Compiler summary

ARouter to summarize

Now that we’ve gone through most of the ARouter code, let’s summarize how ARouter works.

Compile-time arouter- Compiler is responsible for generating some of the injected code:

-arouter $$Group$$group-name = '@route'; -arouter $$Root$$app = 'Group' ARouter$$Group$$group-name - ARouter$$Providers$$app Interface information - Test1Activity$$ARouter$$Autowired @autoWired Implementation of automatic injectionCopy the code

ARouter will cache the injected information when initialized

During navigation, lazy loading is performed based on the cache, and then the actual object is retrieved or the activity is jumped.

Automatic injection calls the corresponding Test1ActivityAutowired instance that copies the field declaring @autowired.