Android interview guide: talk about your understanding of componentization/modularization

Up to now, componentization is really nothing new. Big companies are familiar with it, and they are flying around. That is to say, some medium-sized projects and small projects are now working hard on the road of componentization. So comrades, those who have never played with componentization, and those who are not familiar with it, quickly start it. To put it bluntly, if you don’t know componentization, releasing shadow projects will be a nightmare for you. In essence, any technological progress is made by scratching one's head and cheeks under the pressure of practical needs, and exhausting countless hairs to come up with it. Haha, it's just a joke here. So componentization has been around for so long, web pages have been developed for so long, and more and more people use it. It must be of great benefit to our display development. The next partners will not, if you are not familiar with it, hurry up. How do you answer if you are not asked in an interview!

Let's talk about componentization formally

Componentization is actually not complicated. It is just a kind of thinking, which is essentially an idea of ​​app architecture. To put it bluntly, it is very simple. It is difficult to write the code when the componentization is transformed. The predecessors have basically gone through these pitfalls. Here I mainly record them. If you see the familiar parts, please don’t scold me. After all, they are all the things of the predecessors.

Let me add here that componentization is a kind of app architecture, and its development is also along the normal technological development line, and it is also for code encapsulation for the purpose of pursuing high reuse and high maintainability. The difference is that componentization is right Encapsulation of the entire app again.

So much nonsense, so what is componentization? Do you want to worry about it? Before we talk about componentization in detail, we need to understand two concepts, which are the components and modules mentioned above.

First of all, components and modules are not officially stipulated. They are the concepts that everyone has agreed to after the development of these technologies. In fact, it is very simple, and you can understand it when you talk

  • Module: The module in android is the business module. It refers to the business alone. It is to split the app according to the business. For example, we make the order into a module, and the personal center into a module. Video and audio are all made into modules. The embodiment in the app is a module, and the Chinese meaning of module is also a module. This is not allowed, this is what Google hinted to us. The purpose of modularization is to build building blocks. Just take out a few modules and anyone can launch an app easily. Don’t say that the demand for shadow apps is very strong now. Go and see the projects of big companies. Are they not a bunch of shadows? Project, Toutiao also created a Toutiao video vest, which is actually taking out the video module and adding a startup page. Such examples abound. If you don’t know how to componentize shadow projects, it will be a nightmare for you. Haha, don’t even think about maintaining it when the time comes. How many copies of the code do you need?
  • Component: This is as simple as it is. To put it bluntly, it is what we usually do. The encapsulation of functions is a component. A function is a component. IO, database, network, etc. are all components, so you will understand Bar. Since this is the case, we still have to come up with the concept of a component. Of course, everything has its meaning, because components have a very high and clear requirement for the encapsulation of functional code: one package, everywhere use. We need to maximize maintainability, reusability, scalability, and performance, because only in this way can we truly package in one place and use it everywhere. Of course, the scope of components now covers a wide range. Everything in the app is a component. Basically, we can divide it into: basic functional components, general UI components, and basic business components.

Above I talked about my own understanding of modularization and componentization, which is the understanding of modules and components in current development. In the development of modularization and componentization, the concept has also been adjusted and changed. You only need to look at what it looks like now. If you are interested in further study, you can look at the development process of componentization and modularization.

I think Android Modular Exploration and Practice is the best explanation of modularization and componentization concepts.

Componentization and modularization are now the same thing. If you think of a project as a combination of bags, then modules are the bags with the largest volume, components are small bags, and large bags are the most directly available. For bags that are externally observed and contacted, large bags are also composed of small bags. It is an inappropriate metaphor. This is the relationship between modules and components, which is our understanding of business and function splitting and packaging.

Well, we have officially started to introduce componentization.

Componentization in terms of engineering performance means that we divide the app into different module modules according to their different businesses, and encapsulate various functions into individual libraries. Horizontal dependencies between modules are strictly prohibited, or how to use them alone. I can't bring all the related modules in order to use a module. If such a module has dependent modules, it's nonsense to talk about reusability.

The main app is what we often say that the shell project depends on these modules. The library is dependent on the required modules, but the library version must be considered. With the expansion of business and functions, the number of libraries is also huge. When WeChat splits into components It is said that more than 80 modules have been split, so it can be seen that the library is also indispensable.

