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
- Via logisticscenter completion(postcard); To improve Postcard's information
- 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
- The route information will be found from the memory first
- If it cannot be found in memory, the corresponding group information will be found first, that is, IRouteGroup
- Once found, the corresponding route information will be loaded from IRouteGroup
- 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
- Fragment has no way to startActivityForResult
- 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