1. Why componentization?

It is quite popular in China to develop super APP, that is, I want to add all functions, which leads to more and more complicated business logic.

At this point we begin to face two problems:

  • First of all, ourresResources under folders are going to explode, and we all know itresFolders cannot be layered, they can only be pressedmoduleMake a partition, so yourlayoutandmipmapSuch folders will be the first to be persecuted, and when the resources in these two folders become large, you need to find onelayoutOr a picture can become very taxing
  • Secondly, if at this time yourAPPOr just onemoduleIt will also cause the business logic coupling to be impossible to reuse unless you have very good programming habits, which most people don’t, so we need tocomponentizationCome and give yourself someThe constraintTo create higher quality applications.

2. Componentized transformation of the project using ARouter

I particularly liked the line in ARouter’s introduction that decoupling is not a prerequisite but a process. Next I’ll show you how to componentize a project using ARouter

To componentize, you first need to create modules to separate your business logic. To create a New module, right click on your project name and then New->Module

Android Library

host
com.android.application
module
module
com.android.library
host
module

Here is a summary of my componentized directory structure for my project

DNG (project) // Project root -- host(module) // shell module ———— appglobal.java // Custom Application class ———— hostActivity. Java // Used to start the program's Activity -- Common (module) // public module ———— pr.java // a collection of all path constants ———— ttsservice. Java // interface sinking from ai module ———— utils.java // Common utility class -- Ai (Module) // Service logic module ———— speakerFragment. Java // Service logic ———— ttsServicePl. Java //TTSService implementation class -- navi(module) // Service logic module ———— navifragment. Java // service logic ———— naviViewModel. Java // Service logicCopy the code

Explain:

Starting with the Common module, this module needs to contain all the dependencies you need to use in your project and some common utility classes. After that, each module relies on the Common module, so that you can easily import the common module’s dependencies into other modules without having to repeatedly write a bunch of scripts in the build.gradle of other modules .

To use ARouter, use the API in the common module build.gradle (compile) to introduce runtime dependencies for ARrouter (the following version may not be the latest version).

    api 'com. Alibaba: arouter - API: the 1.4.1'
Copy the code

Similar to R files, we can also define a PR Java file in the common module to hold the path of all the routes used in our project

public final class PR
{

    public static final class navi
    {
        public static final String navi = "/navi/navi";
        public static final String location_service = "/navi/location";
    }

    public static final class ai
    {
        public final static String tts_service = "/ai/tts";
        public final static String asr_service = "/ai/asr";
        public final static String speaker = "/ai/speaker"; }}Copy the code

This helps us to better classify pages by module, and when other modules import the Common module, PR will also be imported, but we do not need to rely on a specific implementation of the module, we can directly reference these constants when the page jump, and centralized management.

One thing to note here is that in ARouter pages are mapped using paths, each path must have at least two levels, and the first level of each page cannot be one that has already been used by other modules.

The host module is an empty APP shell module that basically implements no business logic. It adds functionality to itself by referencing other modules in build.gradle.

    implementation project(':common')
    implementation project(':navi')
    implementation project(':ai')
Copy the code

AppGlobal is my custom Application where we need to initialize ARouter. Be careful not to make mistakes, otherwise you may not see some logs, and be sure to openDebug in Debug mode, otherwise ARouter will only scan the Dex to load the routing table the first time it runs.

public final class AppGlobal extends MultiDexApplication
{
    @Override
    public void onCreate()
    {
        super.onCreate();
        if (BuildConfig.DEBUG)
        {
            ARouter.openLog();     // Print logARouter.openDebug(); } ARouter.init(this); }}Copy the code

This is pretty much all I have in my HostActivity. As you can see, I get the singleton of ARouter, pass in the path using build reference PR, and finally call Navigation to get the fragments of other modules to add to the current Activity.

        Fragment fragment = (Fragment) ARouter.getInstance()
                .build(PR.navi.navi)
                .navigation();

        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.fragment_container, fragment, PR.ux.desktop)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit();
Copy the code

Then there is the navi module, which uses ARouter’s annotations. Remember to configure the environment for the ARouter annotation processor in build.gradle first (and also the host module if it is used).

Android {// omit... / / ARouter annotation processor launch parameters javaCompileOptions {annotationProcessorOptions {the arguments = [AROUTER_MODULE_NAME: Project.getname ()]}}} dependencies {// omit.. // Import public dependency implementation project(':common') // declare ARouter annotationProcessor annotationProcessor'com. Alibaba: arouter - compiler: 1.2.2'
}
Copy the code

We use the @route annotation in the Navi module to map pr.navi. Navi to a specific Fragment or Activity

Like this:

@Route(path = PR.navi.navi)
public class NaviFragment extends Fragment
Copy the code

Or this:

@Route(path = PR.navi.navi)
public class NaviActivity extends AppCompatActivity
Copy the code

ARouter’s approach of using path decoupling allows us to change fragments or activities that pr.navi maps to during development, with minimal impact on the caller when code changes.

Note, however, that ARouter handles different types of fragments differently, and if your path points to a Fragment, you’ll need to get the return value of your navigation and manually add it to the FragmentManager Migrating from Activity to Fragment)

