Arouter – a framework for componentizing Android apps – supports routing, communication, and decoupling between modules

Technical preparation

  1. SPI
  2. APT

Source code analysis

Let’s look at the official demo directly. The core modules include API, Compiler and annotation. First, we will briefly talk about the functions of each module:

  • Annoation: Defines annotations and basic information about a Route
  • Compiler: mainly used to process the annotations at compile time the Router/Interceptor Autowire three annotations, at compile time automatic registration of annotations, such as member variables.
  • Api: The core Api that the user invokes

annoation

Autowired is used for annotation field, which enables automatic injection of parameters passed by the routing component without requiring the user to fetch the parameters passed in the code. Note that the modifier of the annotation object cannot be private.

Interceptor is used to annotate interceptors. Refer to the official documentation for the concept of interceptors

Route Indicates the Route annotation, which can be an Activity or a Service

RouteMeta Indicates routing data, including route type, path, group, weight, and parameters

compiler

RouteProcessor

Route annotator

The key method is # parserOutes. Let’s take a look at the final file generated by this method to make it easier to disentangle the code.

public class ARouter?Root?app implements IRouteRoot {
    publicARouter? Root? app() { }public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        routes.put("service", service.class);
        routes.put("test", test.class); }}Copy the code

The map key is service and test. Valude is ARouter? Group? Service. The class and ARouter? Group? The test class. A component has one and only one Root class, which is distinguished by a suffix.

public class ARouter?Group?test implements IRouteGroup {
    publicARouter? Group? test() { }public void loadInto(Map<String, RouteMeta> atlas) {
  		atlas.put("/test/*", RouteMeta)
    }
}
Copy the code
public class ARouter?Group?service implements IRouteGroup {
    publicARouter? Group? service() { }public void loadInto(Map<String, RouteMeta> atlas) {
   		atlas.put("/service/*", RouteMeta)
    }
}
Copy the code

Note Arouter? Group? The map key in test must be prefixed with /test, which is the same as the suffix

public class ARouter?Providers?app implements IProviderGroup {
    publicARouter? Providers? app() { }public void loadInto(Map<String, RouteMeta> providers) {
        providers.put(className, RouteMeta)
    }
}
Copy the code
public class ARouter?Interceptors?app implements IInterceptorGroup {
    publicARouter? Interceptors? app() { }public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) { interceptors.put(privority, Class); }}Copy the code

To summarize:

  1. Within a Module, there is one and only one automatically generated IRouteRoot class, suffixed with the Module name
  2. There can be multiple IRouteGroup classes automatically generated in a module, all registered in the IRouteRoot class
  3. There is only one IProviderGroup with the suffix Module name

The suffix is not exactly the name of the module. In fact, you can specify it in Gradle, which will be explained later in the parseRoutes method

Next, the routeProcess #parseRoutes method:

  1. Get a Route, build a RouteMeta based on RouteType, and use #categoryies to build a mapping and store it in a groupMap. Value is the RouteMeta collection.)
  2. Create ARouter? Group? GroupName class, and stores the mapping between groupName and the full class name of the generated File to rootName
  3. Create ARouter? Providers? ModuleName class
  4. Create ARouter? Rout? moudleName

If you do not specify a group, ARouter will use the main path of path by default. For example, if you specify path = “/test/main”, groupName is test; MoudleName is specified by you in Gradle.

AutoWiredProccesor

First take a look at the generated file:

public class Test1Activity?ARouter?Autowired implements ISyringe {
    private SerializationService serializationService;

    publicTest1Activity? ARouter? Autowired() { }public void inject(Object target) {
        this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
        Test1Activity substitute = (Test1Activity)target;
        substitute.name = substitute.getIntent().getStringExtra("name"); . substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class); }}Copy the code

The generated ISyring class inherits only one inject(Object) method. The implementation of this method mainly does two things:

  1. AutoWiredFiled if the annotation isservice(Arouter inherits from IProvider), then initialize the object:
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);
Copy the code
  1. AutoWriedThe filed of the annotationsservice, get the corresponding value from the bundle and assign it to the field:
	substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class); // activity
	substitute.name = substitute.getArguments().getString("name"); // fragment
Copy the code
  1. Finally generate the class name? Arouter? AutoWired. Class files

InterceptorProccessor

Intercepting annotation generator

public class ARouter?Interceptors?app implements IInterceptorGroup {
    publicARouter? Interceptors? app() { }public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
        interceptors.put(7, Test1Interceptor.class); }}Copy the code

api

ARouter & _ARouter

Launcher only has two classes ARouter, _ARouter, and ARouter uses singleton mode, If we call arouter.getInstance ().build(), we’ll call the _ARouter method. If we call arouter.getinstance ().

Init (): In the Arouter library, there is a Warehose class that stores routing information. Before using Arouter, we have to call init method, which stores information into Warehose.

The information is successfully stored to the Warehose by calling the #loadInto(Warehose) method.

Now Warehose’s groupsIndex, providersIndex, interceptorsIndex have been stored into our routing information

Finally, call _ARouter#afterInit() to instantiate the interceptorService

Next comes the navigation() method:

The Postcard class needs to know about it first. It inherits RouteMeta and extends some attributes, such as Flag (the flag of an activity), URI, provider, and Anim (animation). Build method returns Postcasrd, postcard#navigation, and _Arouter#navaigation

