本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
一、写在前面
本系列适合:对gradle基础有一定了解。由于gradle已经出来很久了,相关配置文章也比较多,所以就不从头开始说了,这里推荐几篇文章:
https://www.jianshu.com/p/8b8a550246bd
刚哥(任玉刚)亲笔:gradle系列文章(这里我想催更!)
二、为什么要学gradle
Android studio已经出来很久了,相信大部分公司都已经从eclipse转AS了,反正我都快忘记eclipse如何操作了。AS很多强大功能,其中很突出的一项就是gradle构建。还记得第一次用依赖的时候,那感觉爽翻。但是因为build代码不熟悉,也遇到很多坑,经常会莫名其妙报错,当时只能上网查,然后一板一眼的配置。作为程序猿这种不能完全掌握的感觉是最不爽的,很早就想彻底掌控它了。
其次,作为有梦想的咸鱼,不能只做代码的搬运工,这种高阶必备的知识点还是需要掌握的。比如国内比较火热的插件化、热更新都会涉及到gradle插件知识,所以想要进阶,必须掌握gradle。
三、自定义gradle插件
通过学习上文推荐文章,我们已经了解到,gradle就是构建工具,他使用的语言是groovy,我们可以在build.gradle里面写代码来控制,当然,如果代码很多,希望单独提取出来,那么可以使用自定义gradle插件来实现,没错,我们的主角:AndroidDSL(plugin)就是一个自定义插件而已,所以学习它之前需要了解如何自定义gradle插件。
首先,我们新建一个项目,会得到两个build.gradle,一个是主项目的,一个是全局的。我们先只看项目里的build文件,初始状态如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.xtu.neo.mylibrary"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
其中自定义插件的重点:
apply plugin: 'com.android.application'
这就表示我们引入了Android的插件了,下面来演示一下最简单的自定义插件步骤。
事实上所有的自定义插件都需要继承一个plugin类,然后重写apply方法,如下:
apply plugin: com.atom.MyPlugin
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
println "myPlugin invoked!"
}
}
把上述代码加到build.gradle下面,在命令行运行随意的命令:gradlew clean(windows)
调用成功了,当然这是最简单的方式,不过理解这里就能继续看AndroidDSL了
对于自定义插件的步骤我就偷偷懒了,直接给个链接吧
https://blog.youkuaiyun.com/huachao1001/article/details/51810328
四、Android Plugin源码解析
对于如何查看源码,还得感谢刚哥星球的大牛们,其实很简单,只需要把全局build.gradle里的classpath的依赖加入项目build.gradle文件的dependencies里就好了,如下图:
这样就能在项目的依赖树里找到源码了,可以选择复制出来看,也可以直接在AS里看,个人感觉AS也挺方便的
打开第一个,就能看见很多plugin展现在我们眼前了,我们最熟悉的就是AppPlugin和LibraryPlugin了
前者就是主项目需要依赖的插件,后者就是组件化的module需要依赖的插件
我们拿最常用的AppPlugin来说把,根据上面定义插件的步骤,我们就直接看apply方法,由于Appplugin继承了basePlugin,所以又转到basePlugin:
public void apply(@NonNull Project project) {
//省略一些初始化及错误检查代码
//初始化线程信息记录者
threadRecorder = ThreadRecorder.get();
//保存一些基础信息
ProcessProfileWriter.getProject(project.getPath())
.setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
.setAndroidPlugin(getAnalyticsPluginType())
.setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST)
.setOptions(AnalyticsUtil.toProto(projectOptions));
BuildableArtifactImpl.Companion.disableResolution();
//判断是不是新的API,这里我们只看最新实现,老的就不多说了
if (!projectOptions.get(BooleanOption.ENABLE_NEW_DSL_AND_API)) {
TaskInputHelper.enableBypass();
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
project.getPath(),
null,
this::configureProject);
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
project.getPath(),
null,
this::configureExtension);
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
project.getPath(),
null,
this::createTasks);
} else {
//省略以前的实现
}
}
其实最重要的实现在于调用了三次threadRecorder.record,值得一说的是:this::configureProject这种写法
这是JAVA8里lambda语法,等于:()-> this.configureProject(),匿名内部类的简写方式,后面会回调这里。
J8已经出来很久了,相信大家有了一定的了解,这里就不多说。
我们就来看看这个record方法:
@Override
public void record(
@NonNull ExecutionType executionType,
@NonNull String projectPath,
@Nullable String variant,
@NonNull VoidBlock block) {
//刚刚初始化过的单例
ProfileRecordWriter profileRecordWriter = ProcessProfileWriter.get();
//创建GradleBuildProfileSpan的建造者
GradleBuildProfileSpan.Builder currentRecord =
create(profileRecordWriter, executionType, null);
try {
//刚刚提到的回调
block.call();
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
//写入GradleBuildProfileSpan并保存
write(profileRecordWriter, currentRecord, projectPath, variant);
}
}
以上代码做了如下事情:
1、创建GradleBuildProfileSpan.Builder
2、回调方法
3、写入GradleBuildProfileSpan并保存到spans中
我们先不管回调,看1、3的代码,首先create:
private GradleBuildProfileSpan.Builder create(
@NonNull ProfileRecordWriter profileRecordWriter,
@NonNull ExecutionType executionType,
@Nullable GradleTransformExecution transform) {
long thisRecordId = profileRecordWriter.allocateRecordId();
// am I a child ?
@Nullable
Long parentId = recordStacks.get().peek();
long startTimeInMs = System.currentTimeMillis();
final GradleBuildProfileSpan.Builder currentRecord =
GradleBuildProfileSpan.newBuilder()
.setId(thisRecordId)
.setType(executionType)
.setStartTimeInMs(startTimeInMs);
if (transform != null) {
currentRecord.setTransform(transform);
}
if (parentId != null) {
currentRecord.setParentId(parentId);
}
currentRecord.setThreadId(threadId.get());
recordStacks.get().push(thisRecordId);
return currentRecord;
}
代码不少,但是做的事情很简单,就是创建了一个GradleBuildProfileSpan.Builder,并设置了它的threadId、Id、parentId...等等一系列线程相关的东西,并保存在一个双向队列里,并放入threadLocal里解决多线程并发问题。这个threadLocal若不理解的可以移步我的另一篇文章:消息机制:Handler源码解析
接下来是write
private void write(
@NonNull ProfileRecordWriter profileRecordWriter,
@NonNull GradleBuildProfileSpan.Builder currentRecord,
@NonNull String projectPath,
@Nullable String variant) {
// pop this record from the stack.
if (recordStacks.get().pop() != currentRecord.getId()) {
Logger.getLogger(ThreadRecorder.class.getName())
.log(Level.SEVERE, "Profiler stack corrupted");
}
currentRecord.setDurationInMs(
System.currentTimeMillis() - currentRecord.getStartTimeInMs());
profileRecordWriter.writeRecord(projectPath, variant, currentRecord);
}
调用了profileRecordWriter.writeRecord,继续:
/** Append a span record to the build profile. Thread safe. */
@Override
public void writeRecord(
@NonNull String project,
@Nullable String variant,
@NonNull final GradleBuildProfileSpan.Builder executionRecord) {
executionRecord.setProject(mNameAnonymizer.anonymizeProjectPath(project));
executionRecord.setVariant(mNameAnonymizer.anonymizeVariant(project, variant));
spans.add(executionRecord.build());
}
这里使用建造者模式创建了GradleBuildProfileSpan,并保存到了spans里。
关于1、3步骤说了这么多,其实也就是做了这点事情,接下来才是重点了,关于回调:
回头看basePlugin里的3个回调方法configureProject、configureExtension、
createTasks,方法里传的type已经暴露了他们的作用:
1、BASE_PLUGIN_PROJECT_CONFIGURE:plugin的基础设置、初始化工作
2、BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION:EXTENSION的初始化工作
3、BASE_PLUGIN_PROJECT_TASKS_CREATION:plugin的task创建
这三步基本囊括了自定义插件的所有内容,由于篇幅原因,我这里简单先介绍一下第一步,后面再详细解析很重要的后面两步
private void configureProject() {
final Gradle gradle = project.getGradle();
extraModelInfo = new ExtraModelInfo(project.getPath(), projectOptions, project.getLogger());
checkGradleVersion(project, getLogger(), projectOptions);
sdkHandler = new SdkHandler(project, getLogger());
if (!gradle.getStartParameter().isOffline()
&& projectOptions.get(BooleanOption.ENABLE_SDK_DOWNLOAD)) {
SdkLibData sdkLibData = SdkLibData.download(getDownloader(), getSettingsController());
sdkHandler.setSdkLibData(sdkLibData);
}
androidBuilder =
new AndroidBuilder(
project == project.getRootProject() ? project.getName() : project.getPath(),
creator,
new GradleProcessExecutor(project),
new GradleJavaProcessExecutor(project),
extraModelInfo.getSyncIssueHandler(),
extraModelInfo.getMessageReceiver(),
getLogger(),
isVerbose());
dataBindingBuilder = new DataBindingBuilder();
dataBindingBuilder.setPrintMachineReadableOutput(
SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE);
if (projectOptions.hasRemovedOptions()) {
androidBuilder
.getIssueReporter()
.reportWarning(Type.GENERIC, projectOptions.getRemovedOptionsErrorMessage());
}
if (projectOptions.hasDeprecatedOptions()) {
extraModelInfo
.getDeprecationReporter()
.reportDeprecatedOptions(projectOptions.getDeprecatedOptions());
}
// Apply the Java plugin
project.getPlugins().apply(JavaBasePlugin.class);
project.getTasks()
.getByName("assemble")
.setDescription(
"Assembles all variants of all applications and secondary packages.");
// call back on execution. This is called after the whole build is done (not
// after the current project is done).
// This is will be called for each (android) projects though, so this should support
// being called 2+ times.
gradle.addBuildListener(
new BuildListener() {
@Override
public void buildStarted(@NonNull Gradle gradle) {
TaskInputHelper.enableBypass();
BuildableArtifactImpl.Companion.disableResolution();
}
@Override
public void settingsEvaluated(@NonNull Settings settings) {}
@Override
public void projectsLoaded(@NonNull Gradle gradle) {}
@Override
public void projectsEvaluated(@NonNull Gradle gradle) {}
@Override
public void buildFinished(@NonNull BuildResult buildResult) {
// Do not run buildFinished for included project in composite build.
if (buildResult.getGradle().getParent() != null) {
return;
}
sdkHandler.unload();
threadRecorder.record(
ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
project.getPath(),
null,
() -> {
WorkerActionServiceRegistry.INSTANCE
.shutdownAllRegisteredServices(
ForkJoinPool.commonPool());
PreDexCache.getCache()
.clear(
FileUtils.join(
project.getRootProject().getBuildDir(),
FD_INTERMEDIATES,
"dex-cache",
"cache.xml"),
getLogger());
Main.clearInternTables();
});
}
});
gradle.getTaskGraph()
.addTaskExecutionGraphListener(
taskGraph -> {
TaskInputHelper.disableBypass();
Aapt2DaemonManagerService.registerAaptService(
Objects.requireNonNull(androidBuilder.getTargetInfo())
.getBuildTools(),
loggerWrapper,
WorkerActionServiceRegistry.INSTANCE);
for (Task task : taskGraph.getAllTasks()) {
if (task instanceof TransformTask) {
Transform transform = ((TransformTask) task).getTransform();
if (transform instanceof DexTransform) {
PreDexCache.getCache()
.load(
FileUtils.join(
project.getRootProject()
.getBuildDir(),
FD_INTERMEDIATES,
"dex-cache",
"cache.xml"));
break;
}
}
}
});
createLintClasspathConfiguration(project);
}
这个方法主要做了以下几件事情:
1、利用project,初始化了sdkHandler、androidBuilder、dataBindingBuilder等几个必备的对象。
2、依赖了JavaBasePlugin,这个很重要,我们打包需要的“assemble”task就是在其中创建的。
3、对gradle创建做了监听,做了内存、磁盘缓存的工作,你可以在build\intermediates\dex-cache\cache.xml文件下找到JAR包等内容的缓存。
五、结语
本文简单介绍了自定义插件内容,以及如何简单查看Android plugin的源码(当然你也可以下载Android源码查看),并简单梳理了一下插件的执行流程,引出了extensions、task等gradle中较为重要的概念。
后面再一步一步梳理源码,同时介绍gradle部分重要概念,让我们在学习源码的同时,更加深入理解gradle的奥妙。