1 introduction

1.1 What is componentization?

Componentalization simple generalization is to divide a fully functional App or module into multiple sub-modules, each of which can be independently compiled and run, or arbitrarily combined into another new App or module. Each module is not interdependent but can interact with each other, and can even be upgraded or degraded in some special cases

1.2 Why componentization?

Now the project with the increase of demand scale is becoming more and more big, the increase of scale has led to a lot of trouble, all kinds of business in the wrong complex intertwined, between each business module, code without constraint, code boundary of fuzzy, code conflict occurs frequently, change one small problem may cause some new problems, social phenomena, The emergence of componentization is to solve the above problems under the circumstance of adding a new requirement, getting familiar with a large number of code written by predecessors, constantly increasing compilation time and extremely decreasing development efficiency

1.3 Analysis of existing componentization schemes

The componentization scheme of many large factories is based on multi-engineering + multi-module structure (wechat, Meituan and other super apps is a three-level engineering structure of multi-engineering + multi-module + multi-P project (code isolation mode with page as unit). Use Git Submodule to create multiple sub-repositories to manage the code of each module, and package the code of each module as AN AAR and upload it to a private Maven repository. Use remote version number dependence to isolate the code between modules

1.4 How to Select a componentization Scheme?

According to Mr. Conway’s law, the design of the system architecture need according to the communication between the organization structure, because now most of the project’s size and the number of developers as well as the structure is not enough to need some big release componentization plan support (company’s organizational structure and project size is very huge, the solutions they may not completely suitable for all the company’s project). Stricter finer-grained code and the isolation between modules, blindly use some componentization plan, may bring development efficiency is lower, development costs outweigh the benefits, etc., lower cost performance, as a project leader, should according to the size of the project currently and the organizational structure of the developer to choose the most suitable componentized solution, Make the technical plan according to the actual situation of the project, rather than blindly follow the technical plan of some big factories so that the project and developers spend a lot of time to adjust and adapt

2 Component scheme description

ArmsComponent currently adopts the structure of single project and multiple modules. Since the Demo is small and only used to show the basic specifications, it only relies on the source code instead of the remote version number. The code management is also based on single repository and multiple branches. This is also a more efficient solution for early development when the project size is small and the number of developers is small. If your project size is large and the number of developers is large, you can adopt the multiple projects + Multiple Modules mentioned above and use the private Maven repository management component version

No program in the world is perfect enough to accommodate all situations and meet the needs of all people and all projects, so the project leader must make flexible adjustments according to the actual situation of the project to make the most suitable program for his own project

2.1 Architecture Diagram overview

ArmsComponent architecture diagram

2.2 Detailed explanation of architecture diagram

At present, the architecture is divided into three layers, which are the basic layer, the business layer and the host layer in descending order. Due to the small size of the project, the three layers are all concentrated in one project, but you can divide them into multiple projects for collaborative development according to the scale of the project and the number of developers

2.2.1 host layer

The host layer, located at the top layer, is mainly used as an App shell to assemble required modules into a complete App. This layer can manage the entire App life cycle (such as the initialization of Application and various components and third-party libraries).

2.2.2 business layer

The business layer is located in the middle layer, which is mainly divided into business modules based on business requirements and application scenarios. Each module is independent of each other, but can interact with each other. For example, a shopping App is composed of business modules such as search, order, shopping cart and payment

Tips: Each business module can have its own SDK dependencies and its own UI resources (if the SDK dependencies and UI resources are common to other business modules, they can be separated from each other
The SDK (CommonSDK 2.2.3.3) 和
UI Components (CommonRes 2.2.3.3.2)C)

2.2.2.1 Service module splitting

Don’t rush to knock before you write business code, should according to the product demand in the early to late operation planning combined with clear comb the development of the business may happen in the future, to determine the boundary between business, and is likely to change, and then decide really need to split the business module and then to break up

2.2.3 base layer

The basic layer is located at the bottom layer, which includes core basic business module, public service module and basic SDK module. The core basic business module and public service module mainly serve each module of the business layer. The basic SDK module contains various powerful team self-encapsulated SDK and third-party SDK. Provide power for the infrastructure construction of the entire platform

2.2.3.1 Core basic business

Core based on the business layer of each business module provides the basis of some related to business services, such as in the project is divided into two ports, user roles users can play multiple roles, but can only be online at the same time operating a port business, then each port must provide a role switching function, In order to enable users to switch between multiple roles at any time, a management class for users to switch roles freely should be provided in the project as the core basic business, which is relied on by these two ports (apps such as Pull hook and Boss Zhipin can switch between recruiters and applicants).

The division of core basic services should be based on whether the basic services required by most modules of the business layer and some services that need to interact with each business module can be divided into core basic services

2.2.3.2 Public services

The CommonService is a Module named CommonService, which is used for the interaction between modules at the business layer (custom method and class invocation), contains custom Service interfaces, and custom classes that can be passed across modules

The main process is:

Business modules that provide services:

In public service (
CommonService) in the statement
ServiceInterface (with custom methods that need to be called), and then implement this in your own module
ServiceInterface, and pass again
ARouter APIExposed implementation class

Business modules that use services:

through
ARouter
APIHe got the
ServiceInterface (polymorphic holding, actually holding implementation class), can be called
ServiceCustom methods declared in the interface so that interaction between modules can be achieved

Custom classes passed across modules:

Once you have defined custom classes in a public Service that need to be passed across modules (custom methods in Service and event entity classes in EventBus might need custom classes), you can use the ARouter API, Pass the custom object across modules between modules’ pages (ARouter requires that passing the custom object implement the SerializationService interface)

Tips: It is recommended to create a separate package in CommonService for each business module that needs to provide a Service, and then put the Service interface and custom classes that need to be passed across modules in this package for better management

The best way to understand the usage of the public service layer is to understand ARouter’s API

Click to view the ARouter document

2.2.3.3 base SDK

The base SDK is a Module called CommonSDK, which contains a number of powerful SDKS for all modules throughout the architecture

2.2.3.3.1 MVPArms

MVPArms is the most important module in the whole base layer, which can be described as the heart of the whole componentized architecture. It provides a complete set of APIS and SDKS necessary for the development of a complete project, and is the scaffolding of the whole project. I use it to unify the infrastructure of the whole componentized solution and make each module more robust. With MVPArms, ArmsComponent is the only componentization solution that provides a complete infrastructure, so learning MVPArms is a must before learning ArmsComponent

learning
MVPArmsPlease study in the following order:

1. Click learn Demo

2. Click for detailed documentation

3. Click to download the one-click code generation plug-in

2.2.3.3.2 UI components

The UI component in the basic SDK is a Module named CommonRes, which mainly places some UI-related resources that can be used by all business layer modules to facilitate reuse, management and specification of existing resources

Tips: It is worth noting that if some modules in the business layer have the same resource name (for example, two images have the same name), the problem of resource conflict will occur when integrating all modules in the host layer. This problem can be solved by using the resourcePrefix tag in build.gradle of each module to add a different prefix to the resource name of each module

android {
    defaultConfig {
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        ...
    }
    resourcePrefix "public_"
}Copy the code

The types of resources that can be placed are:

  • Common Style, Theme
  • The general Layout
  • Universal Color, Dimen, String
  • General Shape, Selector, Interpolator
  • Universal image resource
  • General purpose animation resources
  • Generic custom View
  • Generic third party custom View

2.2.3.3.3 other SDK

Other SDKS are mainly third-party libraries and THIRD-PARTY SDKS (such as ARouter, Tencent X5 kernel) that are common to some business layers depending on the basic SDK, facilitating reuse, management and standardization of existing SDK dependencies

2.3 Cross-component communication

2.3.1 Why is cross-component communication needed?

Because each service module is independent and does not depend on each other, A service module cannot access the code of other service modules. If you want to jump from page A of service module A to page B of service module B, it cannot be realized by the module itself. So you must rely on other external media to provide services for cross-component communication

2.3.2 Cross-component Communication Scenario

There are two scenarios for cross-component communication:

  • The first is page hopping between components (Activity to Activity, Fragment to Fragment, Activity to Fragment, Fragment to Activity) and data passing when jumping (base data types and serializable custom class types)

  • The second is the invocation of custom classes and custom methods between components (components provide services externally)

2.3.3 Cross-component communication solution

In fact, the above two communication scenarios and even other higher-order functions have been implemented in ARouter. ARouter is an Android routing middleware of Alibaba open source, which can meet many requirements of componentization and is also an important part of this scheme, so you need to read the documents carefully. Get to know the basics

As for cross-component communication framework, this scheme does not do encapsulation, and professional work is entrusted to professional people. There are many excellent frameworks with their own characteristics. You can choose the framework you like according to your needs, not necessarily ARouter, which is also produced by Alibaba. Open source has a long time and a large number of users. It is relatively stable and easy to communicate when problems arise

2.3.4 Analysis of Cross-component communication schemes

If you want to transfer different types of data to ARouter, there is also a corresponding API. (To transfer custom objects, you need to implement SerializationService. Refer to the ARouter documentation for details);

The second type of invocation of custom classes and custom methods between components is a bit more complicated and requires ARouter to work with the CommonService in the architecture. The main process is described in the CommonService (2.2.3.2) and I have drawn a diagram here to make it easier to understand

Diagram of cross-component communication

This mode of service provision is called interface sinking. Please refer to the main process in public Services (2.2.3.2) for easy understanding while looking at the picture

This solution also provides another way to provide EventBus as a service. As we all know, because of the decoupled nature of EventBus, if abused, it will make the project call hierarchy chaotic, which is not easy to maintain and debug. Therefore, this scheme uses AndroidEventBus’s unique Tag, which can make it easier to locate the codes that send and receive events during development. If the component name is used as the prefix of Tag to group, it can also better manage and view the events of each component in a unified way. Using EventBus too much is not recommended either

Tips: Each cross-component communication framework provides services differently, and you can choose from other frameworks

2.3.5 Passing complex data formats across components

In general, basic data types are sufficient to pass data across most components, but complex custom data types can be passed in some cases. There are two ways to pass custom types in the solution:

The first, described in public service (2.2.3.2), is to define this custom class in CommonService

The second method is also simpler and can be passed directly by parsing the Json string

2.4 Component life cycle

Each component (module) can run independently during the test phase, and each component can specify its own Application during the independent run, so it is easy for the component to manage its own life cycle. For example, it is easy to initialize some code in onCreate. However, when entering the integration debugging phase, What if each component needs to initialize its own code when its own Application is unavailable and each component depends only on the lifecycle of the host?

2.4.1 Problem Analysis

During integration debugging, the host depends on all components, but each component cannot depend on the host, which means that each component does not know who its host is, and of course cannot directly call the host methods by accessing the code to add its own logical code to the lifecycle of the host

Copying each module’s initialization code directly into the host’s lifecycle is too violent. Not only is the code coupling not extensible, but the code is also prone to conflicts, so modifying the host’s source code is not feasible

So is there a way for each component to manage its own life cycle independently during integration debugging?

The solution is simple: let each component manage its own life cycle independently at development time, and let each component’s life cycle merge with the host’s life cycle at run time (without modifying or adding host code)

2.4.2 Analysis of feasible schemes

You want to dynamically insert code for each component in the lifecycle of the host without changing the host code, which is a bit like AOP

There are three possible solutions:

  1. In the basic layer, a management class is provided for managing the life cycle of components. Each component manually registers its own life cycle implementation class into this management class. During integration debugging, the host can traverse all the registered life cycle implementation classes through the management class in the corresponding life cycle method of its own Application

  2. Parsing annotations using AnnotationProcessor generates source code at compile time to automatically register all component lifecycle implementation classes, which are then called by the host in the corresponding lifecycle methods

  3. Use Javassist to dynamically modify the class file at compile time, inserting the lifecycle logic for each component directly into the host’s corresponding lifecycle methods

I finally chose the first method, because the two methods though simple to use, also can be automated to complete all operations, very cool, but the technology is complex, the two methods in different editions of Gradle compatibility problems will affect the development of the whole project schedule, and difficult to maintain, also can increase the compilation time

Choosing the first method adds a few steps to the process, but it is simple, easy to understand and maintain, and can be used quickly by subsequent users regardless of Gradle versions and does not increase compilation time

2.4.3 Final Execution scheme

The first method has no specific principle to say, relatively simple, probably is defined in the base layer with the lifecycle method (attachBaseContext(), onCreate()…). Each component implements this interface, and then registers the implementation class with the base layer manager, through which the host invokes all interface implementation classes in the corresponding lifecycle method, in a typical observer pattern, similar to registering click events

In MVPArms, the implementation class of this solution is called ConfigModule. Each component can declare one or more ConfigModule implementation classes. The internal implementation is complex, and the implementation principle is reflection + proxy + observer. This class is also the most important class that the entire MVPArms framework provides to developers

It can configure a large number of custom parameters to the MVPArms framework, including the management of all life cycles in the project (Application, Activity, Fragment), the management of all network requests in the project (Retrofit, Okhttp, Glide), provides great expansion for the framework, so that the framework is more flexible

3 Project Explanation

3.1 How do I make components run independently?

In the gradle.properties directory at the root of the project, change the value of isBuildModule

#isBuildModule is true to run each component independently, false to integrate all components into the host App isBuildModule=trueCopy the code

3.2 configuration AndroidManifest

Since components may require different parameters in the AndroidManifest configuration when run independently and when integrated into the host, Such as component in a separate runtime needs one of these is configured on the Activity < action android: name = “android. Intent. Action. The MAIN” / > as the entrance, when components are integrated into the host, is dependent on the entrance of the host, So there’s no need to configure the action of the android: name = “android. Intent. Action. The MAIN” / >, then we need two different AndroidManifest cope with different situations

To specify a different AndroidManifest, add the following code to the build.gradle component, see the project code

android {
	sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
            if (isBuildModule.toBoolean()) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            }
        }
    }
}Copy the code

