The current project is also very old, the first line of code is said to have been written sometime in 2014, At that time, The IDE for Android was Eclipse, there was no good architecture guide for Android (MVP, MVVM), the latest version of Android was 5.0, and The Material Design of Android was not yet popular…



background

With the development of business and product, the current project APK has 2~10 Android developers engaged in iteration and maintenance (note: the number of developers fluctuates, not because of leaving, but because the current project team is undertaking parallel development of multiple projects). At present, there are 30+ developers in the mobile team of the technical department, and there are many different projects in parallel development, but there is no architecture group (the bottom code farmers can not manage the organization, can only be buried in the code). The most direct problem without architecture group is that there is no organization to unify the technical selection and technical scheme of each project.

Here is a componentized framework XModulable that I wrote:

XModulable use:

1. Add a dependency configuration

android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [ XModule : Project.getname ()]}}}} dependencies {// implementation'com.xpleemoon.xmodulable:XModulable-api:x.x.x'
   compile 'com.xpleemoon.xmodulable:XModulable-api:x.x.x'
   annotationProcessor 'com.xpleemoon.xmodulable:XModulable-compiler:x.x.x'. }Copy the code


2. Implement components

@XModule(name = "XX Component name")
public class XXModule implements IModule{

}Copy the code


3. Initialize the SDK

if (isDebug) {
   XModulable.openDebug();
}
XModulable.init(this);Copy the code



4. Obtain components

Component acquisition can be done in two ways: dependency injection and manual query acquisition.

Dependency injection:

public class TestActivity extends BaseActivity {
   @InjectXModule(name = "xxx") XXModule mXXModule; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); XModulable.inject(this); }}Copy the code


Manual acquisition:

XModulable.getInstance().getModule("XX Component name")Copy the code



5. Add obfuscation rules

-keep class * implements com.xpleemoon.xmodulable.api.template.XModuleLoader
-keep class * implements com.xpleemoon.xmodulable.api.IModule
-keep class **$$XModulableInjector{*; }Copy the code

Principle introduction:

Componentization/modularization

  • Components: Encapsulates functionality for reuse purposes. A feature is a component, such as networking, IO, image loading, and so on

  • Module: Based on the purpose of business independence, organize a series of cohesive businesses, cut and split them with other businesses, and extract them from the main project or original location into a mutually independent part

Since modules are independent, decoupled, and reusable features, there are three main issues we need to address in implementing componentization/modularization:

  1. 1. Module communication — because business modules are isolated from each other, they do not know and cannot perceive the existence of other business modules at all. Therefore, a feasible scheme with maximum isolation, relatively lowest coupling degree and relatively minimum cost is needed to realize communication

  2. 2. Independent operation of modules — During subsequent iterations of maintenance, the responsibilities of personnel in each line of business can be clearer

  3. 3. Flexible combined operation of modules — can adapt to product needs, flexibly split, combined and packaged online



NOTE: This section will use XModulable as an example to explain how it modulates: The most straightforward way to illustrate and understand a program problem is to write a small demo and show key code. This article may not be as detailed as it should be, but I strongly recommend pulling down XModulable to take a look.





XModulable architecture diagram. PNG

XModulable engineering structure.png



Before addressing the three issues raised, take a look at the engineering diagram and architecture diagram of [XModulable]. The modules in the figure above correspond to the hierarchy:

  • App shell — Depends on the business layer and can flexibly combine business layer modules

  • Business layer – IM, Live and Main implement business layer service interfaces for common layer and register and query business modules with Common

  • Common layer — dependency base component layer; Undertake the business layer, expose the service interface of the business layer, and provide module routing service for the business layer

  • Basic layer – basicRes and basicLib

    • BasicRes – contains common resources and various UI components

    • BasicLib – contains networking components, image loading components, various tools, and other functional components

  • XModulable
    It’s just a small demo, and the figure shows my complete vision for each layer, so when I go to the source code I find some missing: Common is missing AOP code, basciRes is missing UI components, basicLib is missing almost all components.

  • XModulable-annoation
    ,
    XModulable-api
    and
    XModulable -compiler
    Belong to
    XModulable SDK
    .

  • XModulable SDK
    It is mainly used for

    Business module registration (automatically registered when the SDK performs initialization) and retrieval (dependency injection and manual retrieval)

    . Here to
    XModulable Sdk
    Do not do specific technical analysis for dependency injection and annotation compile-time processing is not understood or interested in the steps I have written before
    Compile time processing, a simple version of ButterKnife



