In addition to the main engineering code, debugging code, such as logs, debugging switches, debugging tools, functional mocks, and so on, is a necessary development environment for the development phase. Many projects now use cloud testing, third-party cloud testing platform (such as testin, ali cloud, etc.) often cannot skip the login test, so be in the cloud of login token embedded in the installation package, also specify cloud interface measurement and so on some special function of the test scenarios, some cloud requires a specially designed for the three parties at this time of cloud platform using cloud environment. AS/Gradle also has built-in debug and Release environments. How does the code manage so many different environments? How do you completely isolate code from different environments? We have to make sure that debugging, mocks, and other business-neutral code doesn’t go online at all.

First, we create a new project using AS. What does AS/Gradle have?

AS shown in the figure above, AS has created three SourceSet directories under SRC, androidTest, main, and Test, where main is the main project directory and the other two are for unit tests.

Are there only three SourceSet directories for AS/Gradle?

Of course not! We know that buildTypes can be configured in the build.gradle configuration file. We already have debug and release buildTypes by default. We can also create a custom buildType name called Custom.

android {
    ...
    
    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        custom {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}}Copy the code

Once we have synced, we’ll click “Build” on the left of AS and we’ll have a choice of which buildType to use

Many of you may ask, what’s the use of this?

It really helps!

AS mentioned above has created the SourceSet directory named “main” and two other test directories in the SRC directory. Create a debug/release/custom directory in the SRC directory. The contents in this directory are the same as those in the main directory. The main directory is always active, but only one buildType SourceSet directory is active at any one time, as shown in the figure below. Gradle automatically merges multiple SourceSet directories at compile time.

AS/Gradle also supports importing other modules or tripartite repositories based on buildType

    dependencies {
        ...
        debugImplementation project(":debugLib1")
        debugApi 'com. Android. Support: appcompat - v7:28.0.0'
        
        customImplementation project(":customLib")
        customApi 'com. Google. Code. Gson: gson: 2.8.5'. }Copy the code

In addition to buildType,AS/Gradle supports productFlavors, which are about the same things AS buildTypes. Gradle builds with a buildType, productFlavor, and main SourceSet. The SourceSet directories are automatically merged during compilation.

Now that it’s clear how to use multiple environments, we can customize buildType/productFlavor as needed, and then put the debug or Mock code in the corresponding buildType/productFlavor SourceSet to separate the different environments.

Separation of code and everything is okay?

And when you actually do it, BuildType /productFlavor can be referenced directly under main, but not the other way around, because if you do a buildType/productFlavor switch,main might not find buildType/productFlavor In the class.

The question is, since main cannot directly reference the code in buildType/productFlavor, how can the code in buildType/productFlavor be initialized? How does Main call these classes?

Take the cloud testing environment mentioned above as an example. Suppose I created a productFlavor named Cloud for cloud testing, and I want to call the login interface once when the cloud testing environment APP is initialized, so that the token can be built in and there is no need to log in manually in the cloud testing environment.


public class LoginManager {

    public static void login() {
        //do login
    }
}
Copy the code

How to trigger this login operation is a problem. Normally we perform the initialization in the onCreate() method of our custom Application.

public class App extends Application {
    
    @Override
    public void onCreate() { LoginManager.login(); / / here should not be directly quoting LoginManager, because LoginManager for cloud in productFlavor SourceSet, once cut to other productFlavor LoginManager class just can't find my}}Copy the code

There is a way to create a CloudApp in the Cloud that inherits the App from Main and then references it in the AndroidManifest of Could

<application
        android:name="chao.app.cloud.CloudApp" 
        tools:replace="android:name"// If necessary, replace App with CloudApp... />Copy the code
public class CloudApp extends App {

    @Override
    public void onCreate() { super.onCreate(); LoginManager.login(); // If we do not need to worry about the class being lost due to the buildType/productFlavor switch}Copy the code

This works well in simple scenarios, but more complex ones

If productFlavor is Cloud and buildType is Debug, debug will need to have its own initialization, but the Application in AndroidManifest will eventually have only one. So slightly more complex scenarios can be tricky. In addition, classes in buildType/productFlavor cannot be called directly due to multiple environment switches.

How to solve

ServicePool is a magic tool for componentized communication. It can effectively solve the above problems.

Use ServicePool for Service initialization instead of Application initialization. Define a CloudInit for Cloud and a DebugInit for DEBUG.

public class App extends Application {

    @Override
    public void onCreate() { super.onCreate(); AndroidServicePool.init(this); // Initialize ServicePool}}Copy the code
@Init(lazy = false//ServicePool by default uses lazy loading for all components. Service @service public class CloudInit implements IInitService {@override public voidonInit() { LoginManager.login(); }}Copy the code
@Init(lazy = false//ServicePool by default uses lazy loading for all components. Service @service public class DebugInit implements IInitService {@override public voidonInit() {
        //do something
    }
}
Copy the code

Now that we’ve solved the multi-environment initialization problem, how do we solve how classes are called in buildType/productFlavor? I’m sure you’ll find the answer after watching ServicePool.

This document covers demo code click here to view it