What is componentization

The project is divided into several components by function. Each component is responsible for corresponding functions, such as login, pay, and live. Componentization is similar to modularization, except that modularization is business-oriented and componentization is function-oriented. Componentization is more granular and may contain multiple components in a module. In practical development, modularization and componentization are generally combined.

Why componentize

(1) Improve reusability to avoid repeated wheel construction, different projects can share the same component, improve development efficiency and reduce maintenance costs.

(2) The project is divided into components according to functions. Low coupling and high cohesion are achieved between components, which is conducive to code maintenance. Changes of one component will not affect other components.

Componentization scheme

Componentization is an idea. In the process of using componentization, the team does not have to stick to the form, but can make appropriate plans according to the size of the project and the needs of the business. The following figure is a kind of componentization structure design.

  • The host app

    In componentization, an app can be thought of as a portal, a host shell, responsible for generating the app and loading initialization operations.

  • The business layer

    Each module represents a service. Modules are isolated and decoupled from each other for easy maintenance and reuse.

  • Public layer

    Since it’s base, as the name implies, it contains the common library. Such as Basexxx, Arouter, ButterKnife, tool classes and so on

  • Base layer

    Provide basic service functions, such as picture loading, network, database, video playing, live broadcasting and so on.

Note: The above structure is just an example, the hierarchy division and hierarchy naming are not qualitative, just for a better understanding of componentization.

The problems of componentization

Jump and Route

The Activity jump is divided into display and hidden:

