Arouter source code analysis

Time:2021-12-30

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!

Arouter source code analysis

image.png

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.getFileNameByPackageNameMethod is to find the DEX of the app, and then traverse itcom.alibaba.android.arouter.routesAll 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$$appSuch 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.classIt 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