Use ARouter for componentization

ARouter is an open source project of Ali on Github. The focus of this article is not to share the use of ARouter, but to share how to componentize using ARouter. For details on its use, you can see the documentation and add groups to ask. For more information on how to write a Router to implement componentization, I recommend reading my colleague’s article on how to write an introduction to the Router framework

Advantages of componentization

  1. Decoupling enables each business module to focus on its own business implementation, and can not relate to the business of other modules.
  2. Easy to develop, in the development of multiple people, you can develop their own specific modules, in addition to the underlying modules
  3. Configurable, strong reuse, for different App, can have different modules without making big changes.
  4. Each module can run independently to facilitate development and debugging.
  5. We added

Implementation of componentized development

The project structure is:

configuration

  1. We can configure some things according to ARouter’s document, but we need to pay attention to the compile SDK for each module. The processing API SDK can be introduced in the Base SDK.
  2. The Annotation SDK doesn’t have to be introduced, it automatically relies on the introduction itself
  3. At development time, each module will have to be grouped differently, or there will be a class duplication error. The grouping is between the first “/” and the second “/” of the path.

Train of thought

We need to split the components, different modules are responsible for their own business implementation, there is no dependency of other modules. If the module needs to start another Activity or call another module method, we use the methods provided by ARouter to do so. The module diagram is as follows:

  1. App module App module is our Apk module, he is our final product. He can configure the modules he needs and do some initialization work.
  2. Module module is our business implementation module. It is only responsible for the business of this module but not for other businesses. It uses methods and interfaces provided by ARouter to call external data and provide methods for external invocation, that is, Provider in the figure.
  3. Base module About base module, you can also continue to split some fine modules, such as the router processing, but do not recommend to split too much, because the base module will change greatly, that is, in the iteration of version, constantly change, it is not very convenient to develop the split module. For example, base depends on many modules of this project. When the design goes to the bottom layer, many modules need to be changed layer by layer. The Base module should be business-neutral. In this case, the Base module manages the Router and provides the basics, such as BaseActivit, Util resources, and so on. If we have our own SDK, network library dependencies, etc., it is recommended to introduce in base.

practice

Management of the Router

We use a ModuleManager and provide a Map that uses the path of the module’s Provider as its key and value. And associated data structures for secondary encapsulation and management of ARouter’s modules. The approximate implementation is as follows:

public class ModuleManager {
    private ModuleOptions options;
    private ModuleManager() {
    }
    private static class ModuleManagerHolder {
        private static final ModuleManager instance = new ModuleManager();
    }
    public static ModuleManager getInstance() {
        return ModuleManagerHolder.instance;
    }
    public void init(ModuleOptions options) {
        if(this.options == null && options ! = null) { this.options = options; } } public ModuleOptionsgetOptions() {
        return options;
    }
    public boolean hasModule(String key) {
        returnoptions.hasModule(key); }}Copy the code

ModuleOptions specifically manages the configuration that contains those modules. This class is initialized when the App or test Module is running independently (mentioned later). Such as:

public class CustomApplication extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        initARouter();
    }
    private void initARouter() {
        if(LG.isDebug) { ARouter.openLog(); ARouter.openDebug(); ARouter.printStackTrace(); } ARouter.init(this); ModuleOptions.ModuleBuilder builder = new ModuleOptions.ModuleBuilder(this) .addModule(IHomeProvider.HOME_MAIN_SERVICE, IHomeProvider.HOME_MAIN_SERVICE) .addModule(IModule1Provider.MODULE1_MAIN_SERVICE, IModule1Provider.MODULE1_MAIN_SERVICE) .addModule(IModule2Provider.MODULE2_MAIN_SERVICE, IModule2Provider.MODULE2_MAIN_SERVICE) .addModule(IModule4Provider.MODULE4_MAIN_SERVICE, IModule4Provider.MODULE4_MAIN_SERVICE) .addModule(IModule5Provider.MODULE5_MAIN_SERVICE, IModule5Provider.MODULE5_MAIN_SERVICE); ModuleManager.getInstance().init(builder.build()); }}Copy the code

