ARouter source code reading notes

Write in front

This article does not talk about the usage of ARouter, but just read the source code and experience the thought of Ali God So if you want to know how ARouter works, please take a detour and go away

I Let's start with navigation

When ARouter jumps to the interface, there is almost a sentence of code

       ARouter.getInstance().build(ARouterPath.ACTIVITY_PHOTOS)
                    .navigation(this)

What happens after this code is called? Follow it one by one

  • build
    After calling build, a PostCard object is actually generated. The group information is parsed from the path in the PostCard object, and nothing else is done
protected Postcard build(String path, String group) {
     if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
         throw new HandlerException(Consts.TAG + "Parameter is invalid!");
     } else {
         PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
         if (null != pService) {
             path = pService.forString(path);
         }
         return new Postcard(path, group);
     }
 }
  • navigation
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
	...
    try {
    	//It is important to supplement the missing information of postcard, which will be discussed separately below
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
    	...
    }

    if (!postcard.isGreenChannel()) {
    	... //It is divided into main processes and ignored
    } else {
    	//Execute jump
        return _navigation(context, postcard, requestCode, callback);
    }
    return null;
}

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:
            // Build intent
            // Now get the target you want to jump to from postCard and jump
            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)) {    // Non activity, need less one flag.
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // Set Actions
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }

            // Navigation in main looper.
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });

            break;
            ...
}

As can be seen from the above code, two parts have been completed

  1. Via logisticscenter completion(postcard); To improve Postcard's information
  2. Pass_ The navigation method is used to jump, which is no different from the system's jump of Activity

Take a look at logisticscenter What did completion (Postcard) do

public synchronized static void completion(Postcard postcard) {
	//Read the loaded postcard data from memory
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
    	//Get IRouteGroup first, and then load it into memory
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        //If not, load it into memory 
        ...
     	IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
     	//Load into memory
        iGroupInstance.loadInto(Warehouse.routes);
        Warehouse.groupsIndex.remove(postcard.getGroup());
        completion(postcard);   // Reload
    } else {
    	//Spoil all kinds of data in your Gan village from the inside
    	...
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
    }
}

As can be seen above

  1. The route information will be found from the memory first
  2. If it cannot be found in memory, the corresponding group information will be found first, that is, IRouteGroup
  3. Once found, the corresponding route information will be loaded from IRouteGroup
  4. Therefore, according to the official introduction of ARouter, it will be loaded in groups,

II From arouter Init talk

When applying oncreate, you need to call arouter Init method, let's see what this method does

protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());

    return true;
}

You can see the key in logisticscenter In init, let's see what this method does

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
	...
	//Analyze the router map information. The router map here is actually the Class path to be found
    Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    for (String className : routerMap) {
        if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
            //Find a name that is com alibaba. android. arouter. routes. ARouter$$Root$$app
            ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
        } 
        ...
}

As you can see from the above code, the key is to find com alibaba. android. arouter. routes. Arouter $$root $$app class, we find it in the code

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("account", ARouter$$Group$$account.class);
    routes.put("app", ARouter$$Group$$app.class);
    ...
  }
}

Look at the class he loaded

public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/bindPhone", RouteMeta.build(RouteType.ACTIVITY, BindPhoneActivity.class, "/account/bindphone", "account", null, -1, -2147483648));
  }
}

You can see that by calling this class, you can get the Router Activity information annotated by us. In Chapter 1, the jump logic is completed

III What did you do when compiling

It can be seen from the above that the key to the code is that com. Com is generated during editing alibaba. android. arouter. routes. Arouter $$root $$app class and com alibaba. android. arouter. routes. Arouter $$root $$app class. These two classes must be generated in the compilation stage

When we inherit the ARouter framework, we need to add the following configuration

kapt 'com.alibaba:arouter-compiler:1.2.2'

This is used to generate class files. We need the download github address to see the source code
address

https://github.com/alibaba/ARouter
The main logic is in RouteProcess

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
	...
	//Get Route information
    Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
    this.parseRoutes(routeElements);
    ...
}

Take a look at the implementation of parseRoutes

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
    if (CollectionUtils.isNotEmpty(routeElements)) {
    	...
    	//Get typeMirror of Activity
        TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
       	
        // Interface of ARouter
        TypeElement type_IRouteGroup = elementUtils.getTypeElement(IROUTE_GROUP);
        TypeElement type_IProviderGroup = elementUtils.getTypeElement(IPROVIDER_GROUP);
        ClassName routeMetaCn = ClassName.get(RouteMeta.class);
        ClassName routeTypeCn = ClassName.get(RouteType.class);

        /*
           Build input type, format as :
           ```Map<String, Class<? extends IRouteGroup>>```
         */
        ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                )
        );

        /*

          ```Map<String, RouteMeta>```
         */
        ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class)
        );

        /*
          Build input param name.
         */
        ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
        ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
        ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  // Ps. its param type same as groupParamSpec!

        /*
          Build method : 'loadInto'
         */
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);

        //  Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
        for (Element element : routeElements) {
            TypeMirror tm = element.asType();
            Route route = element.getAnnotation(Route.class);
            RouteMeta routeMeta;

            // Activity or Fragment
            if (types.isSubtype(tm, type_Activity)) {
                // Get all fields annotation by @Autowired
                Map<String, Integer> paramsType = new HashMap<>();
                Map<String, Autowired> injectConfig = new HashMap<>();
                injectParamCollector(element, paramsType, injectConfig);

                if (types.isSubtype(tm, type_Activity)) {
                    // Activity
                    logger.info(">>> Found activity route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                }
                routeMeta.setInjectConfig(injectConfig);
            } 
            ...
            categories(routeMeta);
        }

        MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(providerParamSpec);

        Map<String, List<RouteDoc>> docSource = new HashMap<>();

        // Start generate java source, structure is divided into upper and lower levels, used for demand initialization.
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
            String groupName = entry.getKey();

            MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(groupParamSpec);

            List<RouteDoc> routeDocList = new ArrayList<>();

            // Generate groups
            String groupFileName = NAME_OF_GROUP + groupName;
            // write file
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(groupFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IRouteGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfGroupBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated group: " + groupName + "<<<");
            rootMap.put(groupName, groupFileName);
            docSource.put(groupName, routeDocList);
        }

4: Shortcomings

There is no need to say the advantages of ARouter After reading the complete code, you can find some shortcomings of ARouter

  1. Fragment has no way to startActivityForResult
  2. Using annotations, the code is generated in the compilation stage, and there is no incremental compilation, which slows down the compilation speed

At present, we only see these two shortcomings, and others have not been found for the time being

V What have I learned

The following two paragraphs are summarized in the new blog

1. How to use compile time annotation

2. How to traverse the class from the dex package when the application starts

Tags: Android

Posted by SharkBait on Mon, 02 May 2022 12:33:53 +0300