Welcome to follow the official wechat account: FSA Full stack action 👋

I. Functional requirements

1. Component decoupling (ARouter)

ARouter is a useful library for componentized development. Its core function is component decoupling. For example, when you want to jump to another Activity, you will use the following code:

val intent = Intent(this, OtherActivity::class.java)
startActivity(intent)
Copy the code

Such code implies strong coupling. The current Activity code level refers directly to OtherActivity, but when a project uses modular development (modules do not depend directly on each other) or multi-channel variants (different channels specify different SRC directories), Will cause the OtherActivity to be unable to reference, in which case the project will report an error. With ARouter, the above code can be changed to the following code:

ARouter.getInstance().build("/module2/other").navigation()

@Route(path = "/module2/other")
class OtherActivity : BaseActivity() {... }Copy the code

It is clear that with ARouter there are no code-level references between activities, which is component decoupling. ARouter also supports fragments. For more information, please check the official document: github.com/alibaba/ARo…

2. RePlugin

The architectural design of the project involves plug-in, and we use RePlugin. Of course, each company may use RePlugin in a different way, which can be divided into the following two categories:

  • Method 1: The host is the main APP, which contains the main business functions, and only some small functions need to be dynamically updated through plug-ins.
  • Mode two: the host is the vest package, the plug-in is the main APP, the host APK only one thing: update the main APP plug-in.

Here we use RePlugin based on method 2, where the main app is a plug-in, involves a lot of business logic, and the project uses multiple channel variants (different channels specify different SRC directories), so we need to use ARouter in the plug-in, where the host function is simple. ARouter is not needed.

> > > > > > > > > > > > > > > > > > > note: the following all the analysis based on the < < < < < < < < < < < < < < < < < < < <

Second, problems and solutions

(1) There’s no route matched! (2) There’s no route matched! Path = [/arouter/service/interceptor] Group = [arouter]

1. ARouter failed to initialize

There are also many issues mentioned in ARouter’s issue that there is a problem with matching with plug-in frameworks. The official response is that plug-in frameworks such as RePlugin are not well supported.

  • Github.com/alibaba/ARo…
  • Github.com/alibaba/ARo…
  • Github.com/alibaba/ARo…

The following is ARouter’s log output when app was either a single product or a plug-in:

/ / > > > > > > > > > > > > > > > > > > > item log output I/ARouter: : : ARouter openLog [] I/ARouter: : : ARouter openDebug [] I/ARouter: : : ARouter init start.[ ] I/ARouter::: Run with debug mode or new install, rebuild router map.[ ] I/ARouter::: VM with name 'Android' has multidex support E/ARouter::: InstantRun support error, com.android.tools.fd.runtime.Paths I/ARouter::: Thread production, name is [ARouter task pool No.1, thread No.1][ ] D/ARouter::: Filter 6 classes by packageName <com.alibaba.android.arouter.routes> I/ARouter::: Find router map finished, map size = 6, cost 43 ms.[ ] I/ARouter::: Load root element finished, cost 4 ms.[ ] D/ARouter::: LogisticsCenter has already been loaded, GroupIndex[2], InterceptorIndex[0], ProviderIndex[2][ ] I/ARouter::: ARouter init success! [ ] D/ARouter::: The group [arouter] starts loading, trigger by [/arouter/service/interceptor][ ] D/ARouter::: The group [arouter] has already been loaded, trigger by [/arouter/service/interceptor][ ] I/ARouter::: Thread production, name is [ARouter task pool No.1, thread No.2][ ] I/ARouter::: ARouter init over. [] / / > > > > > > > > > > > > > > > > > > > plug-in log output I/ARouter: : : ARouter openLog [] I/ARouter: : : ARouter openDebug[ ] I/ARouter::: ARouter init start.[ ] I/ARouter::: Run with debug mode or new install, rebuild router map.[ ] I/ARouter::: VM with name 'Android' has multidex support E/ARouter::: InstantRun support error, com.android.tools.fd.runtime.Paths I/ARouter::: Thread production, name is [ARouter task pool No.1, thread No.1][ ] D/ARouter::: Filter 0 classes by packageName <com.alibaba.android.arouter.routes> I/ARouter::: Find router map finished, map size = 0, cost 14 ms.[ ] I/ARouter::: Load root element finished, cost 0 ms.[ ] E/ARouter::: No mapping files were found, check your configuration please! [ ] D/ARouter::: LogisticsCenter has already been loaded, GroupIndex[0], InterceptorIndex[0], ProviderIndex[0][ ] I/ARouter::: ARouter init success! [ ] W/ARouter::: ARouter::There is no route match the path [/arouter/service/interceptor], in group [arouter][ ] I/ARouter::: ARouter init over.[ ]Copy the code

Find router Map Finished (map size = 0); ARouter initialization failed.