This completes the management of changing App or Module.

Management services

We use a ServiceManager to get services for different modules, called providers. Install ARouter’s document, we inherit IProvider, write a corresponding module interface, provide interface methods, in the corresponding module to implement the Provider. Then the Provider is managed and retrieved in the ServiceManager. For example, the home module implements an IHomeProvider and the implementation class is HomeProvider.

Public interface IHomeProvider extends IBaseProvider {//Service String HOME_MAIN_SERVICE ="/home/main/service"; // Open screen String HOME_ACT_SPLASH ="/home/act/splash"; // HOME home String HOME_ACT_HOME ="/home/act/home";
    String HOME_TABTYPE = "home_tab_type"; void toast(String msg); void selectedTab(Activity activity,int position); } @route (path = IHomeProvider.HOME_MAIN_SERVICE) public class HomeProvider implements IHomeProvider {private Context context; @Override public void init(Context context) { this.context = context; } @Override public void toast(String msg) { Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); } @Override public void selectedTab(Activity activity,int position) {if(activity instanceof HomeActivity) { ((HomeActivity) activity).selectedTab(position); }}}Copy the code

And then in the ServiceManager,

// Automatic injection can also be used, where the public IModule1Provider is manually discovered and calledgetModule1Provider() {
        returnmodule1Provider ! = null ? module1Provider : (module1Provider = ((IModule1Provider) MyRouter.newInstance(IModule1Provider.MODULE1_MAIN_SERVICE).navigation())); }Copy the code

We manage all the services of this Project. Then, in base, provide different services, call providers, and provide Intent methods to launch activities of different modules, such as:

