With the continuous development of mobile projects, mobile software has become more and more complex and larger, and in order to reduce the complexity and coupling of software, as well as for the reasons of module reuse and team development efficiency, mobile component has become a technical direction that many companies need to explore and practice. This article mainly introduces how to implement the componentization of the mobile terminal of Zhenkun Xing, hoping to inspire the students who are engaged in the componentization of the mobile terminal.
1
Background on the road to evolution
1.1 What is componentization?
Componentization is to combine multiple independent sub-modules into a whole, reduce the coupling between modules, these sub-modules can be assembled and combined at will, and the sub-modules can run independently.
1.2 Reasons for componentization
With the rapid iteration of the App, the new features continue to increase, and this architecture has the following problems:
◆ The code coupling between services is too heavy, and a slight change in one place may cause multiple business modules to be affected, which will bring great costs to development and maintenance;
◆ With the expansion of the team size due to the growth of the business, it is necessary to pay attention to the problem of multi-person collaborative development. Componentization can isolate each business module from each other and support independent development, compilation, and testing.
◆ With the increase of the amount of code in the single project mode, the compilation code will be very slow, which will affect the development efficiency;
◆ The company will derive some independent applications in the future, such as logistics assistants, etc., hoping to directly reuse the previous code for rapid development.
1.3 Componentized Goals
The expected goals of this component-based architecture adjustment are as follows:
1. Improve component reusability and save development and maintenance costs;
2. Reduce the coupling of business modules and make service migration easier.
3. Business modules can be developed and run independently, and each business line is independently responsible.
4. Business modules can be compiled and packaged separately to speed up the compilation speed;
5. Components can be flexibly assembled to quickly generate other applications;
6. Multiple apps share components to ensure the uniformity of the entire technical solution.
2
Zhenkun line componentization practice
2.1 Architectural Design Diagram
Host layer: shell app, which contains some global configurations and main activities, but does not contain any business code.
Service layer: It contains all service components, which are dependent on each other and communicate through the routing bus.
Platform layer: contains the basic service SDK and business-related general code for each service component to call.
By decoupling each module, the upper-layer module is one-way dependent on the lower-level module, and there is no cross-level dependency and same-level dependency. Business components communicate with each other through routing buses and message buses.
The main problems faced in the process of componentization include the following:
◆ How to quickly extract the components;
◆ Components are isolated from each other, how to carry out UI jump and communication;
◆ How components operate independently.
2.2 How to Create a Component
In Android development, Gradle makes it easy to split code. In a Gradle multi-module project, there is a root directory, and each module has a subdirectory. In order for Gradle to know the structure of the project and what modules each subdirectory is, you need to add a settings.gradle file to the root directory, and each module provides its own build.gradle file.
settings.gradle 文件声明了工程的所有模块:
If you want to include the library module as a dependency on the app module, just add the following code to the build.gradle file of the app module:
1. Component extraction steps
1. Extract the common_component first, mainly encapsulating the basic functions required in the project, such as Utils package, network library, and database part. All business components depend on common_component;
2. Extract the simplest business components, first the code files and then the resource files. If no other business component is used for the dependent file, it will be moved to the business module, otherwise it will be moved to the common_component.
3. Continue to extract other components according to the 2 steps, the common_component will get bigger and bigger in the process of extraction, and we will streamline them in the future;
4. Add a routing framework to improve the jump and communication between components.
5. Streamline common_component and reverse extract the content from the business components. For example, the files in the Utils package are separated, and different Utils classes and methods are put into the corresponding business components, and the same business interfaces are put into the same API interface in Retrofit, and then extracted into the business components. Gradually streamline common_component to include no business code you don't need.
2. AndroidManifest合并
In a single project structure, the four major components and permission registration required for APP operation are all put into the same AndroidManifest file. When each module contains its own AndroidManifest file, when the final app is generated, multiple AndroidManifests will be combined into one, that is, the four components registered in each AndroidManifest will be merged together, and the synthesized address directory will be located in:
1. If you declare the required permissions in a functional module, you will see the corresponding permissions in the main module.
2. If the same permission is declared in other modules, the final AndroidManifest will merge the duplicate declared permission, so the same permission will only be declared once.
Therefore, we declare the four major components required by each business in the AndroidManifest of their respective modules, declare some basic general permissions in the common_component, and declare the permissions required by the business module separately (such as recording permissions) in their own AndroidManifest.
3. Global variable settings
The Android system will create an Application object for each program, and the life cycle of the Application object is the longest in the whole program, and its life cycle is equal to the life cycle of the program. We often use Applications where we use Contexts, but the Application definitions in componentized projects are in shell apps, and business modules cannot access them.
In Android development, we will configure different buildConfigField values in different environments in build.gradle, and the system will automatically generate the BuildConfig class after compilation, which contains all the buildConfigField values we configure, but the business components get the BuildConfig not from the main App, but also from their own Library The BuildConfig generated by the project does not contain the value set in the main app.
Define an Application base class in the common_component, each business component and the Application in the shell App depend on it, and the business component obtains the global Context through the base class. Again, we need to put the value of BuildConfig down into the common_component for the business components to use, and finally set it as follows:
common_component/ZApplication
app/ZKHApplication
4. Resource conflicts between components
In the process of componentization, resource files such as color, shape, drawable, layout, etc. may cause resource name conflicts, because the development of different modules is separated, and if the project team does not follow the unified specification naming, there will be resource name conflicts. We can avoid this by using resourcePrefix, after setting this value, all resource names in the component must be prefixed with the specified string, otherwise an error will be reported. However, resourcePrefix can only limit the resources in xml, not the image resources, so we also need to manually add the image resource name to resourcePrefix.
2.3 Unified Profile
Since each module has its own build.gradle file, which contains all kinds of configuration information required by the project, in order to unify the dependency information of each module version, and make the subsequent maintenance module configuration easier, the first problem we need to solve is to unify the configuration file between multiple modules. At present, Android project management Gradle dependencies are mainly based on the following three methods:
1. Manual management;
2. ext method (recommended by Google);
3. kotlin + buildSrc
We use the third method in our project, which is similar to the ext method, but adds IDE autocomplete and click-to-jump on top of it.
1. Manual management
This is the most basic way to manage dependencies, with each new module having to copy a copy of the configuration and dependency information from the original module to be consistent, and each time a dependency is upgraded, a lot of manual changes are required.
module_a/build.gradle
module_b/build.gradle
2. ext mode
Google's recommended dependency management method, which puts all configurations and dependencies together and manages them in a unified manner, and each Library Project can access them through ext
Root-level build.gradle
module_a/build.gradle
module_b/build.gradle
3.kotlin + buildSrc
This method is upgraded to the ext mode and provides the functions of IDE auto-completion and click-to-jump. To do this, you need to create a buildSrc module in your project and then write kotlin code to manage the dependencies.
kotlin + buildScr实现
1. build.gradle.kts
2. Dependencis.kt(kotlin代码)
2.4 Communication between components
Once the components of the project are isolated, the problem that needs to be solved is the communication between the components. This is mainly implemented by routing and message buses, where the routing framework is mainly used for UI jumps and one-to-one communication between components, while the event bus is used for many-to-many communication.
1. Benefits of Routing Frameworks
1. The display of Intent in the Android page jump will cause too much coupling, which is not suitable for componentized splitting, and the implicit Inten is too troublesome to use, and the developer will not be able to start after there are more implicit Intents, and it will cause the AndroidManifest file to increase;
2. Support dynamic modification of routes, support global or local downgrade policies, and then don't worry about crashing due to the activity not being found;
3. Support jump interception, which can handle login, buried and other logic in a unified manner. If you are not logged in, open the login page, and open the page you want to open after logging in successfully.
4. Support standard URL relay, which can make Android, iOS, and H5 jump paths unified. The server can control the client's jump logic at the data level.
5. Support cross-module API calls, and decouple components through control reversal.
2.路由框架ARouter
ARouter is a routing engine launched by Alibaba and is a routing framework, which is not a complete component-based solution, but can be used as a communication engine for component-based architecture. Its main mechanism is routing + interface sinking:
Routing: A route table is generated during compilation to implement UI redirection.
Interface sinking: The interface integrates IProvider and sinks into the commmon_component, implements the interface in the component and exposes the service through annotations.
Basic Functions:1. Add dependencies and configurations
In the case of a Kotlin project, the configuration is as follows:
Basic Functions:2. Add annotations
Basic functions: 3. Initialize the SDK
Basic Functions:4. Initiate a routing operation
3. ARouter is used in the project
Define the IProvider interfaces of each service component in the common_component, and the service components do the specific implementation, and define the interface implementation paths, all activity and fragment paths, and the methods to be called to other service components in the IProvider. common_component defines the ARouterManager to return IProvider, each service component.
common_component/ARouterManager
common_component/IsKuProvider
sku_component/SkuProvider
sku_component/CategoryFragment
4. ARouter Incremental Transformation
1. As shown above, define the global IAppProvider interface and put the AppProvider implementation in the App Module, the class will be deleted after the componentization is completed;
2. Define all routing paths in IAppProvider and add them to the Activities and Fragments in the App Module, so that the project will run successfully through ARouter.
3. Extract the business components and define the corresponding IBussinessProvider (such as ISkuProvider) and define the component communication methods we need, and extract the route path corresponding to the business components from IAppProvider to IBussinessProvider;
4. It should be noted that ARouter allows multiple groups to exist in a module, but does not allow the same grouping to exist in multiple modules.
5. Message bus
With a routing framework to enable components to communicate with each other, why do we need a message bus?
As mentioned above, ARouter communicates through interface sinking, and the interface caller needs to rely on this interface and know which component implements this interface, while the message bus sender only needs to send the message, and does not need to care whether anyone has subscribed to the message, that is, it does not need to know the situation of other components, so that the components can be more completely decoupled. The interface-based approach can only be called one-to-one, but the message-bus-based approach provides many-to-many communication.
However, these advantages of message buses also come with their own drawbacks. Because the sender and receiver do not know each other, resulting in scattered logic, it is difficult to locate after the problem occurs; the message bus can easily realize the cross-page interaction in the App, only need to send the message, ignore the troublesome startActivityForResult and onActivityResult, which will lead to the message sent at will, a large number of abuse. This makes subsequent code maintenance cumbersome, especially for cross-component messages. Here, it is necessary to develop relevant specifications in the project team to control the delivery of good news.
6.事件总线EventBus
EventBus is an Android event publish/subscribe framework that simplifies event delivery by decoupling publishers and subscribers. It can be used for communication between the four components of Android, as well as between asynchronous threads and main threads, supports specifying the thread and priority of event processing, and can send sticky events similar to sticky broadcasts. Simplify code, eliminate dependencies, and accelerate application development.
1. Binding in the base class
Add EventBus registration and de-registration to BaseActivity and BaseFragment, and use annotations to define whether subclasses need to be bound to EventBus or not.
2. Encapsulate the Event
Event is passed into a generic type that refers to a specific event class, and code is used to distinguish between services
3. Use
Send a message:
Receive Messages:
Notes:
◆ Do not send all events through EventBus;
◆ For use in Activities and Fragments, you need to de-register to avoid memory leaks.
2.5 How Components Operate Independently
1. Switch the operating mode of the component
There are two main properties of a module in Android Studio, which are:
1. application attribute, which can run independently of the Android program, that is, our APP
2. library attribute, which cannot be run independently, is generally a library file that Android programs rely on;
The property of Module is configured in the file of each component, when we develop in component mode, the business component should be in the application attribute, and the business component is an Android App, which can be developed and debugged independently, and when we convert to the integrated mode development, the business component should be in the library attribute, so that it can be relied on by our app shell project to form an APP with complete functions.
When we create an Android project with AndroidStudio, Gradle will generate a file gradle.properties in the root directory of the project: we can read the constants in gradle.properties in any build.gradle file in the Android project, and we can define a constant value in gradle.properties isRunAlone to control whether the component runs separately (true is yes, false is no):
Then we read isRunAlone in the build.gradle of the business component, but gradle.properties also has an important property: the data types in gradle.properties are all String types, and we need to convert them ourselves to use other data types, that is to say, we read a value of String type, and we need a Boolean value, the code is as follows:
1. The components run the configuration separately
To run the component separately, you need to configure some related information, such as Application, boot page, etc.
1. Specify the AndroidManifest file by modifying the properties in the SourceSets:
The SourceSets property specifies which source files or folders are to be compiled and which are excluded. Therefore, we can also use the above isRunAlone parameter to configure the resources and dependencies required by different environments.
2. Specify code and resource files by using the debug directory:
Since the debug directory is only used in the development stage when the component is run separately, the debug directory will only take effect when the component is run separately, and the code and resources in the debug directory will be automatically excluded when packaged into the main app as a library or the AAR package is played separately, and there is no pollution to the official code
◆在component/src下创建debug文件夹;
◆ Create 2 folders under the debug folder: java and res;
◆在java文件夹下创建所需要的Application、launchActivity等;
◆ Under the res folder;
◆将Application、launchActivity等注册到AndroidManifest.xml中。
In the whole process of running the app, each business component is not interdependent and completely isolated from each other, and they can interact with each other through the routing framework because they are packaged together. However, we do not have other business components in the process of independent operation, if there is a need for other business components, they need to be added to the dependency configuration, such as the user login in the project involves a lot of things, and it cannot be easily implemented in the separate operation of business components, which is also generally the registration and login will be extracted as a separate component, so that other business components can run independently. Here again, the isRunAlone parameter is required to distinguish it.
3
Summary of the path to evolution
There are Lombok and ButterKnife in the Zhenkun Xing Android project, and they are also migrating to Kotlin, so there are still some holes in the middle. After componentization, the project team will slowly kotlin the components and remove Lombok and Butterknife.
◆Lombok和Kotlin
Due to the compilation sequence, the Java code was not compiled at the time of compiling Kotlin, so Lombok did not automatically generate the code and caused the failure. The current practice in projects is to put Lombok into the Library (mostly entity classes). Since Lombok's capabilities are no longer needed with Kotlin, Lombok will be gradually removed in subsequent projects.
◆ButterKnife
For details, please refer to ButterKnife in Library projects, if the project uses apply plugin: 'kotlin- kapt, you need to change the annotationProcessor to kapt butterknife_compiler. Due to the ability to use Kotlin that no longer requires the ButterKnife, the ButterKnife will be gradually removed in subsequent projects.
◆ The unified configuration file must be prepared in advance, otherwise there will be various troubles in the future, please refer to the unified configuration file for details
◆ Global variable settings
There are many places where Application is used in Android projects, for business components can be obtained through ParentApplication, and in the base Lib you can define a utility class to pass the Application to it when the program starts, for use in the base Lib.
◆ Use of ARouter
First, all projects are converted to Router redirects, and then services are extracted, which is equivalent to a decoupling, and the service extraction process will be much faster.
◆ The independent operation of the components can not be implemented at first, and can be implemented by each business line when needed
Final project structure diagram
component_sku structure diagram
The process of project componentization is also the process of sorting out the project business, the business division is clearer, it is easier for newcomers to take over the project, and it is more convenient to assign development tasks according to the components; the maintainability of the project is stronger, which improves the development efficiency; it is better to troubleshoot problems when there is a problem, and the component can be directly dealt with if there is a problem; the requirements of each component are changed or the code is refactored, etc., and the requirements are changed or refactored by each component, and the impact will be controlled within the component.
4
This article refers to
1. Android组件化方案及组件消息总线modular-event实战
2. Youzan micro mall Android componentization solution
3. Zhihu Android client componentization practice
4. Some thoughts on the componentization of Android business
5. Kotlin+buildSrc for Better Gradle Dependency Management
6. WMRouter: 美外卖Android
7. ARouter 阿里路由框架
8. CC (the industry's first Android componentization framework that supports progressive componentization)
9. Anjuke Android project architecture evolution
10. The evolution of Meituan's Android platform-based architecture
11. Component-based architecture practices from the perspective of the Zhixing Android project
Source-WeChat public account: product technical team
Source: https://mp.weixin.qq.com/s/FeH2Lor620U5VZe8I84s6w