1. Module communication

Modular communication (UI jump and data transfer) needs to grasp the following basic points: isolation, decoupling, low cost (easy maintenance), transfer of complex data (Fragment, View, File…) . It is easy to think of several ways to implement communication between independent independent modules:

  • Android traditional communications (aiDL, broadcast, custom urls…)

    • There is no way to avoid high coupling and difficult maintenance issues as the project expands

    • Another key problem is that you can only pass very simple data like fragments, views, files… These data (or objects, if you will) are not communicated at all, but these data in the actual app are the key nodes that make up an app. For example, there is a MainActivity in the main site of app, which is a structure of ViewPager+TabLayout. In this structure, every page is Fragment from different modules. At this time, our communication cannot be satisfied completely.

  • Third party communication (e.g. EventBus, RxBus…)

    • It is easy to be trapped in the vast event notification and receipt, which increases the cost of debugging and maintenance

    • It is possible to pass some complex data and carry other data objects through events, but code coupling increases accordingly

  • Third-party routing libraries (e.g. ARouter, OkDeepLink, DeepLinkDispatch…) Almost all can achieve isolation, decoupling, low cost (easy maintenance). As for data transfer, it only supports some simple data by default, but we can combine interface oriented programming, expose the interface of the public layer, and realize the corresponding interface method of the business layer to the interface of the public layer (UI jump, data read and write…). Finally, when the business layer uses it, it only needs to route to the interface to complete the communication of complex data. In the case of ARouter, the service interface of the business module can be exposed in the Common layer (IProvider, ARouter provides the service interface, as long as the customized service of the interface is realized, ARouter can perform routing operations), and then the corresponding business module can implement the corresponding service interface of the common layer. Finally, ARouter is used in the business module to route the service interfaces exposed by other business modules.

From the above analysis, routing + interface oriented programming is the best choice to implement componentization/modularization, but there is a problem here – what if one day someone wants to change the routing library or maybe a business module with different special needs uses a different routing library? It doesn’t matter. At this time, we need to encapsulate the routing library so that the routes in the service module are isolated from each other. In other words, the routing operation in one service module is a black box operation for other service modules. My encapsulation idea goes like this: Add the concept of an XModule (think of it as a container) that exposes the service interface in the Common layer and the XModule (whose implementation is also determined by the corresponding business module). Each business module has an XModule for hosting the service interface exposed in the Common layer. The first step in communication between business modules must be to get the XModule and then to get the service through the container.

To sum up, the final componentization/modularization is encapsulation + routing + interface oriented programming. Take the Live business module as an example, from the source point of view they are to achieve this idea. The LiveService service that the Live business module wants to expose to other business modules is exposed in the Common layer. At the same time, a LiveModule (the Live business module’s service container, which hosts the LiveService) is exposed in the Common layer,l, The Live business module is implemented for the corresponding interfaces of the Common layer (LiveModuleImpl and LiveServiceImpl). In this way, the upper level business can get LiveModule through the XModulable SDK and then invoke it through the services hosted by LiveModule.

// Common layer live exposes XModule (LiveModule) and service interface (LiveService) public Abstract Class LiveModule extends BaseModule {public abstract LiveService getLiveService(); } public interface LiveService extends BaseService { Fragment createLiveEntranceFragment(); void startLive(); LiveServiceImpl @xModule (name = ModuleName. Live) public class LiveModuleImpl extends LiveModule { @Autowired LiveService liveService; @Override public LiveServicegetLiveService() {
       return liveService;
   }
}
@Route(path = PathConstants.PATH_SERVICE_LIVE)
public class LiveServiceImpl implements LiveService {
   @Override
   public void init(Context context) {
   }
   @Override
   public Fragment createLiveEntranceFragment() {
       return new LiveEntranceFragment();
   }
   @Override
   public void startLive() { ARouter.getInstance().build(PathConstants.PATH_VIEW_LIVE).navigation(); }}Copy the code


2. The module runs independently

