Welcome to juejin.cn/user/330776… ARouter official documentation: github.com/alibaba/ARo… Componentized ARouter series: juejin.cn/post/704852… Componentized ARouter series (two, cross-module API calls) : juejin.cn/post/705339… Componentized ARouter series: juejin.cn/post/705340…

I. Overview of ARouter

ARouter is a framework for componentizing Android apps — enabling routing, communication, and decoupling between modules. Typical application scenarios for ARouter are:

  1. Mapping from external URLS to internal pages, and parameter passing and parsing;
  2. Page hopping across modules, decoupling between modules;
  3. Intercept jump process, deal with logicality such as landing and burying point;
  4. Cross-module API calls, component decoupling through control inversion;

This article focuses on one of the uses of ARouter: cross-module API calls. In componentization, in order to couple each module, the general practice is to rely on the interface layer of the module instead of directly on each module.

A. ARouter B. ARouter

First, a simple example of the basic use of ARouter- cross-module API calls: In MainActivity there are two text boxes, enter a number and click the button to get the sum:

In the above example, after clicking “add”, you need to call the addition function provided by a computing module. Generally, without considering component coupling, the APP module directly relies on the computing module and calls the API of the computing module directly:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText editText1;
    private EditText editText2;
    private TextView sumTextView;
    private Button sumButton;

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.main_sum_button) {
            // Get the values of two text boxes
            int a = Integer.parseInt(editText1.getText().toString());
            int b = Integer.parseInt(editText2.getText().toString());
            The Calculate class is in the Calculate module, where the Calculate API is called directly
            Calculate calculate = new Calculate();
            intsum = calculate.sum(a, b); sumTextView.setText(Integer.toString(sum)); }}}Copy the code

The Calculate class in the Calculate module is as follows:

public class Calculate {
    public int sum(int a, int b) {
        returna + b; }}Copy the code

The simple example is that the Calculate module provides the computation function, and suppose there is another log module representing the logging function. In this decouple approach, the dependencies between the modules are as follows:

Similarly, similar to chapter 1, “Componentizing Basics ARouter(I)”, this approach can lead to serious structural coupling or cyclic dependencies as project modules become more numerous and dependencies become more complex. Here’s how ARouter is implemented.

2.1 Adding Dependencies and Configurations

apply plugin: 'kotlin-kapt'

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                / / ARouter parameters
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
    implementation 'com. Alibaba: arouter - API: 1.5.2'
    // If kotlin is used in the project, the Annotation Processor needs to be used using the kapt keyword
    // Java code can use annotationProcessor keyword
    kapt 'com. Alibaba: arouter - compiler: 1.5.2'
}
Copy the code

2.2 Abstract interface service layer

In the componentized implementation mode, we need to abstract the function of the module into an interface module, and the dependency between modules only depends on the interface layer, but not on the concrete implementation layer, so as to achieve the purpose of component coupling. The Calculate module abstracts out a lib_calculate_interface module, implemented in lib_calcualte:

There is only one interface in the lib_calculate_interface module that inherits from IProvider, as follows:

public interface CalculateService extends IProvider {
    public int sum(int a, int b);
}
Copy the code

2.3 Interface Service Implementation layer

In the implementation layer lib_calcualte, CalculateServiceImpl implements the specific functionality and annotates @route:

@Route(path = "/calculate/sum")
public class CalculateServiceImpl implements CalculateService {
    @Override
    public int sum(int a, int b) {
        return a + b;
    }

    @Override
    public void init(Context context) {}}Copy the code

2.4 Cross-module API calls

The service interface can be managed and obtained via “ARouter.getInstance().build(“/calculate/sum”).navigation()” :

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText editText1;
    private EditText editText2;
    private TextView sumTextView;
    private Button sumButton;

    // Module interface
    private CalculateService calculateService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (BuildConfig.DEBUG) {
            ARouter.openDebug();
            ARouter.openLog();
        }
        ARouter.init(getApplication());
        // Get the interface implementation layer instance in ARouter mode
        calculateService = (CalculateService) ARouter.getInstance().build("/calculate/sum").navigation();
    }

    @Override
    public void onClick(View v) {=if (v.getId() == R.id.main_sum_button) {
            int a = Integer.parseInt(editText1.getText().toString());
            int b = Integer.parseInt(editText2.getText().toString());
            // Call the implementation layer's sum method
            intsum = calculateService.sum(a, b); sumTextView.setText(Integer.toString(sum)); }}}Copy the code

