PS: What you are currently insisting on may not have any effect at the moment, but it does not mean that it will not be useful at some point in the future.
When an app project becomes complex to a certain degree, componentization is essential. Componentization allows for better division of functionality. When it comes to componentization, some people may think of modularization. In fact, componentization and modularization are essentially the same, both aiming to achieve code reuse and decoupling of business logic. Modularization is mainly based on business division, while componentization is mainly based on functional division. Let's explore componentization from several fundamental aspects.
- Navigation between components
- Dynamic creation
- Resource conflicts
- Static constants
Navigation between components#
In componentization, when two functional modules do not directly depend on each other, their dependency is indirectly established through a base module. When there is a need to navigate between activities of different components, it is often not possible to reference an activity from another module due to the lack of mutual dependency.
Implicit navigation#
Implicit navigation is achieved through the native Android Intent matching mechanism to perform corresponding navigation using actions to specific activities. This way, implicit navigation can be used to achieve navigation between activities of different modules. However, please note that if an activity is moved out of its module without moving the corresponding navigation, an exception will occur if the navigation continues. Implicit intent navigation requires verification of whether the intent will be received. The resolveActivity() method needs to be called on the intent object to determine if at least one application can handle the intent. By using implicit navigation, exported can also be set to false to ensure that only the app itself can start the corresponding component.
ARouter navigation#
In Android development, modules can be seen as different networks, and the corresponding router is the hub that connects these modules. This hub can handle parameters and other aspects of page navigation uniformly. ARouter is an open-source page navigation router released by Alibaba. It can replace implicit navigation to achieve navigation between different modules and components, as well as listening to the navigation process, passing parameters, etc. ARouter supports both path-based navigation and URL-based navigation, and its usage is very flexible. A detailed explanation of how to use ARouter will be provided in a separate article. The comparison between ARouter and traditional Android navigation methods is as follows:
- Explicit navigation relies on classes, while router navigation relies on specified paths.
- Implicit navigation is centrally managed through AndroidManifest, which makes collaborative development difficult.
- Native navigation is registered using AndroidManifest, while router navigation uses annotation registration.
- After startActivity is called in native navigation, the navigation process is controlled by the Android system, while router navigation uses AOP aspect-oriented programming to intercept and filter the navigation process.
Dynamic creation#
The most important aspect of component-based development is to decouple the various modules and components as much as possible. This naturally leads to the consideration of using Java's reflection mechanism. Reflection allows for obtaining all information about a class at runtime, enabling dynamic manipulation of the class's properties and methods. When a Fragment is used as a separate component, if this Fragment component is not intended to be removed, using a regular Fragment would cause the app to crash because the Fragment cannot be found. However, if reflection is used to create the Fragment, it would at least prevent the app from crashing. Exception handling can be used to handle the situation. This approach reduces coupling. Although reflection has some performance issues, it can indeed reduce coupling to a certain extent. Learning about Java reflection mechanism is necessary for component-based development.
In component-based development, each component is required to be able to run independently. In general, each component has its own initialization steps. The best scenario is when the initialization steps of several components required by the project are basically the same. In this case, the initialization can be unified in the BaseModule. However, this scenario is relatively ideal. In most cases, the initialization of each component is different. You may think of initializing in each component's Application. However, if initialization is done in each component's Application, problems may arise during the final compilation due to the merging of Applications. Therefore, reflection comes to mind again. Initialization files can be created in each component, and then the initialization of each component can be performed through reflection in the final Application. In this way, dynamic configuration of the Application in component-based development is achieved through Java's reflection mechanism.
Resource conflicts#
During component-based development, if the AndroidManifest file of ModuleA specifies an Application using the android attribute, and the AndroidManifest file of the main App module also specifies a corresponding Application using the android attribute, conflicts must be resolved in the AndroidManifest file of the main App module using the tools="android" attribute. The replace attribute indicates that the attribute, which is the android attribute under the tag, can be replaced during the compilation process. According to the AndroidManifest file merging rules, the specified Application in the App module should be used.
Let's take an example. In a certain functionality module of my project, I use SMSSDK to implement SMS verification. Since it is only used in one specific module, I only include it in that module. If other modules need to use it, SMSSDK should be included in the BaseModule. If the Application of that module is not specified, MobSDK will assign com.mob.MobApplication as the Application for that module. In this case, conflicts will occur in the android attribute of the AndroidManifest file during the overall compilation and packaging. The solution is to use the replace attribute. The main conflicts in the merged AndroidManifest file are related to this issue. Other conflicts under the tag can also be resolved using the replace attribute. In actual development, more verification will lead to more insights.
Another point to note in component-based development is to prevent resource name conflicts that may cause resource reference errors or loss of certain resources during the final merge. When merging, resources such as strings and color values will be replaced by resources with the same name that are loaded later. The solution is to have certain naming rules for resources. You can use the "resourcePrefix "component name"" configuration in the build.gradle file to enforce the uniqueness of resource names. It is recommended to format the names of resources in modules as "ModuleName_Function_Other".
Static constants#
In component-based development, when modules are finally merged, each component exists in the form of a Lib module. The static variables defined in the R.java file of the Lib module are not declared as final, which means that the corresponding constants cannot be used in the component module. For example, switch statements cannot be used. This requires using if statements instead of switch statements in the component. However, this is not a problem when the component is running independently.
Butterknife is often used in development to conveniently annotate views and view events. It uses compile-time annotation processing and only allows the use of constants in annotations. Therefore, in component-based development, R should be replaced with R2 when using Butterknife. R2 is actually a copy of R, and the variables declared in R2 are final. So, when using Butterknife in component-based development, R2 should be used instead of R in the corresponding annotations. The next article will discuss the componentization of Application.