ARouter Source Analysis 1: Initialization Process

ARouter Source Analysis II: navigation Process

Arouter is simple to use

@Route(path = "/image/imageActivity")
class MainActivity : AppCompatActivity() {
    @Autowired(name = "tip")
    @JvmField
    var mTip: String? = ""
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ARouter.getInstance().inject(this)
        setContentView(R.layout.layout_activity_main)
        findViewById<TextView>(R.id.mTvImageModule).setOnClickListener {
            ARouter.getInstance().build("/login/loginActivity").navigation(this)
        }
    }
}

Use @Route to identify the group represented by the first level of the route (at least level 2/image/imageActivity). Generate files after compilation

Relevant files are generated in the build directory by annotation processor

Group

public class ARouter$$Group$$image implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/image/imageActivity", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/image/imageactivity", "image", new java.util.HashMap<String, Integer>(){{put("tip", 8); }}, -1, -2147483648));
  }
}

Our previous @Route annotation/image/imageActivity calls loadInto to load the mapping information into memory

Providers

public class ARouter$$Providers$$imagemodule implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
  }
}

Represents the defined IProvider, I do not define the IProvider interface, so these loadInto methods are empty

Root

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

This class saves the class information under the image group, which is the ARouter$Group$Image class in the image above. It can be used later to find the ARouter$Group$image class when a specific jump occurs, then call loadInto to to avoid loading all the mapped classes into memory at once during initialization.

Question 1: How does app find classes generated by different modules Arouter?

Answer: Scan the dex file, find all classes of the target package name, take a look at the initialization process, and finally go to the init of LogisticsCenter

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
	//Omit Code
	if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }
//Omit Code
}

ClassUtils.getFileNamePackageName(), which is the process of finding the target class from the dex file, if the matching criteria are
ROUTE_ ROOT_ Classes beginning with PAKCAGE package name
ROUTE_ROOT_PAKCAGE = com.alibaba.android.arouter.routes
You can see that this package name is the package name of all the classes that ARouter generated by the annotation processor. You can see the file directory under the build above. This is the package name of the generated class. Then you can see the process of looking up ClassUtil.

 public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
        final Set<String> classNames = new HashSet<>();

        List<String> paths = getSourcePaths(context);
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());

        for (final String path : paths) {
            DefaultPoolExecutor.getInstance().execute(new Runnable() {
                @Override
                public void run() {
                    DexFile dexfile = null;

                    try {
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
                            //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
                            dexfile = new DexFile(path);
                        }

                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close();
                            } catch (Throwable ignore) {
                            }
                        }

                        parserCtl.countDown();
                    }
                }
            });
        }
        //Omit Code
        return classNames;
    }

You can clearly see that this process is to iterate through the lass file in the Dex file, find all the target classes, and return to the list of target Class names

Back to LogisticsCenter's init method, see the next steps

Q2: Does ARouter load all annotation routes directly into memory?

Answer: No, ARouter started by loading grouping information, interceptors, and providers without calling the loadInto method of the ARouter$Group$XX class, using specific jump navigation, and loading registered jump information under grouping into memory via loadInto.

for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }

routerMap is the collection of class names of the target class that were previously found by looking up the dex file, traversing the collection, and calling the loadInto method of the target class through reflection.

  1. Call loadInto of ARouter$Group$xx to load group information into memory
  2. Call ARouter$Interceptors$xx to load interceptor information into memory
  3. Calling ARouter$Providers$xx to load IProvider information into memory calls are all files generated by the ARouter annotation processor

Keywords: Java Android arouter

Added by Jenk on Tue, 21 Dec 2021 16:04:08 +0200