In this way, in the decoupled implementation, ARouter is responsible for managing and acquiring services, and there is no direct dependency between module implementation layers:

Third, ARouter source code analysis

Here’s how ARouter implements the functionality in Section 2, using the source code.

3.1 Annotation Processing

After adding the @route annotation (path = “/calculate/sum”) in 2.3, ARouter uses the arouter-Compiler declared in Section 2.1 to process the annotation and automatically generate the code to implement Route jumps. For basic knowledge of Annotation Processor, see: Annotation Processor Simple Usage. ARouter APT to automatically generate three class file (in/lib_calculate/build/generated/source/kapt/debug/com/alibaba/android/ARouter/routes directory) :

The three class implements IRouteGroup, IRouteRoot, IProviderGroup respectively, and class names begin with ARouter $, are located in com. Alibaba. Android. ARouter. Routes under the package:

public class ARouter$$Group$$calculate implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/calculate/sum", RouteMeta.build(RouteType.PROVIDER, CalculateServiceImpl.class, "/calculate/sum"."calculate".null, -1, -2147483648)); }}public class ARouter$$Root$$lib_calculate implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("calculate", ARouter$$Group$$calculate.class); }}public class ARouter$$Providers$$lib_calculate implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.bc.impl.CalculateService", RouteMeta.build(RouteType.PROVIDER, CalculateServiceImpl.class, "/calculate/sum"."calculate".null, -1, -2147483648)); }}Copy the code

The difference between APT and “componentializing base ARouter (I) section 3.1” is that: (1) RouteType added to IRouteGroup is routeType. PROVIDER; (2) IProviderGroup has added code to register providers;

3.2 SDK Initialization

The source code for SDK initialization is similar to “Componentized Foundation ARouter (I) Section 3.1”, no further analysis. The following will only analyze the different places in logisticScenter.init (), for the module interface IProvider, with

added to HashMap(Warehouse. ProvidersIndex) :
,iprovidergroup>

public class LogisticsCenter {
    private static Context mContext;
    static ThreadPoolExecutor executor;
    private static boolean registerByPlugin;

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;
        //load by plugin first
        loadRouterMap();
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            // 1. Key code routeMap
            Set<String> routerMap;
            // It will rebuild router map every times when debuggable.
            Debug mode or PackageUtils checks whether the local route is empty or has a new version
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                / / 3. Get ROUTE_ROOT_PAKCAGE (com. Alibaba. Android. Arouter. Routes) package name all the classes
                / / arouter - the compiler automatically generated according to the annotation of class in the com. Alibaba. Android. Arouter. Routes packets
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                // 4. Create a routeMap and save it to sp. Next time read a StringSet from sp. For logic, see else branch;
                if(! routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); }// 5. Update the version of the local route
                PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
            } else {
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }
            // 6. After obtaining a routeMap, register it with the corresponding group based on the route type
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    / / 7. Load the root, the name of the class to SUFFIX_ROOT (com. Alibaba. Android. Arouter. Routes. Arouter $$root) at the beginning
                    // Add to HashMap(Warehouse. GroupsIndex) with 
      ,class>
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    / / 8. Load interceptorMeta, the name of the class to SUFFIX_INTERCEPTORS (com. Alibaba. Android. Arouter. Routes. Arouter $$Interceptors) at the beginning
                    / / to < String, IInterceptorGroup > add to UniqueKeyTreeMap (Warehouse. InterceptorsIndex); Implement sequential interception with tree structure
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    / / 9. Loading providerIndex, the name of the class to SUFFIX_PROVIDERS (com. Alibaba. Android. Arouter. Routes. Arouter $$will) at the beginning
                    // Add to HashMap(Warehouse. ProvidersIndex) with 
      ,iprovidergroup>
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }
    }
}
Copy the code

