Android custom Gradle plugin (7): About multi-channel packaging

The company's SDK project was originally mainly for Google Play. Recently, new requirements have been received, and two new SDKs need to be connected, one for South Korea and one for India. The package needs to be independent applicationId, and different packages need to be configured with different dependencies. .

This requirement can be achieved by the multi-channel configuration of Android Studio. If it is configured directly in Android Studio, it needs to be added by the access party, so we still implement it in the gradle plugin.

Implementation ideas:

  1. Obtain the configuration file from the background according to the package name of the access party.
  2. Configure channel parameters according to the configuration file, eg applicationId.
  3. Dependencies are added dynamically based on the executed packaged Task.

Get the package name of the access party

The project has actually implemented the function of obtaining the package name of the access party in the previous requirements. The package name of the access party comes from AppExtension, and the code is as follows:

class MyPlugin  implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        def appExtension = project.extensions.findByType(AppExtension.class)
        
        project.afterEvaluate {
            def applicationId = appExtension.defaultConfig.applicationId
        }
    }
}
复制代码

However, the multi-channel configuration productFlavorsneeds to be configured before gradle is compiled, so if project.afterEvaluatewe obtain it in gradle, applicationIdwe cannot achieve our purpose, which is to configure multi-channel information according to the configuration file.

Therefore applicationId, it needs to be directly passed into the plug-in from the project of the access party. extensionsTherefore, I thought of configuring the parameters by creating , the code is as follows:

Class DiyExtension{
    String originalApplicationId
}

class MyPlugin  implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        def diyExtension = project.extensions.create("DiyExtension", DiyExtension)
        
        project.afterEvaluate {
            def applicationId = diyExtension.originalApplicationId
        }
    }
}

//在build.gradle中添加
DiyExtension{
    originalApplicationId "com.test.applicationid"
}

复制代码

However, this method is actually the AppExtensionsame as that project.afterEvaluatein order to get the value.

In the end, it was decided to read the file, that is, to give the access party a plugin-config.jsonfile, put it in the app directory of the project, and then we can get it by reading this file in the plug-in . The applicationIdcode is as follows:

plugin-config.json 文件内容
{
  "original_application_id": "com.test.applicationid"
}

//读取文件类
public class FileUtils {

    public static String readFileFromAppFolder(File parentFile, String fileName) {
        File readFile = new File(parentFile, fileName);
        if (readFile.exists()) {
            StringBuilder config = new StringBuilder();
            FileInputStream fileInputStream = null;
            InputStreamReader inputStreamReader = null;
            BufferedReader reader = null;
            try {
                fileInputStream = new FileInputStream(readFile);
                inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
                reader = new BufferedReader(inputStreamReader);
                String configContent;
                while ((configContent = reader.readLine()) != null) {
                    config.append(configContent);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                    if (inputStreamReader != null) {
                        inputStreamReader.close();
                    }
                    if (fileInputStream != null) {
                        fileInputStream.close();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return config.toString();
        }
        return "";
    }
}

class MyPlugin  implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        def pluginConfigStr = FileUtils.readFileFromAppFolder(project.projectDir, "plugin-config.json")
        PluginConfig pluginConfig = new Gson().fromJson(pluginConfigStr, new TypeToken<PluginConfig>() {
            }.getType())
        def originalApplicationId = pluginConfig.original_application_id
    }
}
复制代码

Multi-channel configuration

The configuration code for the most total multi-channel is as follows:

class MyPlugin  implements Plugin<Project> {

    def config = null
    def isKoreaTask = false
    def isIndiaTask = false

    @Override
    public void apply(Project project) {
    
        def appExtension = project.extensions.findByType(AppExtension.class)
        
        //判断当前执行的task是哪个渠道
        checkTask(project)
    
        def pluginConfigStr = FileUtils.readFileFromAppFolder(project.projectDir, "plugin-config.json")
        PluginConfig pluginConfig = new Gson().fromJson(pluginConfigStr, new TypeToken<PluginConfig>() {
            }.getType())
        def originalApplicationId = pluginConfig.original_application_id
    
        //根据包名获取配置文件
        getPluginConfigFromNet(originalApplicationId)
    
        if(config != null){
            //根据配置文件配置多渠道
            if(config.korea.isEnable()||config.india.isEnable()){
                //必须配置dimension否则编译会报错
                appExtension.getFlavorDimensionList().add("country")
                appExtension.productFlavors.register("original", {
                    dimension "country"
                })
                if (config.korea.isEnable()) {
                    appExtension.productFlavors.register("Korea", {
                        dimension "country"
                        applicationId config.korea.application_id
                    })
                }
                if (config.india.isEnable()) {
                    appExtension.productFlavors.register("India", {
                        dimension "country"
                        applicationId config.india.application_id
                    })
                }
            }
        }
        
        project.afterEvaluate {
            //添加不同的依赖
            project.dependencies {
                if(isKoreaTask){
                    implementation("korea lib")
                }
                if(isIndiaTask){
                    implementation("india lib")
                }
            }
        }
    }
    
    static void getPluginConfigFromNet(String packageName) {
        def url = "https://downloadpath/" + packageName + "/config.json"

        def okHttpClient = new OkHttpClient.Builder().build();
        def request = new Request.Builder()
            .url(url)
            .build();
        def call = client.newCall(request);
        try {
            Response response = call.execute();
            if(response.isSuccessful()){
                 if (response.body() != null) {
                     def responseBodyString = responseBody.string()
                     config = gson.fromJson(responseBodyString, new TypeToken<SdkConfig>() {
                        }.getType())
                 }
            }
            response.close();
        } catch (IOException | JsonIOException | JsonSyntaxException e) {
            e.printStackTrace();
        }
    }
    
    private static boolean checkTask(Project project) {
        project.gradle.getStartParameter().getTaskNames().each {
            isKoreaTask = it.contains("korea")
            isIndiaTask = it.contains("india")
        }
    }
}
复制代码

After generating the multi-channel, you can select the corresponding one build variant, as shown below:

Enterprise WeChat screenshot_16483495453293.png

Guess you like

Origin juejin.im/post/7079608511759384590