Arouter source code learning

Time:2020-2-17

Arouter source code learning

Official documents:
Android platform page routing framework

The address of Alibaba arouter GitHub is as follows:
Address of arouter GitHub

Arouter my learning notes GitHub address:
ARouter

Arouter componentized Demo:
Android_Modularization_Demo

It is strongly recommended that:Before reading the source code of arouter, carefully read the official document of arouter: the page routing framework of Android platform, arouter. By reading this document, you will have a general understanding of the implementation of arouter, which is convenient for reading the source code later.

A while ago, our project introduced arouter, mainly for the purpose ofComponent-basedandFunction decoupling。 Therefore, it is necessary to study the source code of arouter and understand its working principle.

The following is the architecture diagram given in the official document.

Arouter source code learning

Learning about the compiler part

Compiler for learning arouter source code

API part source code learning

Through the compiler learned from the source code of arouter, we know that after building the arouter project, the following files will be generated under the debug path:

Arouter source code learning

Arouter source code learning

Arouter source code learning

Arouter source code learning

Arouter source code learning

The loading of these files is done inARouter.init(getApplication());Completed during initialization.
Follow the source code to understand this process:

Arouter initialization — loading classes generated at compile time

sequenceDiagram
participant ARouter as clientA
participant _ARouter as clientB
participant LogisticsCenter as serverA

Note over clienta: trigger initialization in application
clientA->>clientB: ARouter.init(getApplication())
clientB->>serverA: _ARouter.init(application)
Servera -- > > servera: logisticscenter. Init \ n (mcontext, executor) \ n class generated when editing is loaded
Note over clienta: build class loading completed under build

ARouter.init(getApplication());

/**
 * Init, it must be call before used router.
 * <p>
 *1. Initialization is generally completed in application
 */
public static void init(Application application) {
    LogUtils.e("ARouter", "init");
    if (!hasInit) {
        // Journal
        logger = _ARouter.logger;
        //Load generated classes
        hasInit = _ARouter.init(application);
        //Load interceptor
        if (hasInit) {
            _ARouter.afterInit();
        }
    }
}

among_ARouter.init(application);Used to record the class files generated under build.

_ARouter.init(application);

/**
 *Loading classes generated under build
 *
 * @param application
 * @return
 */
protected static synchronized boolean init(Application application) {
    mContext = application;
    //Load generated classes
    LogisticsCenter.init(mContext, executor);
    //Initialization complete
    hasInit = true;
    return true;
}

This is called to the logistics center of the framework, which uses theLogisticsCenter.init(mContext, executor);Method completes the loading of the generated file class.

LogisticsCenter.init

/**
 *Initialization of basic logistics class, loading generated class
 * LogisticsCenter init, load all metas in memory. Demand initialization
 * <p>
 * <p>
 */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    // thread pool
    executor = tpe;
    try {
        long startInit = System.currentTimeMillis();
        Set<String> routerMap;
        //New version or debug package
        // It will rebuild router map every times when debuggable.
        if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
            // These class was generate by arouter-compiler.
            //Get the generated file under the path of com.alibaba.android.arouter.routes
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //Data storage
            if (!routerMap.isEmpty()) {
                context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
            }
            //Store new version number
            PackageUtils.updateVersion(context);    // Save new version name when router map update finish.
        } else {
            //Get data from sp
            routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
        }

        startInit = System.currentTimeMillis();
        //Loop all files under the path of com.alibaba.android.arouter.routes
        for (String className : routerMap) {
            // com.alibaba.android.arouter.routes.ARouter$$Root
            if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                // reflex
                //Create new arouter $$root $$app(). Loadinto();
                //
                //Load data into warehouse.groupsindex
                // routes.put("service", ARouter$$Group$$service.class);
                // routes.put("test", ARouter$$Group$$test.class);
                //
                // This one of root elements, load root.
                ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
            }
            // com.alibaba.android.arouter.routes.ARouter$$Interceptors
            else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                // reflex
                //Create new arouter $$interceptors $$app(). Loadinto (warehouse. Interceptors index)
                // interceptors.put(7, Test1Interceptor.class);
                ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
            }
            // com.alibaba.android.arouter.routes.ARouter$$Providers
            else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                // new ARouter$$Providers$$app().loadInto();
                //
                // providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
                // providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
                // providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
                ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
            }
        }
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