Module and library Most of the time we provide arr and jar to reference the shell project, arr and jar will not be recompiled when compiling, only the version will be checked, and the latest version will be kept, which not only improves the compilation speed of the app, It also provides a way to resolve resource conflicts.

Below I will put some pictures to describe the componentization. Take a closer look, the pictures are much more vivid than the text.

img

img

img

img

img

How the project is componentized:

img

img

img

Component core: router

When we abstract modules, there is no interdependence between modules, and they are strictly decoupled, in order to achieve our purpose of reuse. The modules cannot depend on each other, so the code of other modules cannot be called. Then, what should we do in the face of the common needs of calling up pages between businesses and communicating with each other? That’s right, everyone is in the picture above. See something router.

The router is a communication protocol between modules that we have formulated uniformly. In the router, we mainly deal with the following issues:

Page jump between modules Data transfer between modules Module initialization processing

img

There are ready-made routers, and you can also package them yourself. The idea used is to use the router as a component. All business modules depend on this router component, of course, the shell app is also, and then we register the required page jumps between modules, data transfer, and initialization into the router. Here is Reflecting the unity of our definition, the importance of a common module communication protocol, the router maintains multiple collections to save this relationship, and then we can realize the communication between modules through the router.

The encapsulation of the router is quite troublesome, and it is not easy to write it well. Now there are more used ones:

  • Ali's ARouter
  • The earliest ActivityRouter
  • The router of spiny classmate is my favorite. It is not maintained at present. The idea is great, and the problem of process is taken into consideration. Unfortunately, APT annotation technology is not used.
  • Hands-on router

I introduced several router routes above. Basically, no matter whether you write it yourself or use a ready-made one, the routers basically look like the above ones. Of course, a good router still needs to use the APT annotation technology to dynamically register with the router. The module method is very useful if you write your own code to register. Some problems are difficult to deal with. For example, if the static instance of the router is recycled and you create a new one, what should you do with the module registration method? It is too troublesome to write, so it is better APT annotations are convenient and scalable. Here is a ToyBricks_Android project modular solution that can solve the problem that APT cannot scan the arr package.

Finally, the communication between modules can actually be divided into three types:

  • page up
  • notification of an event
  • Directly call business methods of certain modules

The current router can complete this task very well.

Notification of certain events, such as I switch cities, notify some pages to display or refresh data, this will have a wide range of impact according to the business, and will affect multiple businesses, because of the reusability of the module, We cannot determine which business modules will be specifically affected in the module, so it is more appropriate to use eventbus/broadcast in this scenario.

Directly call the business method of the dictation module. This attribute strongly couples the business between the business modules. You can’t do anything about this design of the product. Generally, when you encounter such a scenario, you will ensure that the relevant business modules will be loaded. , so it is more flexible to define the router, you can call some methods of the specified module

I found another saying, I like it very much, and it is very close to my idea. From: Android architecture design: MVC, MVP, MVVM and componentization

The so-called componentization, the popular understanding is to divide a project into various modules, each module is decoupled from each other, can be independently developed and compiled into an independent APP for debugging, and then the various modules can be combined to form a complete APP . Its advantage is that when the project is relatively large, it is convenient for various developers to collaborate and develop synchronously; the divided modules can be shared between projects, so as to achieve the purpose of reuse. Componentization has many benefits, especially for larger projects.

How to carry out data sharing and data communication among various modules? We can divide the data that needs to be shared into a separate module to place common data. For data communication between modules, we can use Ali's ARouter for page jumps, and use the encapsulated RxJava as EventBus for global data communication.

I don't want to say too much about router, and I can't say it well. For this part, please see my last link, or you can also look at the above 4 routes. No matter how it is implemented, router is to build a communication intermediate platform for module modules. That's the purpose. Please read more carefully, I am also looking at others here.

Let me push you again. We try not to provide source code dependencies for modules and libraries. This is not in line with our purpose of reuse. When you release several shadow functions or other apps, then you use source code dependencies. How do you provide maintenance for the module and library, so try to use arr and jar. It is also good that we can package some libraries with similar positioning into a module.

We must be familiar with the use of gradle. In componentization, we will use gradle extensively to provide various resource loading configurations and environment configurations.

The core purpose of componentization is the high reusability, high maintainability and high scalability of the code. Other advantages are related in nature. We must first grasp the core points to learn, and the others are not the main ones. Look again

Problems encountered in componentization

1. Submodules are compiled and tested separately