        Fragment fragment = (Fragment) ARouter.getInstance()
                .build(PR.navi.navi)
                .navigation();
Copy the code

An Activity does not, and it displays immediately

Arouter.getinstance ().build(pr.navi.navi) // You can set the parameters ARouter will save in the Bundle. WithString ()"pathId", uuid.randomUuid ().toString()) //Activity or context.navigation (this);Copy the code

Navi module is a typical business logic module, here you can import some exclusive only this module can use third party SDK, such as I use the gold in the navi module map SDK, other modules need only I this module function of the map, but it should not know what the hell am I using gold or baidu or tencent map, it can improve the packaging Sex will also be much less costly in changing the concrete implementation of this module in the future.

3. Customize global interceptor, global degrade policy, and global redirection

ARouter implements routing between modules, as well as interceptors, a type of AOP(aspect oriented programming) that is typically used to deal with whether a page is logged in or not. Interceptors are executed between jumps, and multiple interceptors are executed in order of priority. By implementing the IInterceptor interface and annotating it with the @Interceptor annotation, the Interceptor is registered in ARouter.

Process method is introduced to it and InterceptorCallback, it carries the routing key information, whereas InterceptorCallback to deal with the intercept, call onContinue release, or call onInterr The UPT throws a custom exception.

Interceptors are initialized asynchronously (not on the main thread) when ARouter initializes, and if the first route occurs and an interceptor is not initialized, ARouter waits for the interceptor to complete initialization before routing.

@Interceptor(priority = 8) public class TestInterceptor implements IInterceptor { @Override public void process(Postcard  postcard, InterceptorCallback callback) { callback.onContinue(postcard); // Callback. OnInterrupt (new RuntimeException()"I think there's something unusual.")); } @override public void init(Context Context) {// Interceptor initialization, This method will be called once when ARouter is initialized, and only once}}Copy the code

When it does not arrive, we can define a degrade strategy to keep our app running. To achieve this, achieve a degradability for our interface and add @Route to the degradability kit. This degrades globally and can be customized in the onLost callback.

@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
    @Override
    public void onLost(Context context, Postcard postcard) {
        // do something.
    }

    @Override
    public void init(Context context) {

    }
}
Copy the code

Sometimes on a page we need to redirect path to another path, we can implement the PathReplaceService interface, annotate it with @route (must), and it will take effect globally. So if there is no redirection requirement, remember to return to the original path

@Route(path = "/xxx/xxx")
public class PathReplaceServiceImpl implements PathReplaceService {
    String forString(String path) {
        returnpath; // Return the result} Uri after processing according to certain rulesforUri(Uri uri) {
        returnurl; } @override public void init(Context Context) {}}Copy the code

The init method of the above three interfaces is special only when the interceptor is called. The other two methods are initialized only when the interceptor is used for the first time.

4. Interface sink -> Expose service

Sometimes what we need is not the page of another module, but the service it provides (Model layer in MVC). In this case, we need to write an interface for the service we want, and let it implement the IProvider interface, and then put it in the common module. But the implementation of the interface is still in concrete modules that are not common, such as TTSService in the Common module and TTSServiceImpl in the AI module.

This practice is known as interface to sink, in fact it is not strictly in conformity with the decoupling thought, but it is very useful, as you use the ARouter, but no one rules you can’t use startActivity, framework of the final purpose is for the convenience of our code, rather than difficult to us, what’s more, the final result each module is still Loosely coupled.

The initialization time of the service is also the first time it is used. We declare the TTSService interface in the Common module:

public interface TTSService extends IProvider
{
    void send(String text);

    void stop();
}

Copy the code

And implement it in the AI module with the @route annotation

@route (path = pr.ai.tts_service) public class TTSServiceImpl implements TTSService {//... }Copy the code

Then we can use the service in other modules

    TTSService ttsService = (TTSService) ARouter.getInstance()
                .build(PR.ai.tts_service)
                .navigation()
Copy the code

5.ContentProvider-> Application

Some third-party SDK initializations must be done in Application onCreate, but how do we initialize them if we write a module independent of host?

ARouter doesn’t provide an official solution, but in my experience, we can implement initialization by declaring the ContentProvider and registering it in the module’s AndroidManifest.

//java
public class ModuleLoader extends ContentProvider
{
    @Override
    public boolean onCreate()
    {
        Context context = getContext();
        //TODO
        return true; } / /... } //AndroidManifest <provider android:authorities="${applicationId}.navi-module-loader"
    android:exported="false"
    android:name=".app.ModuleLoader"/>
Copy the code

ContentProvider#onCreate is executed after the Application#attachBaseContext call and before the Application#onCreate call, and the Application Conte can be retrieved via getContext xt. This solves part of the problem of third-party SDK initialization.

6. How is ARouter implemented?

Simple summary is actually two knowledge points:

  • useAPTAnnotation handlers are generated from annotationsRouteMetaMetadata to the specified package
  • Boot-on scanDexUnder the specified packageclass, load and cache the routing table, and then run thenavigationIs thepathThe different types mapped to are abstracted as much as possible from the same set of interfaces

If you still want to understand ARouter, you may need to read the source code.

7. ARouter shortcomings

ARouter currently does not support multi-process development, which IS a pity for me, I hope it can be supported in the future.

8. Conclusion

So much for ARouter. If you want to learn more about ARouter’s dependency injection capabilities, go to Github.

Please give me a thumbs up if you like my article, it really means a lot to me.