LogisticsCenter.completion(postcard);
if(! postcard.isGreenChannel()) {// Interceptor related, skip for now
    interceptorSevice.doInterceptions(post, new InterceptorCallback() {
        @Override
        public void onContinue(Postcard postcard) { _navigation(context, postcard, requestCode, callback); }})}else {
    return _navigation(context, postcard, requestCode, callback);
}
Copy the code

First let’s look at the LogisticsCenter#completion(Postcard) method

. RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());if (null == routeMeta) {
    Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance(); // ARouter? Group%%test instantiates the object
    iGroupInstance.loadInto(Warehouse.routes);
    Warehouse.groupsIndex.remove(postcard.getGroup());
    completion(postcard);   // Reload enters the else branch
} else{.../ / postcadrd set properties such as: destination, type, priority, etc
    switch(routeMeta.getType) {
        case PROVIDER:
            Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
            IProvider instance = Warehouse.providers.get(providerMeta);
            if(instance == null) {
                IProvider povider = providerMeta.getConstructor().newInstance();
                provider.init(mContext); The init method is called only once after the provider object is created,
                Warehose.providers.put(providerMeta, provider); // Store the provider to the map. The provider will remain in the map until ARouter#destory is called
                postcard.setProvider(instance);
                postcard.greenChannel();
            }
            break;
        case FRAGMENT:
            ...
            break; }}Copy the code

The routes and providers in the Warehose are also filled with relevant information

_navigation () method:

switch(postcard.getType()) {
    case ACTIVITY:
        ... // build intent
        startActivity(requestCode, currentCotext, intent, postcard, callback);
        break;
    case POSTCARD:
        return postcard.getProvider();
    case Fragment:
        Fragment fragment = fragmentMeta.getConstructor().newInstance();
        fragment.setArguments(postcard.getExtras());
        return fragment;
    default:
        return null;
}
return null;
Copy the code

Next analyze the ARroute#inject(Object) method:

 AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
 autowiredService.autowire(objec);
Copy the code

The code is very simple, the key is the implementation of AutowiredService

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if(! blackList.contains(className)) { ISyringe autowiredHelper = classCache.get(className);if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); / /? ARouter? Autowired = ? ARouter? Autowired
                }
                autowiredHelper.inject(instance); // Text1Activity? ARouter? Autowired. Inject (this) Retrieves the parameters passed in the bundleclassCache.put(className, autowiredHelper); }}catch (Exception ex) {
            blackList.add(className);    // This instance need not autowired.}}Copy the code

At this point, all of the annotation-generated classes and their implementation methods have been introduced, and the reader should have a new understanding of ARouter. Let’s take a look at the interceptor, which we rarely use

ARouter provides us with an interceptor usage scenario:

A classic application is to handle login events during a jump so that you don't need to repeat login checks on the target pageCopy the code

Remember when we were talking about ARroute#navigation we skipped over the interceptor stuff

if(! postcard.isGreenChannel()) {// Interceptor related, skip for now
    interceptorSevice.doInterceptions(post, new InterceptorCallback() {
        @Override
        public void onContinue(Postcard postcard) { _navigation(context, postcard, requestCode, callback); }})}Copy the code

The chain of responsibility pattern is used for interceptors, including in ARouter. If the interceptorService is intercepted, its type is Provider, and init is called first

@Override
public void init(final Context context) {
    LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run(a) {
                for(entry : Warehose.interceptorsIndex) { Class<? extends IInterceptor> interceptorClass = entry.getValue(); IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance(); iInterceptor.init(context); Warehose.interceptors.add(iInterceptor); }}}}Copy the code

InterceptorService# doInterceptions calls the _excute method

    private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    _excute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor excute over with fatal exception.
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.counter.cancel(); }}); }Copy the code

Common error analysis

  1. Service memory leak, consider the following code:

    public class HelloServiceImpl implements HelloService {
        private Context context;
        
        @Override
        public void init(Context context) {
            this.context = context
        }
        
        @Override
        public void say(a) { Toast.makeText(context, R.string.hello, Toast.LENGTH_LONG).show(); }}Copy the code

    Providers are instantiated into warewarehose. Providers by calling arouter. inject from Warehose. In the above code, if the context is an activity object and the activity is assigned to the context member variable, the activity object will not be reclaimed, causing a memory leak

  2. The confusion results in null service

    public class HelloHolder {
         @Autowired(name = "/service/hello")
        HelloService helloService;
        
        public HelloHolder(a) {
            ARouter.getInstance().inject(this);
        }
        
        public void sayHello(a) { helloSerice.say; }}Copy the code

    The code itself is not a problem, the problem is in android development we need to confuse the code, you remember the implementation of AutowiredService, the key code is here,

                    if (null == autowiredHelper) {  // No cache.
                        autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); / /? ARouter? Autowired = ? ARouter? Autowired
                    }
    Copy the code

    The class created by ARouter should have been HelloHolder, right? ARouter? Autowired then instantiates the object and calls the #inject method of the object, but now you confuse HelloHolder with H, causing the creation of the object to fail, that’s why you call ARouter. GetInstance ().inject(this), But service is still null

    In Android development, we configure the keep and other rules in the confusion rules of Activity and Fragment, so there will be no problem. While cases like HelloHolder are often overlooked, it is important to note that annotations are recommended to keep class names from being confused during development.