If ARouter fails to initialize, you should start with the initialization entry ARouter. Init (application)

public final class ARouter {
    public static void init(Application application) {
        if(! hasInit) { hasInit = _ARouter.init(application); . }}}final class _ARouter {
    protected static synchronized boolean init(Application application) { LogisticsCenter.init(mContext, executor); .return true; }}Copy the code

Logisticscenter.init (mContext, executor) logisticScenter.init (mContext, executor)

public class LogisticsCenter {
    /** * LogisticsCenter init, load all metas in memory. Demand initialization */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {

        // Step1. Get all classes of app
        Set<String> routerMap;
        // It will rebuild router map every times when debuggable.
        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>()));
        }

        logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");

        // Step2. Load the route configuration and interceptor related classes
        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); }}... }}Copy the code

The core of this code is the final for loop, which basically means filtering out specific classes related to ARouter by judging the class name rules, such as routing grouping information related classes, interceptor related classes, And loading into the Warehouse respectively. GroupsIndex and Warehouse. InterceptorsIndex, but it is not the key to the problem.

Add-on: ARouter’s project source code contains two additional modules, ARouter – Annotation and ARouter – Compiler, which are the core technologies for dependency injection and component decoupling using ARouter: Compile-time annotations + JavaPoet. As far as component decoupling is concerned, ARouter uses compile-time annotations to capture classes with @Route annotations during project compilation, and then uses JavaPoet to generate class files that describe the mapping between path and components (and the parameters).

According to the output log information, routerMap can know the number of elements in the 0, while routerMap is through ClassUtils getFileNameByPackageName (mContext ROUTE_ROOT_PAKCAGE), ClassUtils:

public class ClassUtils {
    /** * Scan all classnames ** contained under the package by specifying the package name@param context     U know
     * @paramPackageName package name *@returnThe set of all classes */
    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(a) {
                    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(); }}}); } parserCtl.await(); Log.d(Consts.TAG,"Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }


    /**
     * get all the dex path
     *
     * @param context the application context
     * @return all the dex path
     * @throws PackageManager.NameNotFoundException
     * @throws IOException
     */
    public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        File sourceApk = new File(applicationInfo.sourceDir);

        List<String> sourcePaths = new ArrayList<>();
        sourcePaths.add(applicationInfo.sourceDir); //add the default apk path

        //the prefix of extracted file, ie: test.classes
        String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;

// If the VM already supports MultiDex, do not go to the Secondary Folder to load classesx.zip, it is not already there
// Whether there is multidex.version in sp is not accurate, because users who upgrade from a lower version include this sp configuration
        if(! isVMMultidexCapable()) {//the total dex numbers
            int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
            File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

            for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
                //for each dex file, ie: test.classes2.zip, test.classes3.zip...
                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
                File extractedFile = new File(dexDir, fileName);
                if (extractedFile.isFile()) {
                    sourcePaths.add(extractedFile.getAbsolutePath());
                    //we ignore the verify zip part
                } else {
                    throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'"); }}}if (ARouter.debuggable()) { // Search instant run support only debuggable
            sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
        }
        returnsourcePaths; }}Copy the code

The ClassUtils getFileNameByPackageName() and getSourcePaths() codes are so important that I copied them in their original form. Obtain the path of all dex files extracted during apK installation through the package name, and extract all the classes by loading the dex file. Then go back to the last for loop in logisticScenter.init (mContext, executor) to load all the routes and interceptor configuration. So the question is:

Q: whyrouterMapThe number of elements in is zero, right? Or, why can’t the dex file extract the class?

A: The dex file isn’t in the normal directory, so ARouter won’t be able to get the dex file at all, let alone load the route and interceptor configuration.

Github.com/Qihoo360/Re…

Q: How do you get ARouter to load the dex file of the plugin?

A: It is theoretically possible to append routing configuration information to ARouter’s Warehouse. GroupsIndex via reflection. Or change the ARouter code to logisticScenter.init (mContext, executor) and insert the code to get all the classes in the current plug-in dex file.

Q: Does that make ARouter work in plugins?

A: In theory, yes. However, the Activity may still fail to jump.

2. The Activity jump fails

The key method for routing ARouter components is navigation(), and all overloaded navigation() methods end up with the _ARouter#_navigation() method.

final class _ARouter {

    private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (0! = flags) { intent.setFlags(flags); }// Non activity, need FLAG_ACTIVITY_NEW_TASK
                if(! (currentContextinstanceof Activity)) {
                    intent.addFlags(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(a) { startActivity(requestCode, currentContext, intent, postcard, callback); }});break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            caseFRAGMENT: Class<? > fragmentMeta = postcard.getDestination();try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null; }}Copy the code

_ARouter#startActivity() = _ARouter#startActivity();