When doing component-based development, we test the module library to create an apk file for the module alone, and provide the library for external dependencies when releasing the module. This is achieved by configuring the gradle compilation mode of the module

First define a constant in the submodule build.gradle to indicate whether the module is currently in development mode

def isDebug = true mode configuration in submodule's build.gradle. Compile it into a standalone app in debug mode, and compile it into a library in release mode.

if (isDebug.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

There are differences in the module AndroidManifest.xml files in the two modes. As an app that runs independently, it has its own Application, and the entry intent of the Launcher needs to be added, which is not required as a library. This problem is easy to solve, just write two different AndroidManifest.xml, and configure it in gradle.

img

Configure in gradle script

android {
    sourceSets {
        main {
            if(isDebug.toBoolean()) {
                manifest.srcFile 'src/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/release/AndroidManifest.xml'
            }
        }
    }
}

2. Version consistency between sdk and third-party libraries

Different modules rely on inconsistent sdk versions, which will cause compilation problems due to compatibility issues. Different modules refer to different versions of the same third-party library, and the library is not forward compatible, so problems such as method failure and parameter mismatch may occur. So it is necessary to unify the dependency version of the entire project.

The constants defined in the outermost build.gradle can be referenced by the build.gradle file of the entire project, and the unified version definition can be placed here.

ext {
   android_compileSdkVersion = 25
    android_buildToolsVersion = '25.0.2'
   android_minSdkVersion = 21
    android_targetSdkVersion = 25

    lib_appcompat = 'com.android.support:appcompat-v7:25.1.1'
    lib_picasso = 'com.squareup.picasso:picasso:2.5.2'
    lib_gson = 'com.google.code.gson:gson:2.6.1'
}

I haven't tried the module of the arr resource, whether it can still be used in this way

3. Resource ID conflict

The resource files of the module in android will be merged into the main project in the end. The id of the resource file is different from the id of the moudle, so there will be a problem of resource duplication. To solve this problem, our approach That is, add a unified prefix to the module resource

 andorid{
     ...
 
     buildTypes{
         ...
     }
 
     resourcePrefix "moudle_prefix"
 
}

But note that the files under the res folder can be prefixed with the gradle script, but the image resources cannot. We still need to add the prefix when naming the image resources

4. Application initialization problem

When the submodule is used as an application, some initialization work needs to be done during Application.onCreate. As a library, this onCreate cannot be adjusted. So write a static method for the Application of the main project to call.

 public class ApplicationA extends Application {
 
 @Override public void onCreate() {
   super.onCreate();
   //给底层library设置context
   AppContext.init(getApplicationContext());
 }
   /**
    * 作为library时需要初始化的内容
   */
  public static void onCreateAsLibrary() {
    //给FunctionBus传入接口的实例
    FunctionBus.setFunction(new FunctionA() {
      @Override public String getData(String key) {
        return "xixi";
      }
    });
  }
}

Remember to initialize the submodule when the Application onCreate of the main project.

public class MainApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    AppContext.init(getApplicationContext());
    ApplicationA.onCreateAsLibrary();
    ApplicationB.onCreateAsLibrary();
  }
}

In addition to providing methods to call in the shell project, it can also be done in combination with the router using APT technology. Using annotations, we don’t need to call by ourselves, completely decoupling

5. Library dependency problem

Let me talk about a question first. In the componentized engineering model diagram, both the multimedia component and the common component depend on the log component, and the A business component depends on both the multimedia component and the common component. At this time, someone will ask, why don’t you do this? The log component will be relied on repeatedly, and the Common component will also be relied on by every business component. Is there no problem?

In fact, there is no need to worry about this problem at all. If there is a problem of repeated dependencies, an error will be reported when you compile and package. If you still don’t believe it, you can decompile the last packaged APP and look at the code inside. up. Component is just a term we call for convenience in the code development stage. There is no such concept when the components are packaged into the APP. These components will be packaged into arr packages in the end, and then depended on by the app shell project. When building the APP Gradle will automatically exclude duplicate arr packages during the process, and the same code will not exist in the APP;

But although the components will not be repeated, we still have to consider another situation. The third-party libraries we compile in build.gradle, such as the AndroidSupport library, are often dependent on some open source controls, and we must also compile AndroidSupport library, which will cause repeated loading of third-party packages and our own packages. The solution is to find out the extra library and exclude the extra library, and Gradle also supports this, respectively. Two ways: exclude based on component name or exclude based on package name, the following is an example of excluding the support-v4 library:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exlude module: 'support-v4'//根据组件名排除
        exlude group: 'android.support.v4'//根据包名排除
    }
}