/ / show jump Intent Intent = new Intent (cotext, LoginActivity. Class); startActvity(intent)Copy the code
Intent = new Intent(); Intent.setclassname ("app package name ", "activity path "); Intent.setcomponent (new Component(new Component("app ", "activity path ")); startActivity(intent);Copy the code

1, display jump, direct dependence, does not meet the requirements of component decoupling isolation.

2. For implicit jump, if B is removed, abnormal crash will occur when A jumps, so we can handle it safely in the following way

Intent = new Intent(); Intent.setclassname ("app package name ", "activity path "); Intent.setcomponent (new Component(new Component("app ", "activity path ")); if (intent.resolveActivity(getPackageManager()) ! = null) { startActivity(intent); } startActivity(intent);Copy the code

Implicit jump is recommended as a native method. However, in componentalized projects, in order to realize more elegant page jump between components, routing artifact ARouter can be combined. ARouter, like a relay station, does not need to rely on through index, so as to achieve the purpose of decoupling between components.

Aouter is used as follows:

1. Since ARouter is used by all module layer components, we can introduce it in Base

API 'com. Alibaba: arouter - API: 1.5.0' annotationProcessor 'com. Alibaba: arouter - compiler: 1.2.2'Copy the code

Add to each submodule

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}
Copy the code

The annotationProcessor gets the name of the current Module through the javaCompileOptions configuration.

3. Initialize ARouter in Appliction. Since ARouter is used by all module-layer components, it is initialized in BaseAppliction.

public class BaseApplication extends Application { @Override public void onCreate() { super.onCreate(); initComponent(); initRouter(this); } /** * initialize ARouter */ private void initRouter(Application Application) {if (buildconfig.debug) { Otherwise these configurations will not be valid in the init process arouter.openlog (); // Prints the log arouter.openDebug (); // Enable debug mode (if running in InstantRun mode, debug must be enabled! } ARouter. Init (application); // As early as possible, it is recommended to initialize in Application}}Copy the code

4. Add the Route annotation to the Activity

public interface RouterPaths {
    String LOGIN_ACTIVITY = "/login/login_activity";
}
Copy the code
// Add annotations to the routing support page (mandatory) // The path needs to be at least two levels, /xx/xx @Route(path = RouterPaths.LOGIN_ACTIVITY) public class LoginActivity extends BaseActivity { }Copy the code

The first xx refers to the group. If the same group appears in different modules, an error will be reported. Therefore, it is recommended that the group be identified by the module name.

5. Initiate the forward operation

ARouter.getInstance().build(RouterPaths. LOGIN_ACTIVITY).navigation();
Copy the code

ARouter’s has many other features that won’t be covered here.

Aplication Dynamic load

Application as an entry point to the program usually does some initialization, such as the ARouter mentioned above. Since ARouter is used by all module-layer components, it is initialized in BaseApplication. If an initialization belongs only to a module, to reduce coupling, we should place the initialization in the Application of the corresponding module. As follows:

1. Define interfaces in BaseModule

public interface IBaseApplication { void init(); . }Copy the code

2. Configure it in ModuleConfig

public interface ModuleConfig {

    String LOGIN = "com.linda.login.LoginApplication";

    String DETAIL = "com.linda.detail.DetailApplication";

    String PAY = "com.linda.pay.PayApplication";

    String[] modules = {
            LOGIN, DETAIL, PAY
    };

}
Copy the code

3. BaseApplicatiion gets Application instances in each Module by reflection and calls init.

public abstract class BaseApplication extends Application implements IBaseApplication { @Override public void onCreate()  { super.onCreate(); initComponent(); initARouter(); } /** * Initialize components */ private void initComponent() {for (String module: ModuleConfig.modules) { try { Class clazz = Class.forName(module); BaseApplicationImpl baseApplication = (BaseApplicationImpl) clazz.newInstance(); baseApplication.init(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); }}}... }Copy the code

4. Init method is implemented in sub-module and related initialization operations are carried out

Public class LiveApplication extends BaseApplication {@override public void init() {// Do some live-related initializations here}}Copy the code
Intermodule communication

BroadcastReceiver: provides a system that is clunky and not very elegant to use.

EventBus: Simple and elegant to use, decouples the sender from the receiver, 2.x uses reflection mode for performance, 3.x uses annotation mode much faster than reflection.

However, there are situations that BroadcastReceiver and EventBus cannot solve, such as getting data from the detail module in the mine module. Since both detail and mine depend on base, we can use base to implement them.

1. Define the interface in base and inherit ARouter’s IProvider.

public interface IMineDataProvider extends IProvider {
    String getMineData();
}
Copy the code

2. Create the IMineDataProvider class in the Mine module and implement the getMineData method

@Route(path = RouterPaths.MINE_DATA_PROVIDER) public class MineDataProvider implements IMineDataProvider { @Override Public String getMineData() {return "*** received data from mine module ***"; } @Override public void init(Context context) { } }Copy the code

3. Get the MineDataProvider instance in detail and call the methods defined in the IMineDataProvider interface

IMineDataProvider mineDataProvider = (IMineDataProvider) ARouter.getInstance().build(RouterPaths.MINE_DATA_PROVIDER).navigation(); if (mineDataProvider ! = null) { mGetMineData.setText(mineDataProvider.getMineData()); }Copy the code
Resource conflict

There are many modules in a componentized project, and it is inevitable that the resources in the modules will be named the same, resulting in reference errors. To do this, we can do the following configuration in each module’s build.gradle file (for example, the login module).

resourcePrefix "login_"
Copy the code

All resources must be prefixed with the specified string (suggested Module name), otherwise an error will be reported. However, this method is limited to XML files and does not work with image resources, which still need to be manually modified.

// Layout file naming example login_activity_login.xmlCopy the code
<resources> <! <string name="login_app_name">Login</string> </resources>Copy the code
A single component runs debugging

As projects grow larger, it can take a long time to compile or run once, and componentization can be configured to debug each module individually, greatly improving development efficiency. We need to configure each module as follows:

1. Create a common_config.gradle file in the project root directory and declare the variable isModuleDebug.

Ext {// Whether module is allowed to debug isModuleDebug = false}Copy the code

Add common_config to the build.gradle file of the module. Since each module is a library in componentization, you need to change library to Application to run debugging separately.

// add common_config to apply from: "${rootProject.rootDir}/common_config.gradle" if (project.ext.isModuleDebug.toBoolean()) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' }Copy the code
Android {defaultConfig {the if (project. Ext isModuleDebug. ToBoolean ()) {/ / separate debugging when you need to add applicationId applicationId "com.linda.login" } ... } sourceSets {main {// Create the manifest directory and AndroidManifest file in the SRC /main directory of the module that needs to be debugged separately // Use different Androidmanifest.xml for separate debugging and integrated debugging File the if (project. Ext isModuleDebug. ToBoolean ()) {manifest. SrcFile 'SRC/main/manifest/AndroidManifest. XML'} else { manifest.srcFile 'src/main/AndroidManifest.xml' } } } }Copy the code

The differences between the two manifest files are as follows:

<! - separate debugging - > < manifest XMLNS: android = "http://schemas.android.com/apk/res/android" package = "com. Linda. Login" > < application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/login_app_name" android:supportsRtl="true" android:theme="@style/base_AppTheme"> <activity android:name=".ui.LoginActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>Copy the code
<! - integrated debug - > < manifest XMLNS: android = "http://schemas.android.com/apk/res/android" package = "com. Linda. Login" > < application android:allowBackup="true" android:label="@string/login_app_name" android:supportsRtl="true" android:theme="@style/base_AppTheme"> <activity android:name=".ui.LoginActivity" /> </application> </manifest>Copy the code

3. If Module is debugged separately, you can no longer rely on this Module in app, because both App and Module are projects and projects cannot depend on each other. Make the following changes in app build.gradle file

dependencies { if (! project.ext.isModuleDebug) { implementation project(path: ':detail') implementation project(path: ':login') implementation project(path: ':pay') } implementation project(path: ':main') implementation project(path: ':home') implementation project(path: ':mine') }Copy the code

4. Finally, change isModuleDebug to true and compile to see that the login, detail and Pay modules can run debugging independently.

Componentized Demo download address

Related blog

Kotlin componentization Practice — Imitation open eye short video App