// Manage Service public class HomeService {private static Boolean that calls a particular module of the ProviderhasModule() {
        return ModuleManager.getInstance().hasModule(IHomeProvider.HOME_MAIN_SERVICE);
    }
    public static void selectedTab(Activity activity, int position) {
        if(! hasModule())return; ServiceManager.getInstance().getHomeProvider().selectedTab(activity, position); }} public class HomeIntent {private static BooleanhasModule() {
        returnModuleManager.getInstance().hasModule(IHomeProvider.HOME_MAIN_SERVICE); } public static void launchHome(int tabType) { //HomeActivity MyBundle bundle = new MyBundle(); bundle.put(IHomeProvider.HOME_TABTYPE, tabType); MyRouter.newInstance(IHomeProvider.HOME_ACT_HOME) .withBundle(bundle) .navigation(); }}Copy the code

Module can run configuration independently

After these two, we are almost done componentizing the project. However, for componentization, we have another feature, that is, each module can be run independently, convenient for development and debugging. So, what should we do?

  1. We provide two environments, one is Debug, one is Release, debug, we are standalone modules that can run, release, we are library.
  2. To debug, we need to provide some test code and a set of initializers. For example, we need module1 to run independently. In this demo, it is a Fragment used as the main entry for other modules, so we need to add an Activity, a list, and some resources to our debug. We use config.gradle to manage some of our external dependency ARR and some of our build version numbers, SDK version numbers, etc., as follows:
ext { //... The version number and whether the ARR management //home is a module,trueThe time was,falseORZ isMouleDebugHome = if the ps name is not correct, it can be run independentlytrue; // Whether module1 is a module,trueThe time was,falseIsModule1Debug = can be run independentlytrue;
}
Copy the code

Then apply on the first line following build.gradle.

apply from: "config.gradle"
Copy the code

SourceSets is then used to manage the code and configure the debug and release codes. The structure of module1 is as follows

if (rootProject.ext.isModule1Debug) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion
    defaultConfig {
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode 101
        versionName "1.0.1"
        if(! rootProject.ext.isModule1Debug) { applicationId"com.github.io.liweijie.lib1"
        }
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}sourceSets {
        main {
            if(! rootProject.ext.isModule1Debug) { manifest.srcFile'src/debug/AndroidManifest.xml'
                java.srcDir 'src/debug/java/'
                res.srcDirs=['src/debug/res']}else {
                manifest.srcFile 'src/release/AndroidManifest.xml'
                java.srcDir 'src/release/java/'
            }
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com. Android. Support. Test. Espresso: espresso - core: 2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile project(':base')
    testCompile 'junit: junit: 4.12'
    annotationProcessor rootProject.ext.dependencies["aroutercompiler"]}Copy the code

The important thing to note here is that both the Debug and Relase listings need to declare the activities and other components needed. Debug should also configure Application to initialize ARouter. After this, each of our modules is a module that can run independently. Generally speaking, release is actually nothing, because release needs the business logic implementation code required by our Module itself. It is used as library, and it depends on whether relase needs to be configured in your project. In this project, modifying the values in the corresponding config.gradle allows module1 and Home to run independently or as lib additions. The final result is shown as follows:

App running independently:

App2 runs independently:

Home runs independently:

Module1 runs independently:

Suggestions for componentized development

  1. After each module we document, we should import the dependencies in arR form, upload our Maven libraries, compile them, and comment out the setting.gradle configuration to speed up compilation.
  2. Delete the test code from each module (SRC), because it contains tasks that will be executed during synchronization or compilation, slowing down compilation.
  3. The four components should be declared in their respective modules.
  4. Provide a BaseApplication in Base.

Old project componentization process practice

Recently, I was working on the company’s App. Together with my colleagues, I componentized the App I was in charge of. In this process, I would like to share some pits with you to avoid stepping on them again.

  1. Do not complete the componentization in one step, do not take too big steps, the advice is to break down the components that are relatively easy, first into modules, and then carry out business iteration, do not delay on both sides, unless you have enough time to componentize work to complete componentization.
  2. As for resource splitting, some styles, some common strings, some shared images, drawable and other resources are recommended to be stored in the Base Module, which can be shared. For resources belonging to different modules, is not recommended in the base, may be directly at first you put all the resources in the base inside would be better to do, such as don’t have to worry about compiler error, unable to find the resources, need a copy of a problem, but, you deposit in the base, the base module, general development process, If it is constantly changing, then it will be compiled frequently, which will cause a compilation speed problem, so each module should store its own resources.
  3. As for the dependency library of Base, for example, we may have our own SDK, third-party dependencies, etc., which have nothing to do with business logic, it is suggested to use a separate project for secondary encapsulation. After encapsulation, package arR dependencies, upload maven libraries of our own, and compile the Base. Or specific modules. It is not recommended to create modules directly in the app. For example, if we want to introduce volley, we should use a new project to encapsulate volley twice, and then compile arr into base or other modules after stabilization. In this way, the non-business logic is also convenient for other projects in the company.
  4. For the module split by the old project, we can temporarily not write debug, that is, we can not make the old project run independently, because it may involve too much initialization logic, and there is not enough time, so the app directly relies on running tests. For a new module, you can write debug to make the new module run independently for easy debugging.

Problems with componentization

  1. Due to the group price of the project, it may be necessary to split each business, which will lead to more modules. In the end, there may be a module with an Activity and a Fragment as a component, which will be difficult to develop. Therefore, we need to divide modules according to our actual project, and do not divide them too carefully. At the same time, in the componentization process of the old project, because each module is not mature and stable, there will be more modules compiled and the speed is relatively slow, so the separation of the old project also needs to be divided slowly according to certain steps. At the same time, it is recommended to use the method of Activity+Fragment for componentization, which is convenient for other components to use. The Activity does not involve general logic.
  2. Sometimes modify base library, need to modify the dependency mode will be more trouble.
  3. Sometimes there are resource conflicts. This is solved by adding a prefix for module resources in Gradle

ModularSample ModularSample ModularSample ModularSample