3.3 Obtaining Services

3.3.1 ARouter. The build ()

Go on to analyze the source code of _ARouter.getInstance().build() when obtaining the service. The method returns the Postcard object, which represents all the information needed for a routing operation. The general logic is similar to that of “Componentizing Basic ARouter (I) in Section 3.1”. The only difference is that the RouteType of the Postcard object is PROVIDER. When LogisticsCenter completes the Postcard information, for the Postcard object of the PROVIDER, The corresponding implementation layer instance of the IProvider is added to the Postcard object:

public class LogisticsCenter {
    public synchronized static void completion(Postcard postcard) {
        // 1. Find the RouteMeta corresponding to the Postcard's path from Warehouse. Routes
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {
            // If routeMet is empty, search from groupsIndex; Did not find it does not exist, find the dynamic add
        } else {
            // 2. Find the Postcard's corresponding RouteMeta from Warehouse. Routes and improve the Postcard information
            postcard.setType(routeMeta.getType());
            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    // 3. Check whether there are already instances of the corresponding service implementation layer in Warehouse. Providers
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            // 4. If the corresponding service instance does not exist, create one
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!"); }}// 5. Assign the Service Implementation Layer instance object to the postcard
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break; }}}}Copy the code

According to the above analysis, for the Postcard of PROVIDER type, LogisticsCenter will return the corresponding service implementation layer instance object. In particular, the static method here uses the synchronized flag. To ensure that the object fetched by multithreaded operations when calling the IProvider is the same.

3.3.2 rainfall distribution on 10-12 it. Navigation ()

After _ARouter.getInstance().build() returns the Postcard object, I will continue to analyze the navigation() method. The general logic is still similar to that of “Componentized Basic ARouter (I) section 3.3”. The only difference is that the type of Postcard is PROVIDER. I only analyze the corresponding source code here.

final class _ARouter {
    /** * Execute the corresponding routing logic */ based on the perfect Postcard
    private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();
        // 1. Execute different logic according to different routetypes; The routeType in our example is the ACTIVITY
        switch (postcard.getType()) {
            case ACTIVITY:
                / /... omit
                break;
            case PROVIDER:
                // How to handle the Postcard of the PROVIDER type is very simple. You can simply return the IProvider instance
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                / /... omit
            case METHOD:
            case SERVICE:
            default:
                return null;
        }
        return null; }}Copy the code

3.4 summarize

  1. In the idea of componentization, there is no direct dependence between modules. Each module can abstract the method provided externally into a service interface module. Each module only depends on the interface layer, but not on the implementation layer.
  2. ARouter provides the IProvider interface, which modules can inherit to expose services;
  3. ARouter provides arouter.getInstance ().build(“/calculate/sum”).navigation() to obtain the service corresponding to path;
  4. ARouter source code for the IProvider service is directly returned to the corresponding implementation layer instance object;
  5. Each IProvider instance object is stored in Warehouse. Providers, ARouter uses synchronized to ensure that the acquired objects are the same (singleton mode).

Inversion of control @autowired

In section 2.4, the service is obtained via “ARouter.getInstance().build(“/calculate/sum”).navigation()”, and you can also use the injection function provided by ARouter to implement the inversion of control:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Autowired
    private CalculateService calculateService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (BuildConfig.DEBUG) {
            ARouter.openDebug();
            ARouter.openLog();
        }
        ARouter.init(getApplication());
        // Inject a variable with the @autowired tag
        ARouter.getInstance().inject(this);
    }

    @Override
    public void onClick(View v) {=if (v.getId() == R.id.main_sum_button) {
            int a = Integer.parseInt(editText1.getText().toString());
            int b = Integer.parseInt(editText2.getText().toString());
            // Call the implementation layer's sum method
            intsum = calculateService.sum(a, b); sumTextView.setText(Integer.toString(sum)); }}}Copy the code

The principle of @autowired can be found in “componentization foundation ARouter (iii)”.