LogisticsCenter.init(mContext, executor);Created separatelyARouter$$Root$$app.javaARouter$$Interceptors$$app.javaARouter$$Providers$$app.javaObject and called itsloadIntoMethod to load the contents.

Come here_ARouter.init(application);Initialization is completed, and the following classes in bulid are loaded

Arouter source code learning

According to the official documents, here is“Group management, load on demand”。 The root node of each module is loaded here, but for each page under the root node, it will not be loaded temporarily.

Arouter source code learning

Official document original words:
At runtime, you need to load the mapping relationships in. When loading, we will encounter another problem, because we need to face the long-term design of app, so it is impossible to load all pages in one time. When there are 100 or hundreds of pages in app, it is very terrible for memory loss to load all pages in memory at one time, and at the same time, the performance loss cannot be ignored. Therefore, the concept of grouping is proposed in arouter. Arouter allows multiple groups under a certain module, and all groups will be managed by a root node eventually. As shown in the figure above, suppose there are four modules, each module has a root node below it, each root node will manage the group nodes in the whole module, and each group node contains all the pages under the group

Return belowARouter.init(getApplication());Method, tracking_ARouter.afterInit();Method.

Arouter initialization – load interceptor

The class load is completed under build, and the interceptor is loaded under build.

_ARouter.afterInit();

/**
 * afterInit
 * <p>
 *Used to load interceptors
 *After the class is loaded, it is called by {@ link arouter. Init}
 */
static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance()
            //Generate a postcard object
            .build("/arouter/service/interceptor")
            //Return a postcard
            //This navigation () is called several times,
            //The final call is the 65104; arouter.navigation (context, postcard, requestcode, navigationcallback) method
            .navigation();
}

Keep tracking hereARouter.getInstance().build("/arouter/service/interceptor")Method.

ARouter.build(“/arouter/service/interceptor”)

/**
 * Build the roadmap, draw a postcard.
 * <p>
 *Find the corresponding Postcard according to the path
 *
 * @param path Where you go.
 */
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

Here we call again_ARouterMediumbuild(path)Method.

_ARouter.build(“/arouter/service/interceptor”)

/**
 * Build postcard by path and default group
 * <p>
 *Process the pathreplaceservice and change the URL address
 *
 * @param path Postcard
 *@ return returns the postcard corresponding to the path address
 */
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //Navigation (clazz) is a search by type, while build (path) is a search by name
        //If the application does not implement the pathreplaceservice interface, pservice = null
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        //Change URL address
        if (null != pService) {
            path = pService.forString(path);
        }
        //Return Postcard corresponding to URL address
        return build(path, extractGroup(path));
    }
}

Called hereARouter.getInstance().navigation(PathReplaceService.class)Returned aPathReplaceServiceObject, actually loaded herePathReplaceServiceImpl

PathReplaceServiceImplFor URL address replacement requirements:

Arouter source code learning

Continue to track the code below to see how it was loaded into thePathReplaceServiceImpl

ARouter.navigation(PathReplaceService.class);

public <T> T navigation(Class<? extends T> service) {
    return _ARouter.getInstance().navigation(service);
}

Continue tracking_ARouter.getInstance().navigation(service)

_ARouter.navigation(PathReplaceService.class);

protected <T> T navigation(Class<? extends T> service) {
    LogUtils.e("_ARouter", "navigation: " + service.getSimpleName());
    Log.e("xiaxve_ARouter", "navigation: " + service.getName());
    try {
        //When servicename is pathreplaceservice, find pathreplaceserviceimpl through warehouse.providersindex
        Postcard postcard = LogisticsCenter.buildProvider(service.getName());
        // Compatible 1.0.5 compiler sdk.
        if (null == postcard) { // No service, or this service in old version.
            postcard = LogisticsCenter.buildProvider(service.getSimpleName());
        }
        LogisticsCenter.completion(postcard);
        return (T) postcard.getProvider();
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
        return null;
    }
}

Finally find the pathreplaceserviceimpl

HereLogisticsCenter.buildProvider(service.getName())
adoptWarehouse.providersIndex
findRouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/app/pathreplace", "app", null, -1, -2147483648)
Last return onenew Postcard("/app/pathreplace", "app")
Keep tracking downLogisticsCenter.completion(postcard);

LogisticsCenter.completion(new Postcard(“/app/pathreplace”, “app”));