3.3 configuration ConfigModule (GlobalConfiguration)

As mentioned in ConfigModule’s final implementation (2.4.3), GlobalConfiguration is an implementation class that allows you to configure a large number of custom parameters to the framework

The CommonSDK project provides a GlobalConfiguration to configure common configuration information for each component, but each component may need to have some private configuration, such as initializing some specific properties. ConfigModule needs to be implemented in each component as well, as shown in the project code

It is important to note that the configuration of components is different when they are run independently and when they are integrated into the host, as mentioned in Configuring AndroidManifest(3.2). When a component is running independently, it needs to declare its own private GlobalConfiguration in the AndroidManifest and the CommonSDK public GlobalConfiguration, but when it is integrated into the host, Since the host has already declared the CommonSDK’s public GlobalConfiguration, you only need to declare its own private GlobalConfiguration in AndroidManifest. It also shows that AndroidManifest requires different configurations for different situations

3.4 RouterHub

A RouterHub defines the routing addresses of routers and groups the routing addresses of each component with component names as the prefix. You can view and manage the routing addresses of all groups in a unified manner

The routing address naming rules for the component name + page name, such as order component order details page of the routing address can be named “/ order/OrderDetailActivity”

ARouter calls the first ‘/’ character in the routing address Group. For example, in the preceding example, the routing address order is the Group. Addresses starting with order are assigned to this Group

Tips: Remember that different components cannot have the same Group name, otherwise part of the routing address under the Group can not be found!!

Therefore, it is a good choice for each component to use its own component name as the Group, after all, components do not have the same name

3.5 EventBusHub

AndroidEventBus as another cross-component communication method provided by this solution (the first cross-component communication method is public service (2.2.3.2)), AndroidEventBus has one more Tag than GreenRobot EventBus, It is easier to locate and manage events in componentization

The EventBusHub is used to define the Tag string for AndroidEventBus, which is prefixed by the component name and groups the events of each component

Tag is named by component name + page name + action. For example, if you want to refresh the order details page of the order component using AndroidEventBus, The Tag of the refresh method can be named as “order/OrderDetailActivity/refresh”

3.6 Use multiple domain names in the project

zhihu
Dry goods concentration camp
Re the nuggets
Retrofit
RetrofitUrlManager
Retrofit
BaseUrl
BaseUrl