The problem of repeated library dependencies has been solved, but we rely on many open source libraries when developing projects, and each component of these libraries needs to be used. It is very troublesome to depend on each component, especially for When these libraries are upgraded, in order to facilitate our unified management of third-party libraries, we will provide a unified entrance for the entire project to rely on third-party libraries. One of the functions of the Common library introduced earlier is to rely on open source libraries uniformly, because other business components All rely on the Common library, so these business components also indirectly depend on the open source library that Common depends on.

 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
     //Android Support
     compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
     compile "com.android.support:design:$rootProject.supportLibraryVersion"
     compile "com.android.support:percent:$rootProject.supportLibraryVersion"
     //网络请求相关
     compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
     compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
    compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
    //稳定的    compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
    compile "com.orhanobut:logger:$rootProject.loggerVersion"
    compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
    compile "com.google.code.gson:gson:$rootProject.gsonVersion"
    compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"

    compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
    compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"

    //router
    compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
}

6. Module uses different codes for different business environments

We will have at least two environments for testing and online when we do projects. Componentization makes us start to pay attention to gradle. Through gradle configuration, we can reduce a lot of code writing. We can also use gradle to switch environments. In the environment that does not pass Register different code files, see the picture below

img

We have two environments, debug and release, which contain code executed in different environments, and main contains code parts that have nothing to do with environment switching. We can set gradle like this

 android {
     // ...
     sourceSets {
         debug {
             java.srcDirs = ['src/main/java', 'src/debug/java']
         }
         release {
             java.srcDirs = ['src/main/java', 'src/release/java']
         }
    }
}

In addition, when publishing the library, some settings need to be specified, as follows:

android {
    // ...
    defaultConfig {
        // ...
        defaultPublishConfig 'release'
        publishNonDefault true
    }
}

illustrate:

  • defaultPublishConfig 'release', the default library will only produce the version under release, this version will be used by all projects, you can control which version of the library is produced by default through defaultPublishConfig.
  • publishNonDefault true, all versions of the library cannot be produced by default, by setting publishNonDefault to true, all versions of the library can be produced at the same time. The business component module depends on the library produced by different basic components, as follows:
dependencies {
    // ...
    debugCompile project(path: ':baselibrary', configuration: "debug")
    releaseCompile project(path: ':baselibrary', configuration: "release")
}

After using such a configuration script to solve the problem that multiple APK packages rely on different libraries produced by the same component, we finally get the development/test/production APK packages we need.

Merge multiple modules into one folder

We use the module in the studio when we refer to it, the project name +: colon to indicate

implementation project(':basecomponents')

Note that this just means that we want to refer to the module with this name, and we don’t care about the address of this module here

Then it can be understood that the address of the module can be configured as we like, so where to configure the address of the module, the answer is in the setting.gradle file, we can specify its address for the module ':basecomponents'

For example, we want to create a new folder named components to store our component module, we give two components (basecomponents, aaa), and then we specify the file path of each project in setting.gradle

include ':app', ':basecomponents', ':aaa'
project(':basecomponents').projectDir = new File( 'components/basecomponents' )
project(':aaa').projectDir = new File( 'components/aaa' )

img

A good componentized document is a must

Componentization is the future of android development entering a new era. It is the inevitable result of code expansion and support for rapid development. A good componentization document is also necessary today.

Post a picture below

img

img

component pit

Componentization is good, but there are also many pits, which are not easy to fill, especially databinding, dagger, bufferkinft, which are caused by studio compilation problems.

Although the module in the studio is independent of the shell project in terms of code, it must be merged into the shell project at the end of compilation, or how to achieve an APK file. If multiple APK files cannot be plugged in, the plug-in There are more pits. Merging the module into the shell project will cause a fundamental problem, the value of the R file of the module has changed. The R file data of the module is not fixed, only the R file of the shell project is a constant value, which does not change from time to time. The value of the R file of the module will be determined after the resources of the module are merged into the shell project, so this depends on The technology of annotations at compile time caused problems. The R path you specified could not be found in the end, and it is said that it also involves the final of annotations. Pay more attention to the frame, and you have to look at the pits to climb over them

Guess you like

Origin blog.csdn.net/m0_70749039/article/details/130662098