chart
The routing data component is the library that the upper-layer business component must depend on, and the independent function component and public UI component can choose whether to depend on them according to the demand. Common UI components are the common configuration and encapsulation of the overall UI style of the application, and are also relied upon by common business components. The base SDK is the lowest level SDK library on which all business components are based. The top-level business APP is generally divided into functional modules, such as email APP, IM APP and video APP
Why componentize
First, do componentization is mainly with the iteration of software version, exposed a huge problem. Under the same module, jump high degree of coupling between the various data, although development should pay attention to the coupling of the code, but everyone in the team’s experience level and the coding style is different, the understanding of the coupling degree and standard is different also, over time, the module will be more write code between the greater dependence on each other. After all, sometimes you don’t think too much about design patterns when you can just take them and use them. Do the componentization will be relatively independent module independent, to achieve hard code isolation, forced to reduce the coupling degree of the module. Second, the project with the iterative development demand will become more and more big, the development project in the process of the BGF is a very time consuming, after componentization configuration flexibility to choose the required components are compiled, shorten the time three, some components can be Shared among multiple projects, as I have seen the two network backup module and email module of the project. Componentization scheme is not adopted, and moving code and resources is too time-consuming and laborious. Using componentization scheme, directly import module into new project, add corresponding route and route Service method can be used (on the premise that the project adopts componentization scheme)
Several problems in the process of componentization
How can multiple component modules share Application
1. Create the AppProxy class in BaseApplication, which is an implementation of IAppLifeCycle. Call AppProxy’s life cycle method in BaseApplication’s life cycle method. 2. Scan the Manifest file in AppProxy constructor, and get the implementation class in each component through reflection. Add these implementation classes to the list in AppProxy. 3, in the life cycle method loop in the second step of the list call each module in the list injected life cycle proxy object corresponding method core processing is in AppProxy class:
Each module’s proxy implementation class must be registered in the manifest, otherwise it will not be scanned
<! Application--> <meta-data Android :name="com.pandaq.pandamvp.app.lifecycle.LifeCycleInjector"
android:value="AppInjector"/>
Copy the code
LifeCycleInjector has added a priority option for the LifeCycleInjector. The default value is 0. Larger numbers delay loading
/**
* priority for lifeCycle methods inject
*
* @return priority 0 for first
*/
int priority(a);
Copy the code
Activity and Fragment life cycles
- Activity: In the figure above, corresponding to Application lifecycle injection, in AppProxy
The onCreate ()
Method to register the Activity lifecycle callback with the Application. Manage the Activity lifecycle through Application.
@Override
public void onCreate(@NonNull Application application) {
for (IAppLifeCycle appLifeCycle : mAppLifeCycles) {
appLifeCycle.onCreate(application);
}
// Register each Module Activity lifecycle callback method
for(Application.ActivityLifecycleCallbacks callbacks : mActivityLifeCycles) { application.registerActivityLifecycleCallbacks(callbacks); }}@Override
public void onTerminate(@NonNull Application application) {
if(mAppLifeCycles ! =null) {
for(IAppLifeCycle appLifeCycle : mAppLifeCycles) { appLifeCycle.onTerminate(application); }}// Unlog the activity lifecycle callback when the app lifecycle ends
if(mActivityLifeCycles ! =null) {
for (Application.ActivityLifecycleCallbacks callbacks : mActivityLifeCycles) {
application.unregisterActivityLifecycleCallbacks(callbacks);
}
}
mAppLifeCycles = null;
mActivityLifeCycles = null;
mFragmentLifecycleCallbacks = null;
AppUtils.release();
}
Copy the code
- Fragments: The Fragment Lifecycle implementation class has been added to the injector list using the ILifecycleInjector implementation class as well as the Activity lifecycle injection for each module. However, processing in AppProxy is no longer managed by registering with Application. Instead, it is managed through a default Activity lifecycle implementation class that registers these Fragment lifecycle callback classes with FragmentManager
private void registerFragmentCallbacks(Activity activity) {
// Outside the register framework, developers extend BaseFragment lifecycle logic
for (FragmentManager.FragmentLifecycleCallbacks fragmentLifecycle : mFragmentLifeCycles) {
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(fragmentLifecycle, true); }}}Copy the code
The description may not be easy to follow, but refer to the PandaMvp for the code
Communication between components
Component communication here using ARouter, specific use here do not expand, go directly to see ARouter’s document, several key points:
@Route(path = "/test/activity")
public class YourActivity extend Activity {... }Copy the code
ARouter.getInstance().build("/test/activity")
.withLong("key1".666L)
.withString("key3"."888")
.withObject("key4".new Test("Jack"."Rose"))
.navigation();
Copy the code
Ii. Return of page value:
// Build the standard routing request, startActivityForResult
// The first parameter of navigation must be Activity and the second parameter must be RequestCode
ARouter.getInstance().build("/home/main").navigation(this.5);
Copy the code
3, Fragment discovery:
/ / get fragments
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
Copy the code
Cross-component method calls:
// Declare the interface through which other components invoke the service, as defined in the Router component
public interface EmailService extends IProvider {
EmailAccount getAccount(a);
}
// Implement the interface in the corresponding business component
@Route(path = "/email/emailservice")
public class EmailServiceImpl implements EmailService {
@Override
public EmailAccount getAccount(a) {
return new EmailAccount();
}
@Override
public void init(Context context) {}}Copy the code
// Call the component to find the service and then call the method
public class Test {
@Autowired(name = "/email/emailservice")
EmailService emailService;
public Test(a) {
ARouter.getInstance().inject(this); EmailAccount account = emailService.getAccount(); }}Copy the code
To avoid problems such as writing errors, it is best to define a constant class that manages the path of the route uniformly and uses a different parent path grouping for each component
Data entity sharing between components
The transfer of data between individual components is done through Arouter’s services:
As shown in the figure, each independent business Module has its own folder under the Router Module. Take the email component as an example. Other service components need to obtain the account information and mail signature information of users in the mail component. Therefore, email registers its account information and signature information MailAccount and MailSign with the Router Module. This allows other components to identify the two data classes through their dependency on the Router Module. Component A obtains the MailAccount from the Email component. The Email component first registers the exposed getAccount() method in the EmailService interface of the Router, and then registers a callback method in the callbacks if the fetch is asynchronous. A gets the data from EmailService asynchronously through the interface callback.
ORM database add delete change check
ORM database, the project uses GreenDao3.0. Since GreenDao initialization and Tab generation do not cross modules, there are two options for storing data: 1. Each business Module maintains its own database. The data that business components need to communicate with each other is then sunk into a common library by creating separate classes. In this way, the business data can be completely independent, but it needs to write more data entity classes. 2. The data entity classes are directly placed in a public library, and the initialization of GreenDao is also placed in this library. In the actual operation of the project, I put the entity classes that will be stored in the database into the Router Module separately by folder. The database operation tool class is defined in the corresponding component, such as Email component, the cache table operation tool class is called EmailTb, through the method of EmailTb to add, delete, change and check the database. There is no barrier isolation for the internal add, delete, change and check of the Email component. If component A needs to add, delete, change and check the Email table, it needs to register the method exposed in the EmailService to add, delete, change and check indirectly. If Email is not registered to expose the corresponding method, other components cannot operate on the Email database
Resource file name problem
resourcePrefix "a_"
Copy the code
By configuring resourcePrefix in Gradle to add a prefix limit to resource files, the compiler will raise an error at compile time if the name does not conform to the specification. This is a physical task when performing componentized transformation, which is often described as tears
Module combination run
Modules running freely in combination require modules to have the ability to be both an application and a library. Gradle. properties and build.gradle files are configured, and scripts are compiled to decide which business component apps to package.
# launchApp = xx (component module name) # buildAll = false # # 2 LaunchApp = app (component Module name) # buildAll = false # loadComponents = xx1,xx2 LaunchApp = app_bmodule # whether to package the app, true shellApp package will depend on allComponents. False packaging will rely on loadComponents buildAll = false # All business component App Module name allComponents = APP_amodule, app_bModule # Multi-business component into ShellApp syndication enables the module name loadComponents =Copy the code
Common build.gradle configuration
// Determine whether the business component module is a library or a standalone App based on whether the configuration is launchApp
boolean isShellApp = project.getName() == shellApp
boolean isLaunchApp = project.getName() == launchApp
if (isLaunchApp) { // Shell APP always runs in Application mode, and other business components plug and plug in dependent library mode according to configuration
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'} · · · sourceSets {main {jnilibs.srcdirs = ['libs']
if (isShellApp){ // Container apps will only run in App mode or not
manifest.srcFile 'src/main/AndroidManifest.xml'
}else {
// The application mode and library mode manifest files are different, which one is used according to isLaunchApp
if (isLaunchApp) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'}}}}Copy the code
Build. gradle for each business component module:
·
·
·
defaultConfig { // Add the applicationId and version number depending on whether it is launchApp
if (isLaunchApp) {
applicationId "com.pandaq.app_amodule"
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}}Copy the code
The container App Module (main Module) needs to be configured to dynamically rely on the business component library for multi-component packaging and repackaging, as compared to build.gradle for normal business component modules:
·
·
·
defaultConfig { // Add the applicationId and version number depending on whether it is launchApp
if (isLaunchApp) {
applicationId "com.pandaq.app_amodule"
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"} · · · · · ·// Load the dependent business APP on demand
if (buildAll.toBoolean()) { // Add dependencies to all business components during refactoring
for (String name : allComponents.split(",")) {
implementation(project(":$name")) {
exclude group: 'com.android.support'
exclude module: 'appcompat-v7'
exclude module: 'support-v4'}}}else { // You can select the service group price to join the container App
for (String name : loadComponents.split(",")) {
if(! name.isEmpty()) { implementation(project(":$name")) {
exclude group: 'com.android.support'
exclude module: 'appcompat-v7'
exclude module: 'support-v4'
}
}
}
}
}
Copy the code
Other thinking
Componentization is risky and should be pursued with caution. It is a long and arduous process to componentize a large non-componentized project. Each module in the project will inevitably have a variety of coupling relations, often affecting the whole body, so it needs to be componentized. The first step is to encapsulate and decouple the project, and separate functions should sink and sink and be rewritten. Sometimes code reuse is a disaster for componentization, especially when interfaces that are not part of a functional module are reused.