A business module in Android Studio is actually a module. From the point of view of Gradle, modules are either run as application plugin or library plugin. Therefore, in order for business modules to run independently, You need to control gradle’s ability to switch between application Plugin and Library Plugin, while providing the source code for a separate runtime.

First create a business module configuration in your project’s build.gradle. IsStandAlone indicates whether the business module is running independently:

ext {
   applicationId = "com.xpleemoon.sample.modulable"Modules = [main: [isStandalone:false,
                   applicationId: "${applicationId}.main",
           ],
           im  : [
                   isStandalone : false,
                   applicationId: "${applicationId}.im",
           ],
           live: [
                   isStandalone : true,
                   applicationId: "${applicationId}.live"]]}Copy the code


Then set build.gradle for the corresponding business module:

The def currentModule = rootProject.modules. Live // isStandalone value determines whether the current business module is running independentlyif (currentModule.isStandalone) {
   apply plugin: 'com.android.application'
} else {
   apply plugin: 'com.android.library'} android {omit... defaultConfig {if(currentModule isStandalone) {/ / the current component run independently, you need to set up applicationId applicationId currentModule. ApplicationId} omit... Def moduleName = project.getname () // service component resourcePrefix to avoid conflicting resourcePrefix"${moduleName}_"JavaCompileOptions {annotationProcessorOptions {the arguments = [/ / moduleName ARouter processor needed parameters: ModuleName, // Modulable: moduleName]}}}sourceSets {main {// Run separately to configure the source fileif (currentModule.isStandalone) {
               manifest.srcFile 'src/standalone/AndroidManifest.xml'
               java.srcDirs = ['src/main/java/'.'src/standalone/java/']
               res.srcDirs = ['src/main/res'.'src/standalone/res'}}}}...Copy the code

Finally, write in the business module the additional source files that sourceSets in Build. gradle declare are needed to run separately, such as Application, SplashActivity, and Manifest.

After completing the above process, you can select the corresponding business module live to run


3. Modules can be combined flexibly

To create a flexible combination of modules, simply change the isStandalone value of the business module configuration in the project build.gradle, and then determine whether to rely on the business module isStandalone in the app shell build.gradle. The key code is as follows:

Dependencies {... def modules = rootProject.modules def isMainStandalone = modules.main.isStandalone def isIMStandalone = Modules. Im. IsStandalone def isLiveStandalone = modules. Live. IsStandalone / / to determine whether a business component run independently and realize flexible depend on the business componentif (isMainStandalone && isIMStandalone && isLiveStandalone) {
       api project(':common')}else {
       if(! isMainStandalone) { implementation project(':main')}if(! isIMStandalone) { implementation project(':im')}if(! isLiveStandalone) { implementation project(':live')}}}Copy the code


Product technical debt

OK, now that the componentization/modularization issues are out of the way, let’s go back to sorting out the technical debt for existing products:

  1. Code is coupled, bloated, and cluttered

  2. The module level is inappropriate

    1. Business modules are interdependent and coupled

    2. The granularity of service module separation is not enough, and some modules are like a hodgepodge

    3. Service modules cannot be independently compiled and run, and service modules cannot be flexibly combined into APK

  3. Basic components cannot be extracted quickly enough to be used by other projects

The above problems directly lead to the inability of new colleagues to quickly clarify the engineering structure and to quickly enter the development.

If the team expands in the future, it will be divided into independent business groups based on business function modules, which will lead to a fight between the personnel organization structure and the engineering organization structure

Suit the remedy to the case

(a) control code quality

