学习android的同学都知道android工程从使用android studio开发以后就使用了gradle作为工程的构建工具这就导致我们在了解gradle前提下还要对android-gradle-plugin这个插件有所了解 因为gradle其实就是一个容器或者框架基本上什么工程都可以去构建那如何构建成为android工程呢其实主要的原因就是这个android-gradle-plugin在起作用。代码如下
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
这段代码大家肯定已经非常的熟悉了这样就相当于我们为gradle框架引入了android-gradle-plugin插件使用这个插件呢也非常的简单只需要在我们工程的build.gradle文件中如下即可
apply plugin: 'com.android.application'
通过Project的apply方法我们就可以使用我们刚刚引入的android-gradle-plugin插件引入了这个插件以后我们就可以使用插件为我们提供的各种属性和方法如下
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.imooc.demo"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
buildTypes {
debug {
// 测试混淆
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
compileOptions {
sourceCompatibility rootProject.ext.sourceCompatibility
targetCompatibility rootProject.ext.targetCompatibility
}
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
}
其实前面这些东西大家学android的一定非常熟悉也不是我们本文章的重点这里我要问大家一个问题为什么我们可以使用android{}这个闭包并且在android{}这个闭包中去配置各种各样的defaultConfig{},buildTypes{}等等这就要说我们本博客的重点android-gradle-plugin.
既然我们本章的重点是源码分析所以android-gradle-plugin具体如何去使用我们就不再多说下面我们就来看一下android-gradle-plugin的源码是如何工作的。
首先我们来看一下android-gradle-plugin为我们引入了那些核心类(注意只列出核心的包和类)
第一部分核心类
这个包中的所有类就是我们在配置build.gradle脚本的时候所要用到的下面我们挑最核心的类来为大家讲解一下。其实这个包中看着有这么多类核心中的核心呢其实只有以下三个类大家看我的UML图就可以知道他们的关系
大家从图中可以看到其实就这三个核心类这三个类的关系呢也可以从UML中看出来另外两个继承于BaseExtension,LibraryExtension和AppExtension其实就是我们上面在build.gradle中写的那个android{}闭包这两个类中只有一个核心方向就是getXXXVariants.是用来获取我们最终的输出变体的我们后面会讲什么是变体(Varitant)。所以这三个核心配置类中基本所有的配置又都在BaseExtension中去定义了下面我们就来看一下这个BaseExtension中的代码
public abstract class BaseExtension implements AndroidConfig {
private final List> transformDependencies = Lists.newArrayList();
private final AndroidBuilder androidBuilder;
private final SdkHandler sdkHandler;
private final DefaultConfig defaultConfig;
private final AaptOptions aaptOptions;
private final LintOptions lintOptions;
private final ExternalNativeBuild externalNativeBuild;
private final DexOptions dexOptions;
private final TestOptions testOptions;
private final CompileOptions compileOptions;
private final PackagingOptions packagingOptions;
private final JacocoOptions jacoco;
private final Splits splits;
private final AdbOptions adbOptions;
private final NamedDomainObjectContainer productFlavors;
private final NamedDomainObjectContainer buildTypes;
private final NamedDomainObjectContainer signingConfigs;
private final NamedDomainObjectContainer buildOutputs;
private final List deviceProviderList = Lists.newArrayList();
private final List testServerList = Lists.newArrayList();
private final List transforms = Lists.newArrayList();
private final DataBindingOptions dataBinding;
private final NamedDomainObjectContainer sourceSetsContainer;
private String target;
private Revision buildToolsRevision;
private List libraryRequests = Lists.newArrayList();
private List flavorDimensionList;
private String resourcePrefix;
private ExtraModelInfo extraModelInfo;
private String defaultPublishConfig = "release";
private Action variantFilter;
protected Logger logger;
private boolean isWritable = true;
protected Project project;
private final ProjectOptions projectOptions;
boolean generatePureSplits = false;
public String getBuildToolsVersion() {
return this.buildToolsRevision.toString();
}
public void setBuildToolsVersion(String version) {
this.buildToolsVersion(version);
}
public void buildTypes(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.buildTypes);
}
public void productFlavors(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.productFlavors);
}
public void signingConfigs(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.signingConfigs);
}
public void flavorDimensions(String... dimensions) {
this.checkWritability();
this.flavorDimensionList = Arrays.asList(dimensions);
}
public void sourceSets(Action> action) {
this.checkWritability();
action.execute(this.sourceSetsContainer);
}
这个类是个抽象类定义了AppExtension和LibraryExtension中公共的行为实际中使用的还是AppExtension和LibraryExtension.由于这个BaseExtension代码太多我们只取了他的属性和部分方法其他的方法也基本都是对这些属性的操作大家可以自行查看。大家来看这些属性有没有很眼熟的对了像LintOptions,DexOptions,buildTypes,singsConfigs,productFlavors等都可以在这个类中看到所以我们在build.gradle文件中的android{}闭包中可以调用那些方法在我们的这些Extension中都可以找到所以以后想写一个额外的配置直接到这几个XXXExtension中去找对应的方法即可。比如说我们想为不同的flavor的BuildConfig.java类去添加一些属性我们就可以使用BaseFlavor中的buildConfigField()方法去为其添加额外的属性。源码如下
/**
* Adds a new field to the generated BuildConfig class.
*
* <p>The field is generated as: {@code = ;}
*
* </p><p>This means each of these must have valid Java content. If the type is a String, then the
* value should include quotes.
*
* @param type the type of the field
* @param name the name of the field
* @param value the value of the field
*/
public void buildConfigField(
@NonNull String type, @NonNull String name, @NonNull String value) {
ClassField alreadyPresent = getBuildConfigFields().get(name);
if (alreadyPresent != null) {
String flavorName = getName();
if (BuilderConstants.MAIN.equals(flavorName)) {
logger.info(
"DefaultConfig: buildConfigField '{}' value is being replaced: {} -> {}",
name,
alreadyPresent.getValue(),
value);
} else {
logger.info(
"ProductFlavor({}): buildConfigField '{}' "
+ "value is being replaced: {} -> {}",
flavorName,
name,
alreadyPresent.getValue(),
value);
}
}
addBuildConfigField(new ClassFieldImpl(type, name, value));
}
这个方法大家也应该可以看懂就是开始检查了一下要添加的数据的合法性如果检查通过则将数据包装成一个ClassFieldImpl去添加到一个map中去最终。所以只要熟悉了这几个Extension类那我们就可以去写任意我们需要的配置而不用再担心看不懂别人写的一些build.gradle配置。
好到这里呢我们就可以随心所欲的去编写build.gradle文件了下面我们就开始分析android-gradle-plugin为我们提供的另外一个包中的核心类例如我们刚刚提到的BaseFlavor类其实并不与我们的XXXExtension类在一个包中而是在我们的gradle-core-3.0.1这个jar包中如图
这就是我们今天要重点讲的第二部分比第一部分的内容要多的多这一部分才是我们android-gradle-plugin功能实现在核心老样子我们还是来看下这个包中有那些核心的类这个包中核心的类就比较多了我们来分块看一下。
在看代码之前我们要先讲一个概念就是变体Variant,前面我们就提到过的一个概念这个概念是整个android-gradle-plugin最核心的一个概念所有的东西都是在围绕这个Variant去展开的其实一个Variant可以广义的理解为我们生成一个最终的apk过程上中所有的属性方法和Task.也就是说一个Variant定义了我们生成一个Apk所有的东西比如我们可以通过Variant去定义Apk生成的名字路径等等因为我们有Flavor的存在所以一次构建会有多个apk或者aar的输出所以也会有多个Variant的存在。好在理解了这个 Varaint的概念以后我们首先来看源码的第一部分API
第一部分API就有这么多的类不急我们看源码不会看他的所有类只要掌握最核心的那几个类就可以在讲最核心的类之前呢 我们来说一下API这一部分的代码呢就是我们在写build.gradle文件时需要操作Variant时用到所有API部分我们在操作Variant时是不会用到其它类的只会用到这一部分。例如我们在通过Vairant去改变Apk的名字或者生成路径的时候我们用到的API就是ApplicationVariant这个类通过这个类我们就可以去修改到apk的相关东西。这个API包中最核心的类呢其实也只有一个那就是BaseVariant原理与我们前面提到的BaseExtension是一样的在BaseVariant中定义了所有变体最核心最通用的东西下面我们看一下这个BaseVariant这个接口中的源码
/**
* A Build variant and all its public data. This is the base class for items common to apps,
* test apps, and libraries
* 通过上面的这个英文注释大家也可以再次加深对Variant的理解
*/
public interface BaseVariant {
/**
* Returns the name of the variant. Guaranteed to be unique.
*/
@NonNull
String getName();
/**
* Returns a description for the build variant.
*/
@NonNull
String getDescription();
/**
* Returns a subfolder name for the variant. Guaranteed to be unique.
*
* This is usually a mix of build type and flavor(s) (if applicable).
* For instance this could be:
* "debug"
* "debug/myflavor"
* "release/Flavor1Flavor2"
*/
@NonNull
String getDirName();
/**
* Returns the base name for the output of the variant. Guaranteed to be unique.
*/
@NonNull
String getBaseName();
/**
* Returns the flavor name of the variant. This is a concatenation of all the
* applied flavors
* @return the name of the flavors, or an empty string if there is not flavors.
*/
@NonNull
String getFlavorName();
/**
* Returns the variant outputs. There should always be at least one output.
*
* @return a non-null list of variants.
*/
@NonNull
DomainObjectCollection getOutputs();
/**
* Returns the {@link com.android.builder.core.DefaultBuildType} for this build variant.
*/
@NonNull
BuildType getBuildType();
/**
* Returns a {@link com.android.builder.core.DefaultProductFlavor} that represents the merging
* of the default config and the flavors of this build variant.
*/
@NonNull
ProductFlavor getMergedFlavor();
/**
* Returns a {@link JavaCompileOptions} that represents the java compile settings for this build
* variant.
*/
@NonNull
JavaCompileOptions getJavaCompileOptions();
/**
* Returns the list of {@link com.android.builder.core.DefaultProductFlavor} for this build
* variant.
*
* </p><p>This is always non-null but could be empty.
*/
@NonNull
List getProductFlavors();
/**
* Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier
* items are meant to be overridden by later items.
*
* @return a list of source provider
*/
@NonNull
List getSourceSets();
/**
* Returns a list of FileCollection representing the source folders.
*
* @param folderType the type of folder to return.
* @return a list of folder + dependency as file collections.
*/
@NonNull
List getSourceFolders(@NonNull SourceKind folderType);
/** Returns the configuration object for the compilation */
@NonNull
Configuration getCompileConfiguration();
/** Returns the configuration object for the annotation processor. */
@NonNull
Configuration getAnnotationProcessorConfiguration();
/** Returns the configuration object for the runtime */
@NonNull
Configuration getRuntimeConfiguration();
/** Returns the applicationId of the variant. */
@NonNull
String getApplicationId();
/** Returns the pre-build anchor task */
@NonNull
Task getPreBuild();
/**
* Returns the check manifest task.
*/
@NonNull
Task getCheckManifest();
/**
* Returns the AIDL compilation task.
*/
@NonNull
AidlCompile getAidlCompile();
/**
* Returns the Renderscript compilation task.
*/
@NonNull
RenderscriptCompile getRenderscriptCompile();
/**
* Returns the resource merging task.
*/
@Nullable
MergeResources getMergeResources();
/**
* Returns the asset merging task.
*/
@Nullable
MergeSourceSetFolders getMergeAssets();
/**
* Returns the BuildConfig generation task.
*/
@Nullable
GenerateBuildConfig getGenerateBuildConfig();
/**
* Returns the Java Compilation task if javac was configured to compile the source files.
* @deprecated prefer {@link #getJavaCompiler} which always return the java compiler task
* irrespective of which tool chain (javac or jack) used.
*/
@Nullable
@Deprecated
JavaCompile getJavaCompile() throws IllegalStateException;
/**
* Returns the Java Compiler task which can be either javac or jack depending on the project
* configuration.
*/
@NonNull
Task getJavaCompiler();
/**
* Returns the java compilation classpath.
*
* </p><p>The provided key allows controlling how much of the classpath is returned.
*
* </p><ul><li>
* </li><li>if <code>null</code>, the full classpath is returned
* </li><li>Otherwise the method returns the classpath up to the generated bytecode associated with
* the key
* </li></ul>
*
* @param key the key
* @see #registerGeneratedBytecode(FileCollection)
*/
@NonNull
FileCollection getCompileClasspath(@Nullable Object key);
/**
* Returns the java compilation classpath as an ArtifactCollection
*
* <p>The provided key allows controlling how much of the classpath is returned.
*
* </p><ul><li>
* </li><li>if <code>null</code>, the full classpath is returned
* </li><li>Otherwise the method returns the classpath up to the generated bytecode associated with
* the key
* </li></ul>
*
* @param key the key
* @see #registerGeneratedBytecode(FileCollection)
*/
@NonNull
ArtifactCollection getCompileClasspathArtifacts(@Nullable Object key);
/**
* Returns the file and task dependency of the folder containing all the merged data-binding
* artifacts coming from the dependency.
*
* <p>If data-binding is not enabled the file collection will be empty.
*
* @return the file collection containing the folder or nothing.
*/
@NonNull
FileCollection getDataBindingDependencyArtifacts();
/** Returns the NDK Compilation task. */
@NonNull
NdkCompile getNdkCompile();
/**
* Returns the tasks for building external native projects.
*/
Collection getExternalNativeBuildTasks();
/**
* Returns the obfuscation task. This can be null if obfuscation is not enabled.
*/
@Nullable
Task getObfuscation();
/**
* Returns the obfuscation mapping file. This can be null if obfuscation is not enabled.
*/
@Nullable
File getMappingFile();
/**
* Returns the Java resource processing task.
*/
@NonNull
AbstractCopyTask getProcessJavaResources();
/**
* Returns the assemble task for all this variant's output
*/
@Nullable
Task getAssemble();
/**
* Adds new Java source folders to the model.
*
* These source folders will not be used for the default build
* system, but will be passed along the default Java source folders
* to whoever queries the model.
*
* @param sourceFolders the source folders where the generated source code is.
*/
void addJavaSourceFoldersToModel(@NonNull File... sourceFolders);
/**
* Adds new Java source folders to the model.
*
* These source folders will not be used for the default build
* system, but will be passed along the default Java source folders
* to whoever queries the model.
*
* @param sourceFolders the source folders where the generated source code is.
*/
void addJavaSourceFoldersToModel(@NonNull Collection sourceFolders);
/**
* Adds to the variant a task that generates Java source code.
*
* This will make the generate[Variant]Sources task depend on this task and add the
* new source folders as compilation inputs.
*
* The new source folders are also added to the model.
*
* @param task the task
* @param sourceFolders the source folders where the generated source code is.
*/
void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
/**
* Adds to the variant a task that generates Java source code.
*
* This will make the generate[Variant]Sources task depend on this task and add the
* new source folders as compilation inputs.
*
* The new source folders are also added to the model.
*
* @param task the task
* @param sourceFolders the source folders where the generated source code is.
*/
void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection sourceFolders);
/**
* Register the output of an external annotation processor.
*
* </p><p>The output is passed to the javac task, but the source generation hooks does not depend on
* this.
*
* </p><p>In order to properly wire up tasks, the FileTree object must include dependency
* information about the task that generates the content of this folders.
*
* @param folder a ConfigurableFileTree that contains a single folder and the task dependency
* information
*/
void registerExternalAptJavaOutput(@NonNull ConfigurableFileTree folder);
/**
* Adds to the variant new generated resource folders.
*
* </p><p>In order to properly wire up tasks, the FileCollection object must include dependency
* information about the task that generates the content of this folders.
*
* @param folders a FileCollection that contains the folders and the task dependency information
*/
void registerGeneratedResFolders(@NonNull FileCollection folders);
/**
* Adds to the variant a task that generates Resources.
*
* This will make the generate[Variant]Resources task depend on this task and add the
* new Resource folders as Resource merge inputs.
*
* The Resource folders are also added to the model.
*
* @param task the task
* @param resFolders the folders where the generated resources are.
*
* @deprecated Use {@link #registerGeneratedResFolders(FileCollection)}
*/
@Deprecated
void registerResGeneratingTask(@NonNull Task task, @NonNull File... resFolders);
/**
* Adds to the variant a task that generates Resources.
*
* This will make the generate[Variant]Resources task depend on this task and add the
* new Resource folders as Resource merge inputs.
*
* The Resource folders are also added to the model.
*
* @param task the task
* @param resFolders the folders where the generated resources are.
*
* @deprecated Use {@link #registerGeneratedResFolders(FileCollection)}
*/
@Deprecated
void registerResGeneratingTask(@NonNull Task task, @NonNull Collection resFolders);
/**
* Adds to the variant new generated Java byte-code.
*
* </p><p>This bytecode is passed to the javac classpath. This is typically used by compilers for
* languages that generate bytecode ahead of javac.
*
* </p><p>The file collection can contain either a folder of class files or jars.
*
* </p><p>In order to properly wire up tasks, the FileCollection object must include dependency
* information about the task that generates the content of these folders. This is generally
* setup using {@link org.gradle.api.file.ConfigurableFileCollection#builtBy(Object...)}
*
* </p><p>The generated byte-code will also be added to the transform pipeline as a {@link
* com.android.build.api.transform.QualifiedContent.Scope#PROJECT} stream.
*
* </p><p>The method returns a key that can be used to query for the compilation classpath. This
* allows each successive call to {@link #registerPreJavacGeneratedBytecode(FileCollection)} to
* be associated with a classpath containing everything <strong>before</strong> the added
* bytecode.
*
* @param fileCollection a FileCollection that contains the files and the task dependency
* information
* @return a key for calls to {@link #registerGeneratedBytecode(FileCollection)}
*/
Object registerPreJavacGeneratedBytecode(@NonNull FileCollection fileCollection);
/** @deprecated use {@link #registerPreJavacGeneratedBytecode(FileCollection)} */
@Deprecated
Object registerGeneratedBytecode(@NonNull FileCollection fileCollection);
/**
* Adds to the variant new generated Java byte-code.
*
* </p><p>This bytecode is meant to be post javac, which means javac does not have it on its
* classpath. It's is only added to the java compilation task's classpath and will be added to
* the transform pipeline as a {@link
* com.android.build.api.transform.QualifiedContent.Scope#PROJECT} stream.
*
* </p><p>The file collection can contain either a folder of class files or jars.
*
* </p><p>In order to properly wire up tasks, the FileCollection object must include dependency
* information about the task that generates the content of these folders. This is generally
* setup using {@link org.gradle.api.file.ConfigurableFileCollection#builtBy(Object...)}
*
* @param fileCollection a FileCollection that contains the files and the task dependency
* information
*/
void registerPostJavacGeneratedBytecode(@NonNull FileCollection fileCollection);
/**
* Adds a variant-specific BuildConfig field.
*
* @param type the type of the field
* @param name the name of the field
* @param value the value of the field
*/
void buildConfigField(@NonNull String type, @NonNull String name, @NonNull String value);
/**
* Adds a variant-specific res value.
* @param type the type of the field
* @param name the name of the field
* @param value the value of the field
*/
void resValue(@NonNull String type, @NonNull String name, @NonNull String value);
/**
* Set up a new matching request for a given flavor dimension and value.
*
* </p><p>To learn more, read <a href="d.android.com/r/tools/use-flavorSelection.html">Select
* default flavors for missing dimensions</a>.
*
* @param dimension the flavor dimension
* @param requestedValue the flavor name
*/
void missingDimensionStrategy(@NonNull String dimension, @NonNull String requestedValue);
/**
* Set up a new matching request for a given flavor dimension and value.
*
* </p><p>To learn more, read <a href="d.android.com/r/tools/use-flavorSelection.html">Select
* default flavors for missing dimensions</a>.
*
* @param dimension the flavor dimension
* @param requestedValues the flavor name and fallbacks
*/
void missingDimensionStrategy(@NonNull String dimension, @NonNull String... requestedValues);
/**
* Set up a new matching request for a given flavor dimension and value.
*
* </p><p>To learn more, read <a href="d.android.com/r/tools/use-flavorSelection.html">Select
* default flavors for missing dimensions</a>.
*
* @param dimension the flavor dimension
* @param requestedValues the flavor name and fallbacks
*/
void missingDimensionStrategy(@NonNull String dimension, @NonNull List requestedValues);
/**
* If true, variant outputs will be considered signed. Only set if you manually set the outputs
* to point to signed files built by other tasks.
*/
void setOutputsAreSigned(boolean isSigned);
/**
* @see #setOutputsAreSigned(boolean)
*/
boolean getOutputsAreSigned();
}
从头看下来以后通过这个类我们几乎可以得到关于Variant的所有信息例如Variant的name, description, buildtype等当然还可以得到在生成最终Apk过程中的相关Task, Task我们放在最后去看因为是最复杂的一部分。
既然第一部分都是些API部分让我们直接程序员去直接调用那这些接口的实际实现都在那里呢我们看源码当然不能只看这些空的API了那么上面的这些API的具体实现就是我们要去看的第二部分如图
所有API的实现呢都在internal/api这个包中大家从类的名字也可以看出来后面基本都带有impl表示实现的意思那既然我们的接口中核心是BaseVariant这个接口所以我们可以确定他的实现中BaseVarintImpl当然也是实现中的核心所以我们就来看下BaseVarintImpl这个实现类的源码
public abstract class BaseVariantImpl implements BaseVariant {
@NonNull private final ObjectFactory objectFactory;
@NonNull protected final AndroidBuilder androidBuilder;
@NonNull protected final ReadOnlyObjectProvider readOnlyObjectProvider;
@NonNull protected final NamedDomainObjectContainer outputs;
BaseVariantImpl(
@NonNull ObjectFactory objectFactory,
@NonNull AndroidBuilder androidBuilder,
@NonNull ReadOnlyObjectProvider readOnlyObjectProvider,
@NonNull NamedDomainObjectContainer outputs) {
this.objectFactory = objectFactory;
this.androidBuilder = androidBuilder;
this.readOnlyObjectProvider = readOnlyObjectProvider;
this.outputs = outputs;
}
@NonNull
protected abstract BaseVariantData getVariantData();
public void addOutputs(@NonNull List outputs) {
this.outputs.addAll(outputs);
}
@Override
@NonNull
public String getName() {
return getVariantData().getVariantConfiguration().getFullName();
}
@Override
@NonNull
public String getDescription() {
return getVariantData().getDescription();
}
@Override
@NonNull
public String getDirName() {
return getVariantData().getVariantConfiguration().getDirName();
}
@Override
@NonNull
public String getBaseName() {
return getVariantData().getVariantConfiguration().getBaseName();
}
@NonNull
@Override
public String getFlavorName() {
return getVariantData().getVariantConfiguration().getFlavorName();
}
@NonNull
@Override
public DomainObjectCollection getOutputs() {
return outputs;
}
@Override
@NonNull
public BuildType getBuildType() {
return readOnlyObjectProvider.getBuildType(
getVariantData().getVariantConfiguration().getBuildType());
}
@Override
@NonNull
public List getProductFlavors() {
return new ImmutableFlavorList(
getVariantData().getVariantConfiguration().getProductFlavors(),
readOnlyObjectProvider);
}
@Override
@NonNull
public ProductFlavor getMergedFlavor() {
return getVariantData().getVariantConfiguration().getMergedFlavor();
}
}
这里我没有完全copy出源码来为了不让我们的版面看起来太多从我copy出来的这几个方法大家可以看到他确实继成了我们的BaseVariant这个接口然后对这些方法都进行了实现看他的实现的时候我们可以发现几乎都是调用了getVariantData()这个方法里的实现所以我们可以再往下看源码的时候就要看BaseVariantData这个类究尽是如何实现这些方法的我们拿一个比较常用的buildConfigField这个方法来看一下源码如下
@Override
public void buildConfigField(
@NonNull String type, @NonNull String name, @NonNull String value) {
getVariantData().getVariantConfiguration().addBuildConfigField(type, name, value);
}
可以看到BaseVariantImpl中对于buildConfigField的实现就是调用BaseVariantData中的configuration实现的我们来看一下最终他是如何去实现的代码如下
private final Map mBuildConfigFields;
public void addBuildConfigField(String type, String name, String value) {
ClassField classField = new ClassFieldImpl(type, name, value);
this.mBuildConfigFields.put(name, classField);
}
可以看到最后的实现就是将我们的数据包装成ClassField放到了一个map中没有任何复杂的地方。
我们现来分析一个在BaseVariantImpl中有一个方法如下
@Override
public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection sourceFolders) {
getVariantData().registerJavaGeneratingTask(task, sourceFolders);
}
这个方法的作用是我们可以通过此方法定义一个Task去生成一个java文件同时可以让生成的java文件被编译成class字节码他的实现同样是通过BaseVariantData实现的我们来看一下具体的实现
public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection generatedSourceFolders) {
Preconditions.checkNotNull(javacTask);
//将插件原来的源码生成Task依赖于我们的Task,保证我们Task也能被执行到
sourceGenTask.dependsOn(task);
final Project project = scope.getGlobalScope().getProject();
if (extraGeneratedSourceFileTrees == null) {
extraGeneratedSourceFileTrees = new ArrayList<>();
}
for (File f : generatedSourceFolders) {
ConfigurableFileTree fileTree = project.fileTree(f).builtBy(task);
//将外部生成的java文件添到加源码树中
extraGeneratedSourceFileTrees.add(fileTree);
//将我们生成的java文件树添加到javaC Task中这样我们额外生成的类也可以被编译成class字节码
javacTask.source(fileTree);
}
addJavaSourceFoldersToModel(generatedSourceFolders);
}
好通过我在源码中添加的注释相信大家可以知道这个方法为什么可以动态的去添加java文件的生成和编译了。所以如果大家对其它方法的实现感兴趣可以重点去看这两个类中的实现即可所以在我们第二部分中最重要的两个类就是BaseVaraintImpl和BaseVariantData其它的类都是一些辅助类。
第三个比较重要的部分就是一些其它类的实现下面我们来看一下这些类所在的一个位置
在这个dsl包中的类呢就是一些辅助BaseVariant中的一些类比如我们的BuildType构建类型BaseFlavor渠道类等等这些类大家应该看起来比较容易理解所以如果大家想看在buildType{}中可以调用那些方法就直接去看BuildType类中有那些方法即可下面我们来看一个我们最常用的DefalueConfig类这个类就对应于我们在build.gradle文件中写defaultConfig{},我们来看一下他的源码
/** DSL object for the defaultConfig object. */
@SuppressWarnings({"WeakerAccess", "unused"}) // Exposed in the DSL.
public class DefaultConfig extends BaseFlavor {
public DefaultConfig(
@NonNull String name,
@NonNull Project project,
@NonNull Instantiator instantiator,
@NonNull Logger logger,
@NonNull ErrorReporter errorReporter) {
super(name, project, instantiator, logger, errorReporter);
}
}
可见这个类是继成于我们前面提到过的BaseFlavor类我们再来看一下BaseFlavor中的源码
public void minSdkVersion(int minSdkVersion) {
setMinSdkVersion(minSdkVersion);
}
public void setMinSdkVersion(@Nullable String minSdkVersion) {
setMinSdkVersion(getApiVersion(minSdkVersion));
}
/**
* Sets minimum SDK version.
*
* </p><p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
* uses-sdk element documentation</a>.
*/
public void minSdkVersion(@Nullable String minSdkVersion) {
setMinSdkVersion(minSdkVersion);
}
@NonNull
public com.android.builder.model.ProductFlavor setTargetSdkVersion(int targetSdkVersion) {
setTargetSdkVersion(new DefaultApiVersion(targetSdkVersion));
return this;
}
/**
* Sets the target SDK version to the given value.
*
* </p><p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
* uses-sdk element documentation</a>.
*/
public void targetSdkVersion(int targetSdkVersion) {
setTargetSdkVersion(targetSdkVersion);
}
public void setTargetSdkVersion(@Nullable String targetSdkVersion) {
setTargetSdkVersion(getApiVersion(targetSdkVersion));
}
/**
* Sets the target SDK version to the given value.
*
* </p><p>See <a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html">
* uses-sdk element documentation</a>.
*/
public void targetSdkVersion(@Nullable String targetSdkVersion) {
setTargetSdkVersion(targetSdkVersion);
}
大家可以看到我们最常见的minSdkVersion, targetSdkVersion等常用方法。所以这个包中的类也是比较重要的大家有时间的话也可以整体过一下熟悉他们的实现当然无需每行代码都看懂知道他的一些核心方法和作用即可。
源码中的最后一部分是我们整个androd-gradle-plugin最核心也是最复杂的一部分也就是Task部分我们先来看一下Task这部分的源码位置如图
为什么说这最后一部分Task是最重要的呢学习过gradle的人都应该知道Task才是真正的执行逻辑也就是说我们前面通过XXXExtension等进行的各种配置还只是一些配置没有其它任何作用只有结合了我们的Task,才能真正的将前面所有的配置都写到我们最终生成的APK中的对应文件中去也就是前面的各种配置是我们的构思是Task让我们的构思得到了实现举个栗子我们在写build.gradle文件时通过productflavor编写了什么百度google,xiaomi等渠道如果没有对应的Task去处理这些渠道那你怎么写都没有用。
这个包中的Task这么多一个个去看他们的源码显然不太可能下面给大家推荐一篇文章以里有一些重要的Task的一些作用Gradle Android插件用户指南下面我来为大家阅读一个常见的Task的执行前面我们讲解了如何使用buildConfigFiled来为不同的flavor去添加一些属性下面我们来看看通过这个方法添加的属性最后是如何被写入到java源文件中去的。源码如下
@CacheableTask public class GenerateBuildConfig extends BaseTask {
//所有要写入到java源文件的属性存在一个list中
private Supplier> items;
//Task的真正执行逻辑
@TaskAction void generate() throws IOException {
// must clear the folder in case the packagename changed, otherwise,
// there'll be two classes.
File destinationDir = getSourceOutputDir();
FileUtils.cleanOutputDir(destinationDir);
//BuildConfig.java源文件的一个生成辅助类
BuildConfigGenerator generator =
new BuildConfigGenerator(getSourceOutputDir(), getBuildConfigPackageName());
//添加一些取基础属性从这里我们可以知道为什么BuildConfig.java这个类中总是有这些属性的存在
generator.addField("boolean", "DEBUG",
isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
.addField("String", "APPLICATION_ID", '"' + appPackageName.get() + '"')
.addField("String", "BUILD_TYPE", '"' + getBuildTypeName() + '"')
.addField("String", "FLAVOR", '"' + getFlavorName() + '"')
.addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
.addField("String", "VERSION_NAME", '"' + Strings.nullToEmpty(getVersionName()) + '"')
//将我们在外部动态添加的属性一同写入
.addItems(getItems());
List flavors = getFlavorNamesWithDimensionNames();
int count = flavors.size();
if (count > 1) {
for (int i = 0; i < count; i += 2) {
generator.addField("String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"');
}
}
//通过辅助类生成java文件
generator.generate();
}
//这里我把BuildConfigFiledGenerator的核心generate()方法取出大家可以看到这个方法无非就是一个文件的写没有其它复杂的
public void generate() throws IOException {
File pkgFolder = this.getFolderPath();
if(!pkgFolder.isDirectory() && !pkgFolder.mkdirs()) {
throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
} else {
File buildConfigJava = new File(pkgFolder, "BuildConfig.java");
Closer closer = Closer.create();
try {
FileOutputStream fos = (FileOutputStream)closer.register(new FileOutputStream(buildConfigJava));
OutputStreamWriter out = (OutputStreamWriter)closer.register(new OutputStreamWriter(fos, Charsets.UTF_8));
JavaWriter writer = (JavaWriter)closer.register(new JavaWriter(out));
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY", new Object[0]).emitPackage(this.mBuildConfigPackageName).beginType("BuildConfig", "class", PUBLIC_FINAL);
Iterator var7 = this.mFields.iterator();
while(var7.hasNext()) {
ClassField field = (ClassField)var7.next();
emitClassField(writer, field);
}
var7 = this.mItems.iterator();
while(var7.hasNext()) {
Object item = var7.next();
if(item instanceof ClassField) {
emitClassField(writer, (ClassField)item);
} else if(item instanceof String) {
writer.emitSingleLineComment((String)item, new Object[0]);
}
}
writer.endType();
} catch (Throwable var12) {
throw closer.rethrow(var12);
} finally {
closer.close();
}
}
}
}
其它的Task分析流程基本一样大家只要关注他的核心变量和被@TaskAction注解的方法即可。从这个Task我们可以看出无论你前面怎么配置如果没有Task 最后将那个List写入到java源文件中去都是白费所以Task才是真正最核心的地方。其它一些比较重要的Task如ProcessManifest,GenerateResValues等大家也可以过一下他们的核心逻辑。
讲到现在我们可以看到android-gradle-plugin两个核心包中的类虽然挺多但是通过我们进行分析以后我们开发人员重点关注它的一些核心类即可。总后总结一下Variant的概念大家一定要理解最后问大家一个问题这两个核心jar包是如何关联起来的呢就是通过Variant.大家通过下面我的这张UML图就可以看出来如图
通过这张图大家也可以看到两个不同的包的一些核心类的继承关系。好的对android-gradle-plugin的源码分析就到这里大家有什么疑问可以在下方议论留言。欢迎点赞</p>
欢迎关注课程:《Gradle3.0自动化项目构建技术精讲》
热门评论
大佬要是在多点逗号看起来可能会轻松点
厉害厉害。大佬。。。。