final class _ARouter {
    /**
     * Start activity
     *
     * @see ActivityCompat
     */
    private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  // Need start for result
            if (currentContext instanceof Activity) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
            } else {
                logger.warning(Consts.TAG, "Must use [navigation(activity, ...)]  to support [startActivityForResult]"); }}else {
            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
        }

        if ((-1! = postcard.getEnterAnim() && -1! = postcard.getExitAnim()) && currentContextinstanceof Activity) {    // Old version.
            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
        }

        if (null! = callback) {// Navigation over.callback.onArrival(postcard); }}}Copy the code

Can see _ARouter # startActivity () is through ActivityCompat startActivity () to start the Activity, it is very critical, plug-in for the Activity, must be started by the context of the plugin, So who is the currentContext here? The following source code can be located via a call to Postcard#setContext() :

final class _ARouter {
    private static Context mContext;
    protected static synchronized boolean init(Application application) { mContext = application; .return true;
    }
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        // Set context to postcard.
        postcard.setContext(null== context ? mContext : context); . }}Copy the code

That is, when calling _ARouter#navigation(), use the external context if there is an incoming context, otherwise use application, The _ARouter application comes from the application passed in by ARouter#init(Application) when the framework is initialized. Ok, now the question is, is this application host or plug-in?

Consider only the case as a plug-in, because as a single item there is no such thing as a host plug-in.

Scenario 1: Initialize ARouter in a custom Application

public class MyApplication extends Application {

    @Override
    public void onCreate(a) {
        super.onCreate();
        ARouter.init(this); // This is the application of the plugin}}Copy the code
  • This in a custom Application is the Application of the plug-in.

Scenario 2: Initialize ARouter in the Activity

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ARouter.init(this.getApplication()); / / host application
        ARouter.init(((Application) this.getApplicationContext())); / / application}}Copy the code
  • activity.getApplication(): Get the application of the host
  • activity.getApplicationContext(): Get the application of the plugin

Related issue:

  • Github.com/Qihoo360/Re…
  • Github.com/Qihoo360/Re…

The main cause of an Activity jump failure is the use of the host context, which occurs when the ARouter is initialized in a non-custom Application. There are two solutions to this problem:

If you’re initializing ARouter in a custom Application, you don’t need to look at this.

Scenario 1: Use in non-custom applicationsapplicationContextInitialize ARouter:

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ARouter.init(((Application) this.getApplicationContext())); }}Copy the code

Scenario 2: Use the context parameternavigation(Context)Method to perform routing operations:

public class MyActivity extends AppCompatActivity implements View.OnClickListener {

    private Button button;

    @Override
    public void onClick(View view) {
        ARouter.getInstance().build("/module2/other").navigation(this); }}Copy the code

Third, LiteARouter

In order for ARouter to initialize properly in RePlugin, there are two options:

  • Reflection: Adds route configuration information to the ARouter by reflectionWarehouse.groupsIndex, the interceptorWarehouse.interceptorsIndexAnd the IProvider serviceWarehouse.providersIndex.
  • Modify: modify the code for ARouter inLogisticsCenter.init(mContext, executor)Insert code to get all the classes in the current plug-in dex file.

The above two schemes have their own advantages and disadvantages. Personally, I think the transformation method should be better. However, I did not completely deal with it in the above way. It is not completely guaranteed that other functions except routing can be used normally. On the other hand, the development time is seriously insufficient due to the tight time and heavy task of the project, and we only need to use the routing function. So, what I did was transform the ARouter, keep only the routing functionality, and call it LiteARouter.

  • LiteARouter’s Git repository: github.com/GitLqr/Lite…

1. Arouter – Compiler analysis

By analyzing the arouter – compiler code, combining jadx decompiling, can know the final will be com. Alibaba. Android. Arouter. Routes packets generated under arouter $$$$XXX Root class, such as:

package com.alibaba.android.arouter.routes;

import com.alibaba.android.arouter.facade.template.IRouteGroup;
import com.alibaba.android.arouter.facade.template.IRouteRoot;
import java.util.Map;

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

Substance is the name of my main program Module, usually app, which is the value of AROUTER_MODULE_NAME in gradle file.

ARouter$$Root$$XXX loadInto(Map

> Routes) method stores the route information into Routes, which is Warehouse. GroupsIndex. ARouter$$Group$$XXX generates the actual routing information in the ARouter$$Group$$XXX class.
,>

package com.alibaba.android.arouter.routes;

import com.alibaba.android.arouter.facade.enums.RouteType;
import com.alibaba.android.arouter.facade.model.RouteMeta;
import com.alibaba.android.arouter.facade.template.IRouteGroup;
import com.charylin.substance.screen.main.MainActivity;
import java.util.Map;

public class ARouter$$Group$$substance implements IRouteGroup {
    public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/substance/main", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/substance/main"."substance", (Map<String, Integer>) null, -1, Integer.MIN_VALUE)); }}Copy the code