People on the team need to have a sense of code quality, otherwise it’s easy to have one wave of people refactoring and optimizing, and another wave of people writing bugs and bad code, and the point of refactoring is lost. Therefore, it is important to clearly communicate the control of code quality before entering into refactoring.

  1. Control common branch permissions (Master, Develop, and version branches) and keep common branch permissions in a few hands to avoid code conflicts and uncontrolled branches

  • Non-project owners only have Develop permission — master, Develop, and version branches of the remote repository cannot be merged

  • Develop Git flow and code review process to improve team collaboration efficiency

    • The project leader moves out of the release branch from the Master (or develop branch, depending on its own project management)

    • Developers move out of individual development branches from release branches

    • Developers work on individual development branches

    • Developers need to push to the remote side after they have developed on a personal branch,

    • The developer creates the Merge Request (Source Branch: personal branch, Target Branch: version branch) remotely (we used GitLab), assigning it to the project leader and @ the relevant developer

    • Perform code review

    • Review is complete, and the responsible person merges the remote branches

    (2) reasonable module level

    First, take a look at the module hierarchy diagram:




    It is very difficult to reclassify module levels from the original app level. Because a project is often many parallel development iterations, when you have cut or planning out the module level, but other members in the opposite direction, will inevitably lead to code in the process of implementation of merger with the vast number of conflicts and rework need to be solved, so here we also need to instill the module level and planning.

    1. The hierarchy is divided from top to bottom: APP shell, business layer, Common layer and Basic layer. Their responsibilities are as follows

    • App shell – Directly dependent on business modules

    • Business layer – the aggregation of independent business functions in a project, consisting of multiple business modules constituting the business layer

    • Common layer — connecting the preceding and the following: to undertake upper-layer business and provide routing services for business modules; It relies on basic and provides it to the upper layer

    • Basic layer – basicRes and basicLib

      • BasicRes – contains common resources and various UI components

      • BasicLib – contains networking components, image loading components, various tools, and other functional components

  • Business modules extract common code, components, and common resources to sink

    • Common code sinks into common, which can involve BaseAplication, BaseActivity, broadcast notification events (or eventBus-related events, depending on itself)

    • UI components and base resources sink into basicRes

    • Networking components, image loading components, various tools, and other functional components sink into basicLib

  • Hodgepodge modules are split and independent. Take the main business module as an example, including push, share, update, map, user center, second-hand house, new house, rental…… Such a bloated module cannot be broken down and completed in one go, so a plan must be made and progress should be made in a rhythm. Our plan is to split in order of business relevance from lowest to highest:

    • Share, update sink to basicLib

    • Push and drop the map to basicLib

    • The user center is an independent service module

    • Second-hand housing, new housing, rental independent business module

  • The business module runs independently; Service modules can be flexibly combined into APK

  • (3) Maven dependency on the Intranet of basic components

    After the separation of basic components, if module dependence is directly carried out, it will lead to repeated compilation and can not be flexibly provided to other apps. Therefore, we need to upload the basic components to the Intranet Maven, and then rely on them through Maven.

    1. BasicRes and basicLib are uploaded to Intranet Maven as basic resource components and basic function components

    2. BasicRes and basicLib are split and uploaded to the Intranet Maven based on fine-grained components, so that other projects can flexibly rely on them based on actual requirements

    Set pace and goals

    Establish the refactoring rhythm and process as follows, allocate the planning to each version reasonably, and steadily advance the componentized/modularized refactoring exploration practice while ensuring product iteration.

    rhythm

    The target

    Perform range

    The first phase

    Control code quality

    1. Control permissions for common branches (Master, Develop, and version branches); 2. Develop Git flow and code review process

    The second phase of the

    Reasonable module hierarchy (existing hierarchy split down)

    1. Hierarchy; 2. The business module extracts common codes, components and common resources to sink

    The third stage

    Reasonable module hierarchy (hodgepodge of modules split and independent 1)

    Share, update sink to basicLib

    The fourth stage

    Reasonable module hierarchy (Hodgepodge module split independent 2)

    Push and drop the map to basicLib

    The fifth stage

    Reasonable module hierarchy (hodgepodge module split independent 3)

    The user center is an independent service module

    The sixth period

    Reasonable module hierarchy (hodgepodge module split independent 4)

    Second-hand housing, new housing, rental independent business module

    The seventh period

    Reasonable module hierarchy (independent operation and flexible combination of business modules)

    Service modules run independently and can be flexibly combined into APK

    The eighth period

    Intranet Maven dependency of basic components

    1. Upload basicRes and basicLib to Intranet Maven; 2. Split basicRes and basicLib based on fine-grained components and upload them to maven on the Intranet

    Source: https://github.com/xpleemoon/XModulable


    Author: Xpleemoon. Ping an Haofang Android senior engineer

    Related to recommend

    App componentization and business split stuff

    Hotfix principles hotfix framework comparison and code repair

    Android Architecture: Three Steps to MVP Architecture (Part 1)


    Like can pay attention to: