The cause of

The 2020-04-13 work encounter a problem, third-party so application reference library, jars, and initializes the Java code, there are differences in different car phones, I naturally think of, the Gradle productFlavor should be able to handle, but try a day didn’t succeed, give up.

A little unconvinced, I decided to take a look at Gradle’s source code. Because I know very little about Gradle, I can only look hard and guess while looking, which is not guaranteed to be right. But if you don’t write it down, you’ll soon forget it.

The entrance

Android in build.gradle should be in build.gradle

META-INF/gradle-plugins/com.android.application.property

implementation-class=com.android.build.gradle.AppPlugin

class AppPlugin extends AbstractAppPlugin {
    @Override
    @NonNull
    protected Class<? extends AppExtension> getExtensionClass() {
        return BaseAppModuleExtension.class;
    }
}

public abstract class AbstractAppPlugin extends BasePlugin<AppExtensionImpl> {
    @NonNull
    @Override
    protected BaseExtension createExtension(...) {
        return project.getExtensions()
                .create("android", getExtensionClass(), ...);
    }
}
Copy the code

CreateExtension () reads android from gradle and parses it into getExtensionClass(), which in application cases is BaseAppModuleExtension. In lib-type projects, LibraryExtension.

META-INF/gradle-plugins/android-library.property

implementation-class=com.android.build.gradle.LibraryPlugin

public class LibraryPlugin extends BasePlugin<LibraryExtensionImpl> {
    @NonNull
    @Override
    protected BaseExtension createExtension(...) {
        return project.getExtensions()
                .create("android", getExtensionClass(), ...) ; } @NonNull protected Class<? extends BaseExtension>getExtensionClass() {
        returnLibraryExtension.class; }}Copy the code

The actual android classes are different depending on the project type.

Forget library for a moment and move on to the Application, BaseAppModuleExtension class:

open class BaseAppModuleExtension(...) : AppExtension(...) { var dynamicFeatures: MutableSet<String> = mutableSetOf() val bundle: BundleOptions... fun bundle(action: Action<BundleOptions>)... }Copy the code

The content should be the new App Bundle. I haven’t read about it yet, so I’ll skip it and look at the parent class AppExtension

public class AppExtension extends TestedExtension {
    private final DefaultDomainObjectSet<ApplicationVariant> applicationVariantList
            = new DefaultDomainObjectSet<ApplicationVariant>(ApplicationVariant.class);
    
    public DomainObjectSet<ApplicationVariant> getApplicationVariants() {
        returnapplicationVariantList; } @Override public void addVariant(BaseVariant variant) { applicationVariantList.add((ApplicationVariant) variant); }}Copy the code

Now there are two problems:

  1. What is ApplicationVariant
  2. What is DomainObjectSet

What is Q1 ApplicationVariant

For question 1, I found a code that defines the file name for packaging APK:

// Instead of lambda, use the interface directly, Variants. All (new Action<ApplicationVariant>() {@override void execute(ApplicationVariant) applicationVariant) { applicationVariant.outputs.forEach(new Consumer<BaseVariantOutput>() { @Override void accept(BaseVariantOutput baseVariantOutput) { println"baseVariantOutput.baseName = ${baseVariantOutput.baseName}"
                println "baseVariantOutput.dirName = ${baseVariantOutput.dirName}"
                println "baseVariantOutput.name = ${baseVariantOutput.name}"
                baseVariantOutput.outputFileName = "Hello_${applicationVariant.buildType.name}_Time.apk"

                // println "baseVariantOutput.class.name = ${baseVariantOutput.class.name}"
                println "baseVariantOutput.outputFileName = ${baseVariantOutput.outputFileName}"} }) } }) baseVariantOutput.baseName = debug baseVariantOutput.dirName = baseVariantOutput.name = debug baseVariantOutput.baseName = release baseVariantOutput.dirName = baseVariantOutput.name = release baseVariantOutput.class.name = com.android.build.gradle.internal.api.ApkVariantOutputImpl_Decorated baseVariantOutput.outputFileName = Hello_debug_Time.apk baseVariantOutput.outputFileName = Hello_release_Time.apk public  interface BaseVariantOutput extends OutputFile { ProcessAndroidResources getProcessResources(); TaskProvider<ProcessAndroidResources> getProcessResourcesProvider(); ManifestProcessorTask getProcessManifest(); TaskProvider<ManifestProcessorTask> getProcessManifestProvider(); Task getAssemble(); String getName(); String getBaseName(); String getDirName(); // interface OutputFile extends VariantOutput File getOutputFile(); // interface VariantOutput String getOutputType(); Collection<String> getFilterTypes(); Collection<FilterData> getFilters(); OutputFile getMainOutputFile(); Collection<? extends OutputFile> getOutputs(); }Copy the code

You have a place very puzzled with this code, the baseName, name, dirName, these three variables can point it out directly, but not outputFileName, BaseVariantOutput all the way to check the parent class, this variable is not found, where it came from. Until I print its class: in the superclass BaseVariantOutputImpl ApkVariantOutputImpl defines outputFileName. The setter/getter.

The implementation class for ApplicationVariant is: . Com. Android. Build. Gradle. Internal API. ApplicationVariantImpl_Decorated, same parameter ApplicationVariant interfaces, buildType defined in the implementation class.

This syntax is so counterintuitive to Java that it makes writing code very difficult.

. At the same time also saw, applicationVariant buildType = release/debug, and they are

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

If we define other types in buildTypes, such as internal, that traversal will print it out as well.

Leaving customization aside for the moment, how is the default debug added by default? The implementation is a coincidence:

ApplicationVariantFactory
@Override
public void createDefaultComponents(
        @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
        @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
        @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
    // must create signing config first so that build type 'debug'can be initialized // with the debug signing config. signingConfigs.create(DEBUG); buildTypes.create(DEBUG); buildTypes.create(RELEASE); } called by BasePlugin: private void configureExtension() -> private void basePluginApply(@NonNull Project project) -> public final void Apply (@ NonNull Project Project). | entry the original AbstractAppPlugin createExtension () is in the apply () method is called, as a plug-in added to the Project.Copy the code

As you can see, signingConfigs also specifies the default debug value, which is why: Debug mode can be run directly, but release mode cannot be run, because the signing tool is not configured by default.

At this time there is a new problem, NamedDomainObjectContainer is what things? Gradle’s class inheritance is a bit messy, but it is related to DomainObjectSet, so let’s put it in Q2.

Q2 A bunch of subclasses of DomainObjectContainer

Public interface DomainObjectCollection<T> extends Collection<T> {// Create an instance based on type <S extends T>  withType(Class<S>type);
    <S extends T> DomainObjectCollection<S> withType(Class<S> type, Action<? super S> configureAction);
    <S extends T> DomainObjectCollection<S> withType(Class<S> type, Closure configureClosure); DomainObjectCollection<T> matching(Spec<? super T> spec); DomainObjectCollection<T> matching(Closure spec); // From the name looks like add/remove callback Action<? super T> whenObjectAdded(Action<? super T> action); void whenObjectAdded(Closure action); Action<? super T> whenObjectRemoved(Action<? super T> action); void whenObjectRemoved(Closure action); Void all(Action<? super T> action); void all(Closure action); Collection<T> findAll(Closure Spec); }Copy the code

The withType method doesn’t have a call yet. From other methods, you can guess that it encapsulates the Collection. For example, the subclass public Interface DomainObjectSet

extends DomainObjectCollection

. Set

means that the Collection it wants to encapsulate is a Set and does not want duplicate values. Gradle = build.gradle = build.gradle = build.gradle = build.gradle


public interface NamedDomainObjectContainer<T> extends NamedDomainObjectSet<T> { T create(String name); . } public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T> { Namer<T> getNamer(); . } println"android.buildTypes.class.name = ${android.buildTypes.class.name}"
android.buildTypes.class.name = org.gradle.api.internal.FactoryNamedDomainObjectContainer_Decorated
Copy the code

NamedDomainObjectContainer. Create (name) is in the front createDefaultComponents () see a call, but the implementation class is not found in the source code, how can’t see the specific implementation, but you can guess:

The generic here means that it wants to maintain a list of objects of type T, and create(name) generates new T objects according to the parameter name and adds them to the list.

For example, buildtypes.create (“debug”) creates a BuildType with the name DEBUG. When you define an internal in build.gradle.buildTypes, The guess is that buildTypes. Create (“internal”) will eventually create a BuildType with the name internal.

The next article

Next look at the AppExtension extends TestedExtension parent class