public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
    //Warehouse.routes has not been assigned when I first came here
    //So the data returned is null
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        /**
         *Load the corresponding intra group data
         */
        //Find routes.put ("app", arouter $$group $$app. Class) through "app";
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        if (null == groupMeta) {
            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 {
                if (ARouter.debuggable()) {
                    logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                }
                // new ARouter$$Group$$app();
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                //Load content in "app" group
                //What's loaded here is
                // atlas.put("/app/pathreplace",
                //            RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/app/pathreplace", "app", null, -1, -2147483648));
                iGroupInstance.loadInto(Warehouse.routes);

                //Remove the loaded groups from the warehouse.groupsindex, and avoid adding them to the warehouse.routes repeatedly
                Warehouse.groupsIndex.remove(postcard.getGroup());
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
            //Recursive call
            completion(postcard);   // Reload
        }
    }
    //On second load, found
    // RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/app/pathreplace", "app", null, -1, -2147483648))
    else {
        //Assign value to Postcard
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        Uri rawUri = postcard.getUri();
        if (null != rawUri) {   // Try to set params into bundle.
            //... not called this time, temporarily omitted
        }

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must be implememt IProvider
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                //When called for the first time, warehouse.providers has not been assigned a value, so instance = = null
                //Parameter pathreplaceserviceimpl.class
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // There's no instance of this provider
                    IProvider provider;
                    try {
                        // new PathReplaceServiceImpl();
                        provider = providerMeta.getConstructor().newInstance();
                        // new PathReplaceServiceImpl().init(mContext);
                        provider.init(mContext);
                        //Add to warehouse.providers
                        Warehouse.providers.put(providerMeta, provider);
                        // 
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                //Save new pathreplaceserviceimpl() in postcard,
                //Therefore, the instance object of iprovider can be obtained from postcard;
                postcard.setProvider(instance);
                //Greenchannel() ignores interceptors
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                //Greenchannel() ignores interceptors
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}

critical code
This method also embodies the idea of group loading“app”Node under“/app/pathreplace”Perfect.new Postcard("/app/pathreplace", "app")Object, created by reflectionPathReplaceServiceImplObject and added tonew Postcard("/app/pathreplace", "app")in

  • 1. Pass“app”Group findroutes.put("app", ARouter$$Group$$app.class);
  • 2. Create by reflectionnew ARouter$$Group$$app()Object and loadARouter$$Group$$app()All data in the group, includingRouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/app/pathreplace", "app", null, -1, -2147483648))
  • 3. Recursive calls, creatingnew PathReplaceServiceImpl();And add toWarehouse.providersAndnew Postcard("/app/pathreplace", "app")in

Come herePathReplaceServiceWe’re done loading. Let’s go back to_ARouter.build("/arouter/service/interceptor")Continue loading the interceptor.

_ARouter.build(“/arouter/service/interceptor”)

Here we go back_ARouter.build("/arouter/service/interceptor")Method,

protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //Navigation (clazz) is a search by type, while build (path) is a search by name
        //If the application does not implement the pathreplaceservice interface, pservice = null
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        //Here, the path replaceservice is used to change the URL address
        if (null != pService) {
            path = pService.forString(path);
        }
        //Return Postcard corresponding to URL address
        return build(path, extractGroup(path));
    }
}

Here we go backnew Postcard("/arouter/service/interceptor", "group")

Return below_ARouter.afterInit()Method

_End of arouter. Afterinit() method

/**
 * afterInit
 * <p>
 *Used to load interceptors
 * <p>
 *After the class is loaded, it is called by {@ link arouter. Init}
 */
static void afterInit() {
    // Trigger interceptor init, use byName.
    interceptorService = (InterceptorService) ARouter.getInstance()
            //Generate a postcard object
            .build("/arouter/service/interceptor")
            //Return a postcard
            //This navigation () is called several times,
            //The final call is the 65104; arouter.navigation (context, postcard, requestcode, navigationcallback) method
            .navigation();
}

Here, the way we’re going to track down isnew Postcard("/arouter/service/interceptor", "group").navigation();

And this call will eventually call the_ARouterin

public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}

Will eventually returnInterceptorServiceImplInterceptor object and assign toInterceptorService interceptorService

Probably becauseInterceptorServiceImplClass exists, so in the official document, it saysArouter is bootstrapRight.

I don’t want to talk about what’s behind me. If I see what’s behind here, I don’t need to talk about it anymore. Let’s get here.