ARouter$$Group$$XXX loadInto(Map

atlas) stores routes into atlas, which is Warehouse. Routes.
,>

2. LogisticsCenter analysis

Now go back to the last for loop in logisticScenter.init (mContext, executor) to see how it loads the route configuration:

public class LogisticsCenter {
    /** * LogisticsCenter init, load all metas in memory. Demand initialization */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {...// Step2. Load the route configuration and interceptor related classes
        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); }}... }}Copy the code

ARouter$$Interceptors$$XXX, ARouter$$Providers$$XXX, ARouter$$Providers$$XXX, ARouter$$Providers$$XXX, ARouter$$Providers$$XXX How come there is no warehouse.routes? In fact, LogisticsCenter has an important completion(Postcard) method. It is mainly about completing the Postcard’s information. The following is the source code of the completion(Postcard) method:

public class LogisticsCenter {

    /**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should complete by this method.
     */
    public synchronized static void completion(Postcard postcard) {
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

        if (null == routeMeta) {
            // Maybe its does't exist, or didn't load.
            if(! Warehouse.groupsIndex.containsKey(postcard.getGroup())) {throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {

                // Load route and cache it into memory, then delete from metas.
                try {
                    addRouteGroupDynamic(postcard.getGroup(), null);
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                completion(postcard);   // Reload}}... }public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (Warehouse.groupsIndex.containsKey(groupName)){
            // If this group is included, but it has not been loaded
            // load this group first, because dynamic route has high priority.
            Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
            Warehouse.groupsIndex.remove(groupName);
        }

        // cover old group.
        if (null! = group) { group.loadInto(Warehouse.routes); }}}Copy the code

The approximate logic of the source code of LogisticsCenter#completion(Postcard) is that if the route path of the Postcard cannot be retrieved from Warehouse. Routes, the route information is missing. The routing configuration of the Postcard will be dynamically loaded according to its routing group (packet information) and saved in Warehouse. Routes. Therefore, the specific routeMeta in Warehouse.

3, LogisticsCenter transformation

In conclusion, because Warehouse. Routes will dynamically load the fill in the LogisticsCenter#completion(Postcard) method, I just need to load the route into Warehouse. GroupsIndex by reflection, so I modified LogisticsCenter#loadRouterMap() :

public class LogisticsCenter {

    private static Context sContext;
    private static ThreadPoolExecutor sExecutor;

    private LogisticsCenter(a) {}public synchronized static void init(Context context, ThreadPoolExecutor tpe) {
        loadRouterMap();
    }

    private static void loadRouterMap(a) {
        try {
            String nameOfRouteRootClass = Consts.NAME_OF_ROUTE_ROOT_CLASS; // com.charylin.litearouter.routes.LiteARouter$$RootClass<? > routeRootClz = Class.forName(nameOfRouteRootClass);if(routeRootClz ! =null) {
                IRouteRoot routeGroup = routeRootClz.asSubclass(IRouteRoot.class).newInstance();
                if(routeGroup ! =null) { routeGroup.loadInto(Warehouse.groupsIndex); }}}catch (Exception e) {
            LiteARouter.logger.error(Consts.TAG, "load router map error.", e); }}}public final class Consts {
    public static final String SEPARATOR = "$$";
    public static final String PROJECT = "LiteARouter";
    public static final String TAG = PROJECT + "... "";
    public static final String NAME_OF_ROOT = PROJECT + SEPARATOR + "Root";
    public static final String PACKAGE_OF_GENERATE_FILE = "com.charylin.litearouter.routes";
    public static final String NAME_OF_ROUTE_ROOT_CLASS = PACKAGE_OF_GENERATE_FILE + '. ' + NAME_OF_ROOT;
}
Copy the code

To keep reflection simple, I changed the name of the ARouter$$Root$$XXX class to LiteARouter$$Root, that is, no module name appended to the class name. That is, AROUTER_MODULE_NAME does not need to be configured in gradle file.

4. Transformation of Arouter – Compiler

Since the design of the routing class name (ARouter$$Root$$XXX) has changed, the generated class name rule should also be modified in compiler:


@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE})
public class RouteProcessor extends BaseProcessor {

    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {...// Write root meta into disk.
        String rootFileName = NAME_OF_ROOT; // String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(rootFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT))) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfRootBuilder.build()) .build() ).build().writeTo(mFiler); }}Copy the code

Because only the routing function needs to be preserved, the generated class logic corresponding to unnecessary functions such as interceptors and dependency injection is stripped away.

So far, these are the problems AND solutions I found when dealing with RePlugin+ARouter. The above is only the most important part of the transformation of ARouter. More details can be found by comparing the differences between the various files of LiteARouter and ARouter.