深入理解 Android Studio Sync 流程

AI摘要

本文深入剖析Android Studio的Sync流程,介绍Sync过程涉及的各个角色及其作用,从源码层面分析Sync的关键步骤,包括功能入口、BuildAction配置与执行等内容。文章还探讨了如何从产品和技术角度优化Sync流程。

动手点关注

887a89a4782bc5bc8a9ce8451b901752.gif

干货不迷路

1. 初识 Sync

我们一般会把 Sync 理解为 Android Studio 的准备阶段,包括解析工程配置信息、下载远程依赖到本地、更新代码索引等准备工作,当修改 gradle build 文件后,需要重新 Sync 将 Gradle 构建配置信息同步到 IDE,进而使 IDE 的功能及时应用新的构建配置,这些功能包括项目的 Gradle Task 列表展示、依赖信息展示等等。Sync 是 Android Studio 中独有的概念,当通过 Gradle 命令行程序构建 Android 应用时,只会经历 Gradle 定义的 Initialization、Configuration 和 Execution 生命周期,根本没有 Sync 的概念。Android Studio 的 Sync 阶段涉及到 IDE、Gradle、Plugin 等多个角色,梳理清楚这些角色各自的作用和联系,才能清晰理解 Sync 的整体架构,下面分别来介绍这些角色:

ee49de792e8ccf2eb7729cb5f59698d6.png

IDE 层面:Android Studio 基于 IntelliJ IDEA 扩展而来,复用了 IntelliJ IDEA 强大的代码编辑器和开发者工具等 IDE 基础能力,在此之上 Android Studio 提供了更多提高 Android 构建效率的功能,如 Android 模拟器、代码模版等等。另外 Android Studio 也将自身专业的 Android 应用开发能力反哺给 IntelliJ IDEA,以 Android IDEA Plugin 的形式,使 IntelliJ IDEA 支持 Android 应用开发,二者互相赋能,相辅相成。

Gradle 层面:Gradle 是一个灵活而强大的开源构建系统,它除了提供跨平台的可执行程序支持命令行执行 Gradle 构建外,还专门提供了 Gradle Tooling API 编程 SDK,供外部更方便、更紧密的将 Gradle 构建能力嵌入到 IDE 中,IntelliJ IDEA、Eclipse、VSCode 等 IDE 都采用了这种方式。在 Gradle 源码中也有专门服务于 IntelliJ IDEA、Eclipse 等 IDE 的代码模块,构建工具和 IDE 两个角色之间同样是互相赋能,强强联合。

Plugin 层面:Plugin 层面包括 Android IDEA Plugin 和 Android Gradle Plugin,Android IDEA Plugin 为 IntelliJ IDEA/Android Studio 拓展了 Android 应用开发能力;Android Gradle Plugin 为 Gradle 拓展了 Android 应用构建能力。谷歌通过这两个 Plugin 将现代成熟优秀的 IDE 开发能力和构建工具联合在一起为 Android 所用,相比于早期 Eclipse 加 ANT 构建的开发方式,大幅提升了 Android 应用开发效率和体验。

2. Sync 流程分析

了解了 Sync 阶段涉及到的角色以及它们之间的关系后,接下来深入 Android Studio 源码,从代码层面梳理清楚 Sync 的关键流程。

2.1 Android Studio 源码分析

2.1.1 功能入口及准备

在 Android Studio 中触发 Sync 操作后,会从最上层的入口类 GradleSyncInvoker 调用到实际负责解析 Gradle 构建信息的 GradleProjectResolver,调用链如下图所示:

b2053f88ff571cb557d9b6cdc4d3327a.png

调用过程中涉及到的关键类:

  • GradleSyncInvoker:触发 Sync 的入口类,在 Android Studio 多处需要执行 Sync 的地方,都是通过调用此类的 requestProjectSync 方法来触发的

  • ExternalSystemUtil:GradleSyncInvoker、GradleSyncExecutor 等类是专门针对 Sync 功能的封装,Sync 是 Android Studio 中独有的操作,在 IntelliJ IDEA 中并没有 Sync 的概念,IntelliJ IDEA 通过 [Reload All Gradle Projects] 操作来触发解析工程的 Gradle 构建信息,直接从 ExternalSystemUtil 类开始执行

  • GradleProjectResolver:负责具体执行 Sync,其中 resolveProjectInfo 方法是具体执行 Sync 逻辑的地方,该方法的定义如下:

    public DataNode<ProjectData> resolveProjectInfo(
        @NotNull ExternalSystemTaskId id,
        @NotNull String projectPath,
        boolean isPreviewMode,
        @Nullable S settings,
        @NotNull ExternalSystemTaskNotificationListener listener)
      throws ExternalSystemException, IllegalArgumentException, IllegalStateException
  • id:本次 Sync 操作的唯一标识,后续可通过调用 GradleProjectResolver 中的 cancelTask 方法取消本次 Sync 任务

  • projectPath:工程绝对路径

  • settings:Sync 工程的配置参数,可设置 Java 版本等

  • listener:用于监听此次 Sync 的过程及结果

  • isPreviewMode:是否要启用预览模式,Android Studio 首次打开未知来源项目时,会让开发者选择项目的打开方式,如下图,若选择 [Stay in Safe Mode] 则会以“预览模式”运行项目,表示 IDE 仅可以浏览项目的源代码,不会执行或解析任何构建任务和脚本

e6b492a87543e665a541176cc14edca1.png

进入 GradleProjectResolver 的 resolveProjectInfo 方法中后,首先会对预览模式进行处理,如下代码所示,如果是预览模式,则会简单构造出对应的工程数据结构后立马返回,不进行任何的解析行为:

if (isPreviewMode) {
  String projectName = new File(projectPath).getName();
  ProjectData projectData = new ProjectData(GradleConstants.SYSTEM_ID, projectName, projectPath, projectPath);
  DataNode<ProjectData> projectDataNode = new DataNode<>(ProjectKeys.PROJECT, projectData, null);
  ......
  return projectDataNode;
}

使用 Android Studio/IntelliJ IDEA 打开工程时,除了指定工程所在根目录,还可以指定 Gradle 配置文件,这个逻辑在源码中也有所体现:

if (projectPathFile.isFile() && projectPath.endsWith(GradleConstants.EXTENSION) && projectPathFile.getParent() != null) {
  projectDir = projectPathFile.getParent();
  if (settings != null) {
    List<String> arguments = settings.getArguments();
    if (!arguments.contains("-b") && !arguments.contains("--build-file")) {
      settings.withArguments("-b", projectPath);
    }
  }
} else {
  projectDir = projectPath;
}

可以看到如果打开的不是工程根目录而是配置文件,那就会把该配置文件所在的目录作为工程路径,并且会通过 --build-file 参数来指定配置文件,后续会传入到 Gradle 中。对工程初步处理后,接着通过 Gradle Tooling API 获取工程的 BuildEnvironment:

ModelBuilder<BuildEnvironment> modelBuilder = connection.model(BuildEnvironment.class);

调用 Gradle Tooling API 时其内部会自动下载项目配置版本的 Gradle,也就是说,上面代码中获取 BuildEnvironment Model 的调用本身确保了 Gradle 的下载,即将 /grade/wrapper/gradle-wrapper.properties 里 distributionUrl 指定的 Gradle 下载到 GRADLE_HOME/wrapper/dists 下。

BuildEnvironment 是 Gradle 提供的 Gradle Model,Gradle Model 是一个非常重要的概念,当 IDE 通过 Gradle Tooling API 和 Gradle 交互时,传输的就是各种各样的 Model,比如 Gradle 自带的 GradleProject 、 BuildEnvironment 等,另外也可以在 Gradle Plugin 中通过 ToolingModelBuilderRegistry 注册自定义的 Model,如 Android Gradle Plugin 中注册了 AndroidProject Model,那就可以通过 Gradle Tooling API 获取 Android 项目特有的 AndroidProject Model,从而获取由 Android Gradle Plugin 提供的 Android 应用相关工程信息。

2.1.2 配置 BuildAction

继续分析 Android Studio Sync 源码,接下来构造了一个 ProjectImportAction,它实现了 Gradle Tooling API 中的 BuildAction 接口,BuildAction 定义如下:

/**
* An action that executes against a Gradle build and produces a result of type {  @code  T}.
 *  @param <T> The type of result produced by this action.
*/
public interface BuildAction<T> extends Serializable {

T execute(BuildController controller);
}

BuildAction 是即将传入到 Gradle 构建进程中执行的行为,并且可将结果数据序列化返回给调用方。这个 BuildAction至关重要,它是实际和 Gradle 通信的地方,其中实现了组织生成工程信息、下载依赖等功能,是 Sync 流程中的核心逻辑。BuildAction 再配合 Gradle Tooling API 中的 BuildActionExecuter,就可以将 BuildAction 交由 Gradle 触发执行了,在执行之前,需先通过 BuildActionExecuter 配置 JVM 参数、Gradle 命令行参数以及环境变量等构建信息:

private static void configureExecutionArgumentsAndVmOptions(@NotNull GradleExecutionSettings executionSettings,
                                                            @NotNull DefaultProjectResolverContext resolverCtx,
                                                            boolean isBuildSrcProject) {
  executionSettings.withArgument("-Didea.sync.active=true");
  if (resolverCtx.isResolveModulePerSourceSet()) {
    executionSettings.withArgument("-Didea.resolveSourceSetDependencies=true");
  }
  if (!isBuildSrcProject) {
    for (GradleBuildParticipant buildParticipant : executionSettings.getExecutionWorkspace().getBuildParticipants()) {
      executionSettings.withArguments(GradleConstants.INCLUDE_BUILD_CMD_OPTION, buildParticipant.getProjectPath());
    }
  }
  GradleImportCustomizer importCustomizer = GradleImportCustomizer.get();
  GradleProjectResolverUtil.createProjectResolvers(resolverCtx).forEachOrdered(extension -> {
    if (importCustomizer == null || importCustomizer.useExtraJvmArgs()) {
      // collect extra JVM arguments provided by gradle project resolver extensions
      ParametersList parametersList = new ParametersList();
      for (Pair<String, String> jvmArg : extension.getExtraJvmArgs()) {
        parametersList.addProperty(jvmArg.first, jvmArg.second);
      }
      executionSettings.withVmOptions(parametersList.getParameters());
    }
    // collect extra command-line arguments
    executionSettings.withArguments(extension.getExtraCommandLineArgs());
  });
}

上述代码较多,我们先只关注于 GradleExecutionSettings 的 withArgument 和 withVmOptions 方法,它们分别负责收集 Gradle 命令行参数和 JVM 参数。从代码中可以看出,这些 Gradle 命令行参数及 JVM 参数大多数是从 extension 中收集而来,这里的 extension 是指 IntelliJ IDEA Plugin 中的扩展,和扩展点结合使用,可扩展 IntelliJ IDEA 平台特性或其他 IntelliJ IDEA Plugin 的功能特性,举个例子,Gradle IDEA Plugin 提供了工程解析的扩展点:

<extensionPoint 
   qualifiedName="org.jetbrains.plugins.gradle.projectResolve" 
   interface="org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension"/>

Android IDEA Plugin 中实现了此扩展点,提供了 Android 工程解析的拓展:

<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
  <projectResolve implementation=
  "com.android.tools.idea.gradle.project.sync.idea.AndroidGradleProjectResolver"
   order="first"/>
......
</extensions>

接着来看 BuildAction 的参数配置逻辑,最终设置 JVM 参数的地方在 GradleExecutionHelper 的 prepare 方法中:

List<String> jvmArgs = settings.getJvmArguments();
BuildEnvironment buildEnvironment = getBuildEnvironment(connection, id, listener, (CancellationToken)null, settings);
if (!jvmArgs.isEmpty()) {
  // merge gradle args e.g. defined in gradle.properties
  Collection<String> merged;
  if (buildEnvironment != null) {
    // the BuildEnvironment jvm arguments of the main build should be used for the 'buildSrc' import
    // to avoid spawning of the second gradle daemon
    BuildIdentifier buildIdentifier = getBuildIdentifier(buildEnvironment);
    List<String> buildJvmArguments = buildIdentifier == null || "buildSrc".equals(buildIdentifier.getRootDir().getName())
                                     ? ContainerUtil.emptyList()
                                     : buildEnvironment.getJava().getJvmArguments();
    merged = mergeBuildJvmArguments(buildJvmArguments, jvmArgs);
  } else {
    merged = jvmArgs;
  }
  List<String> filteredArgs = ContainerUtil.mapNotNull(merged, s -> StringUtil.isEmpty(s) ? null : s);
  operation.setJvmArguments(ArrayUtilRt.toStringArray(filteredArgs));
}

如上代码所示,JVM 参数的配置逻辑很简单:将之前从一系列 GradleProjectResolve 扩展中收集的、存放在 GradleExecutionSettings 中的 JVM 参数和 BuildEnvironment 中的 JVM 参数合并,然后调用 BuildActionExecuter 的 setJvmArguments 方法,将 JVM 参数设置给 BuildAction。Gradle 命令行参数同样是在 GradleExecutionHelper 的 prepare 方法中配置:

...
List<String> filteredArgs = new ArrayList<>();
if (!settings.getArguments().isEmpty()) {
  String loggableArgs = StringUtil.join(obfuscatePasswordParameters(settings.getArguments()), " ");
  LOG.info("Passing command-line args to Gradle Tooling API: " + loggableArgs);
  // filter nulls and empty strings
  filteredArgs.addAll(ContainerUtil.mapNotNull(settings.getArguments(), s -> StringUtil.isEmpty(s) ? null : s));
  ...
}
filteredArgs.add("-Didea.active=true");
filteredArgs.add("-Didea.version=" + getIdeaVersion());
operation.withArguments(ArrayUtilRt.toStringArray(filteredArgs));

对于一个最简单的 Kotlin App Demo 工程,Gradle 命令行参数如下:

来源参数
Android Studio 源码--init-script
/private/var/folders/_4/j3fdr4nd0x7cf17yvt20f5c00000gp/T/ijmapper.gradle
-Didea.sync.active=true
-Didea.resolveSourceSetDependencies=true
-Porg.gradle.kotlin.dsl.provider.cid=676307056703202
-Pkotlin.mpp.enableIntransitiveMetadataConfiguration=true
--init-script/private/var/folders/_4/j3fdr4nd0x7cf17yvt20f5c00000gp/T/ijinit3.gradle
-Didea.active=true
-Didea.version=2021.3
Android IDEA Plugin 扩展:com.android.tools.idea.gradle.project.sync.idea.AndroidGradleProjectResolver--init-script
/private/var/folders/_4/j3fdr4nd0x7cf17yvt20f5c00000gp/T/sync.studio.tooling4770.gradle
-Djava.awt.headless=true
--stacktrace-Pandroid.injected.build.model.only=true
-Pandroid.injected.build.model.only.advanced=true
-Pandroid.injected.invoked.from.ide=true
-Pandroid.injected.build.model.only.versioned=3
-Pandroid.injected.studio.version=10.4.2
-Pandroid.injected.build.model.disable.src.download=true
-Pidea.gradle.do.not.build.tasks=true
Kotlin IDEA Plugin 扩展:org.jetbrains.kotlin.idea.gradleJava.scripting.importing.KotlinDslScriptModelResolver-Dorg.gradle.kotlin.dsl.provider.mode=classpath
Kotlin IDEA Plugin 扩展:org.jetbrains.kotlin.idea.gradleJava.scripting.importing.KotlinDslScriptModelResolver-Porg.gradle.kotlin.dsl.provider.cid=676307056703202
Kotlin IDEA Plugin 扩展:org.jetbrains.kotlin.idea.gradleJava.configuration.KotlinMPPGradleProjectResolver-Pkotlin.mpp.enableIntransitiveMetadataConfiguration=true

重点关注其中的 --init-script 命令行参数,它可以指定一个初始化脚本,初始化脚本会在项目构建脚本之前执行。初始化脚本是 Gradle 提供的一个非常灵活的机制,除了命令行配置,还可以将初始化脚本命名为 init.gradle 放置到 USER_HOME/.gradle/ 下进行配置。初始化脚本允许自定义所有项目的构建逻辑,比如定义特定机器上所有项目的 JDK 路径等环境信息。在上述 Kotlin App Demo 工程中,Android IDEA Plugin 扩展配置了一个初始化脚本,内容如下:

initscript {
    dependencies {
        classpath files([mapPath('/Users/bytedance/IDE/intellij-community/out/production/intellij.android.gradle-tooling'), mapPath('/Users/bytedance/IDE/intellij-community/out/production/intellij.android.gradle-tooling.impl'), mapPath('/Users/bytedance/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib/1.5.10-release-945/kotlin-stdlib-1.5.10-release-945.jar')])
    }
}
allprojects {
    apply plugin: com.android.ide.gradle.model.builder.AndroidStudioToolingPlugin
}

初始化脚本中对所有项目应用了 AndroidStudioToolingPlugin 插件,此插件中通过 ToolingModelBuilderRegistry 注册了 AdditionalClassifierArtifactsModel,这个 Gradle Model 中实现了下载依赖 sources 和 javadoc 的功能:

/**
* Model Builder for [AdditionalClassifierArtifactsModel].
*
* This model builder downloads sources and javadoc for components specifies in parameter, and returns model
* [AdditionalClassifierArtifactsModel], which contains the locations of downloaded jar files.
*/
class AdditionalClassifierArtifactsModelBuilder : ParameterizedToolingModelBuilder<AdditionalClassifierArtifactsModelParameter> {
...

也就是说,Android IDEA Plugin 提供了依赖 sources 和 javadoc 的下载功能,当通过 Gradle Tooling API 获取 AdditionalClassifierArtifactsModel Gradle Model 的时候,会触发依赖的 sources 和 javadoc 下载。

分析完 BuildAction 的 JVM 参数和 Gradle 命令行参数配置流程后,最后来看 BuildAction 环境变量的配置,最终配置的地方在 GradleExecutionHelper 的 setupEnvironment 方法中:

GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.withEnvironment(settings.getEnv());
commandLine.withParentEnvironmentType(
  settings.isPassParentEnvs() ? GeneralCommandLine.ParentEnvironmentType.CONSOLE : GeneralCommandLine.ParentEnvironmentType.NONE);
Map<String, String> effectiveEnvironment = commandLine.getEffectiveEnvironment();
operation.setEnvironmentVariables(effectiveEnvironment);

GeneralCommandLine 中包括当前 Java 进程所有的环境变量,其他环境变量和 JVM 参数类似都被收集在 GradleExecutionSettings 中,Android Studio 会先将其他环境变量与 GeneralCommandLine 中的环境变量合并,然后配置给 BuildAction. 对于不进行任何配置的默认 sync 行为,GradleExecutionSettings 中环境变量为空,全由 GeneralCommandLine 提供。

2.1.3 执行 BuildAction

分析完 BuildAction 的配置逻辑后,接着来看 BuildAction 中具体做了哪些事。BuildAction 中的行为不再处于 Android Studio IDE 进程了,而是在 Gradle 构建进程中执行。IDE 通过 Gradle Tooling API 与 Gradle 交互时,主要媒介是 Gradle Model,BuildAction 中也不例外。BuildAction 的具体执行逻辑见其实现类 ProjectImportAction 中的 execute 方法,我们只关注此方法中与 Gradle Model 相关的代码:

public AllModels execute(final BuildController controller) {
  ...
  fetchProjectBuildModels(wrappedController, isProjectsLoadedAction, myGradleBuild);
  addBuildModels(wrappedController, myAllModels, myGradleBuild, isProjectsLoadedAction);
  ...
}

BuildAction 中调用 fetchProjectBuildModels 和 addBuildModels 方法获取 Gradle Model。先来分析 fetchProjectBuildModels 方法,该方法中进一步调用 getProjectModels 方法:

private List<Runnable> getProjectModels(@NotNull BuildController controller,
                                        @NotNull final AllModels allModels,
                                        @NotNull final BasicGradleProject project,
                                        boolean isProjectsLoadedAction) {
    ...
    Set<ProjectImportModelProvider> modelProviders = getModelProviders(isProjectsLoadedAction);
    for (ProjectImportModelProvider extension : modelProviders) {
      extension.populateProjectModels(controller, project, modelConsumer);
    }
    ...
}

如上代码所示,通过 ProjectImportModelProvider 的 populateProjectModels 方法进一步去获取 Gradle Model。BuildAction 的 addBuildModels 方法与此十分相似:

private void addBuildModels(@NotNull final ToolingSerializerAdapter serializerAdapter,
                            @NotNull BuildController controller,
                            @NotNull final AllModels allModels,
                            @NotNull final GradleBuild buildModel,
                            boolean isProjectsLoadedAction) {
    Set<ProjectImportModelProvider> modelProviders = getModelProviders(isProjectsLoadedAction);
    for (ProjectImportModelProvider extension : modelProviders) {
      extension.populateBuildModels(controller, buildModel, modelConsumer);
    }
    ...
}

可以看到同样是交给了 ProjectImportModelProvider 去获取 Gradle Model,不同的是,前者调用的 populateProjectModels,此处调用的是 populateBuildModels 方法,ProjectImportModelProvider 的作用就是生成 Gradle Model。ProjectImportModelProvider 同 JVM 参数和 Gradle 命令行参数一样,都是由一系列 Gradle IDEA Plugin 扩展提供,如下代码所示:

for (GradleProjectResolverExtension resolverExtension = tracedResolverChain;
     resolverExtension != null;
     resolverExtension = resolverExtension.getNext()) {
    ...
    ProjectImportModelProvider modelProvider = resolverExtension.getModelProvider();
    if (modelProvider != null) {
      projectImportAction.addProjectImportModelProvider(modelProvider);
    }
    ProjectImportModelProvider projectsLoadedModelProvider = resolverExtension.getProjectsLoadedModelProvider();
    if (projectsLoadedModelProvider != null) {
      projectImportAction.addProjectImportModelProvider(projectsLoadedModelProvider, true);
    }
}

对于 Android 工程来说,重点关注 Android IDEA Plugin,它提供的 ProjectImportModelProvider 的实现类为 AndroidExtraModelProvider,该实现类的 populateProjectModels 方法如下:

override fun populateProjectModels(controller: BuildController,
                                   projectModel: Model,
                                   modelConsumer: ProjectImportModelProvider.ProjectModelConsumer) {
  controller.findModel(projectModel, GradlePluginModel::class.java)
    ?.also { pluginModel -> modelConsumer.consume(pluginModel, GradlePluginModel::class.java) }
controller.findModel(projectModel, KaptGradleModel::class.java)
    ?.also { model -> modelConsumer.consume(model, KaptGradleModel::class.java) }
}

此方法中通过 Gradle Tooling API 获取了 GradlePluginModel 和 KaptGradleModel 两个 Gradle Model,GradlePluginModel 中提供了项目已应用的 Gradle Plugin 信息;KaptGradleModel 提供了 Kotlin 注解处理相关信息。接着来看 populateBuildModels 方法:

override fun populateBuildModels(
  controller: BuildController,
  buildModel: GradleBuild,
  consumer: ProjectImportModelProvider.BuildModelConsumer) {
  populateAndroidModels(controller, buildModel, consumer)
  populateProjectSyncIssues(controller, buildModel, consumer)
}

其中分别调用了 populateAndroidModels 和 populateProjectSyncIssues 方法,先来看 populateProjectSyncIssues 方法,它会进一步通过 Gradle Tooling API 获取 ProjectSyncIssues Gradle Model,用于收集 Sync 过程中出现的问题。再来分析 populateAndroidModels 方法,它在整个 Sync 过程中至关重要,其中通过 Gradle Tooling API 获取了 Android 工程相关的 Gradle Model:

val androidModules: MutableList<AndroidModule> = mutableListOf()
  buildModel.projects.forEach { gradleProject ->
findParameterizedAndroidModel(controller, gradleProject, AndroidProject::class.java)?.also { androidProject ->
consumer.consumeProjectModel(gradleProject, androidProject, AndroidProject::class.java)
      val nativeAndroidProject = findParameterizedAndroidModel(controller, gradleProject, NativeAndroidProject::class.java)?.also {
consumer.consumeProjectModel(gradleProject, it, NativeAndroidProject::class.java)
      }
androidModules.add(AndroidModule(gradleProject, androidProject, nativeAndroidProject))
    }
}

如上代码所示,分别获取了由 Android Gradle Plugin 注册的两个 Gradle Model AndroidProject 和 NativeAndroidProject,AndroidProject 中包括 Android 应用的 BuildType、Flavors、Variant、Dependency 等关键信息;NativeAndroidProject 中包括 NDK、NativeToolchain 等 Android C/C++ 项目相关信息。获取了最关键的 AndroidProject 和 NativeAndroidProject 后,接着是对单 Variant Sync 的处理:

if (syncActionOptions.isSingleVariantSyncEnabled) {
    chooseSelectedVariants(controller, androidModules, syncActionOptions)
}

怎么理解单 Variant Sync 呢?Variant 指 Android 应用的产物变体,比如最简单的 Debug 和 Release 版本应用包。Variant 对应于一套构建配置,与项目源码结构、依赖列表相关联。Android 应用可能有多个 Variant,如果在 Sync 时构造所有 Variant,整体耗时可能极长,所以 Android Studio Sync 默认只会构造一个 Variant,并支持 Variant 切换功能。如果启用了单 Variant Sync,前面获取 AndroidProject 时会传入过滤参数,告知 Android Gradle Plugin 构造 AndroidProject 时无需构造 Variant 信息。

2.2 Sync 流程梳理

2.2.1 Android Studio 视角

Android Studio Sync 流程的源码庞大而繁杂,本文着重分析了从触发 Sync 入口到 Gradle 构建结束的阶段,后续还有对 Gradle 数据的处理,以及语言能力模块基于 Gradle 数据进行代码索引的流程,而语言能力又是一个大型而复杂的模块,本文就不再继续展开。

上文源码分析部分将触发 Sync 入口到 Gradle 构建结束划分为 Sync 功能入口及准备、配置 BuildAction 以及执行 BuildAction,整体流程如下图所示:

3c134087cd9a6699aaadcc95abb99510.png

2.2.2 Gradle 视角

上面都是以 IDE 视角去分析 Android Studio Sync,整体流程较复杂,接下来以 Gradle 视角去梳理 Sync 流程,着重关注 Gradle 侧的行为:

425a447352e0c157ba57eebcbac35f4d.png

如上图,对于 Gradle 来说,Sync 流程中 Android Studio 会通过 Gradle Tooling API 从 Gradle 侧获取一系列所需的 Gradle Model,除 Gradle 自身外,Gradle Plugin 也可以提供自定义的 Gradle Model。另外 Sync 流程中 Gradle 会经历自身定义的生命周期,聚焦此视角梳理流程如下:

b19e44647dfaaf7f5f85bed5cc18e6db.png

当通过 BuildAction 获取 Gradle Model 时会触发 Gradle 的构建行为,Gradle 构建会经历自身生命周期定义的 Initialization、Configuration 和 Execution 阶段。

3. 总结

本文首先介绍了 Android Studio Sync 流程中各个角色的作用及联系,对 Sync 有一个较清晰的整体认识,然后从源码角度深入分析从触发 Sync 入口到 Gradle 构建结束的阶段,并详细解释了 Gradle Model、BuildAction 等关键概念,最后分别从 Android Studio 视角和 Gradle 视角对 Sync 流程进行了整体梳理。

通过对 Android Studio Sync 流程的深入分析,除了对 Sync 功能的实现原理深度掌握外,对其意义也有了更深的理解。Sync 是 Android Studio 定义的一个 IDE 准备阶段,在这个准备阶段中,需提前准备好关键的 IDE 功能,而这些功能要达到可用状态,需要获取其必需的数据。基于这个角度,对 Sync 流程的优化方向也有了一定启发:首先从产品层面出发考虑 Sync 阶段的定义,不是开发者真正必需的功能都可以考虑省略或延后准备;然后确认必需功能所需的最小数据集,不必需的数据都可以省略;最后针对必需数据,通过更高效的实现或缓存,找到最快的获取方式。

4. 参考链接

https://mp.weixin.qq.com/s/cftj6WueoHlLh-So9REEXQ

https://developer.android.com/studio/intro

https://android.googlesource.com/platform/tools/base/+/studio-master-dev

https://plugins.jetbrains.com/developers

5. 关于我们

我们是字节跳动终端技术团队(Client Infrastructure)下的 Developer Tools 团队,负责打造公司范围内,面向不同业务场景的研发工具,提升移动应用研发效率。目前急需寻找 Android 移动研发工程师 / iOS 移动研发工程师 / 服务端研发工程师。了解更多信息请联系:wangyinghao.ahab@bytedance.com,邮件主题 简历-姓名-求职意向-期望城市-电话。

相关推荐

2024 抖音欢笑中国年(五):Wasm、WebGL 在互动技术中的创新应用

前言随着 Web 前端技术的不断发展,越来越多的新兴技术方案被引入到 Web 开发中,其中 Wasm 和 WebGL 作为前端领域的两大利器,为开发者带来了更多的可能性。本文将结合2024 年抖音欢笑中国年的部分项目,重点介绍如何利用 Wasm 和 WebGL 对目前流行的一些前端互动技术(比如 Lottie、渲染引擎、动画图片等)进行创新和实践,利用 Wasm 和 WebGL 等新技术方案的特性...

字节跳动技术团队官方博客 2万+

2024 抖音欢笑中国年(四):渲染技术实践与探索

作者:陈瑞、欧阳浩铸、王武俊、倪梵云前言抖音在2024年春节期间推出了欢笑中国年系列活动,为用户带来了全新的体验和乐趣。而SAR Creator则为该项目研发工作提供了重要的技术支持。SAR Creator是一款基于 Typescript 的高性能、轻量化的互动解决方案,目前支持了浏览器和跨端框架平台,服务于字节内部的各种互动业务。这些绚烂多彩的互动场景当然也离不开实时渲染技术的支持,因此本文将专...

字节跳动技术团队官方博客 2万+

2024 抖音欢笑中国年(三):编辑器技巧与实践

前言本次春节活动中,我们大部分场景使用内部的 SAR Creator互动方案来实现。SAR Creator 是一款基于 TypeScript 的高性能、轻量化的互动解决方案,目前支持了Web和字节内部跨端框架平台,服务于字节内部的各种互动业务,包括但不限于抖音春节、抖音直播礼物、抖音UG活动等。SAR Creator 编辑器支持了图形化界面,提供了各类完善的系统(光照、动画、脚本等)供用户快速便捷...

字节跳动技术团队官方博客 1万+

2024 抖音欢笑中国年(二):AnnieX互动容器创新玩法解析

本文基于24年抖音春节活动业务背景,介绍了字节跨端容器AnnieX在游戏互动套件上的探索,致力于提升容器在游戏互动场景的优化能力。业务背景AnnieX作为字节一方游戏统一容器,服务字节内部电商、直播、UG等跨端场景业务。在字节一方游戏互动场景,有大量的一方游戏业务对容器有特定的流量、端能力和游戏优化的诉求。因此我们不断深入互动游戏业务特点,为字节游戏提供完善游戏端能力和流量运营能力,同时提供游戏互...

字节跳动技术团队官方博客 1万+

2024 抖音欢笑中国年(一):招财神龙互动技术揭秘

字节跳动旗下的抖音等 App 在 2024 年春节期间推出了欢笑中国年系列活动,在实现增长业务目标的同时,为用户带来了全新的体验和乐趣。「招财神龙」是其中的一个重要玩法。前言本次春节活动,使用到了字节内的主要前端、跨端、互动技术产品。主要涉及:跨端框架提供了首屏直出的方案使其具有较短的首屏时间,能够大大提升业务加载成功率。跨端框架也提供了 Canvas 作为 SAR Creator 等渲染引擎的...

字节跳动技术团队官方博客 1万+

Monorepo 解决方案 — 基于 Bazel 的 Xcode 性能优化实践

背景介绍书接上回《Monorepo 解决方案 — Bazel 在头条 iOS 的实践》,在头条工程切换至 Bazel 构建系统后,为了支持用户使用 Xcode 开发的习惯,我们使用了开源项目 Tulsi 作为生成工具,用于将 Bazel 工程转换为 Xcode 工程。但是在使用的过程中,我们发现了一些问题,其中影响较大的是,Xcode 工程卡顿:对于头条这种大型项目来说,Xcode 卡顿一直是本地...

字节跳动技术团队官方博客 1万+

抖音 ANR 自动归因平台建设实践

背景介绍本文在 2024 年初最新一期『抖音客户端基础技术大揭秘』技术沙龙活动中已做过专题分享,本次将内容重新整理文章进行分享。公众号后台回复技术沙龙可查看沙龙回放及 PPT~抖音作为一个超大型的应用,我们在 ANR 问题治理上面临着很大的挑战。首先对于存量问题的优化,由于缺少有效的归因手段,一些长期的疑难问题一直难以突破解决,例如长期位于 Top 1 的 nativePollOnce 问题。同时...

字节跳动技术团队官方博客 2万+

CVPR 2024 | CAMixerSR 动态注意力分配的超分辨率加速框架

背景随着相关技术和应用的发展,比如超高清屏幕、虚拟现实(VR)等沉浸式体验的增加,用户对超高分辨率图像和视频的需求变得越来越强烈。在这些场景中,图像的质量和清晰度对于提供最佳的用户体验至关重要。超高分辨率不仅能提供更清晰、更真实的视觉效果,还能在一定程度上增强人们的互动和沉浸感,在一些VR场景中我们需要8K甚至16K的才可以满足需求。然而要生成或者处理这些超高分辨率的内容,对算力的要求也是与日增长...

字节跳动技术团队官方博客 1万+

CVPR 2024 | Modular Blind Video Quality Assessment:模块化无参视频质量评估

无参视频质量评估 (Blind Video Quality Assessment,BVQA) 在评估和改善各种视频平台并服务用户的观看体验方面发挥着关键作用。当前基于深度学习的模型主要以下采样/局部块采样的形式分析视频内容,而忽视了实际空域分辨率和时域帧率对视频质量的影响,随着高分辨率和高帧率视频投稿逐渐普及,特别是跨分辨率/帧率视频转码档位画质评估场景中,这种影响变得更加不可忽视。在本文中,我们...

字节跳动技术团队官方博客 1万+

2024 AI & 前端:回首展望,光芒未至,破晓之前!

前言回望 2023 年,ChatGPT 的突然爆火,让 AI 无疑成为最为值得注目的新兴领域之一,我们也一起见证了生成式 AI 的寒武纪大爆发。这一年来,国内外的生成式 AI 、大模型和相关产品以令人眼花缭乱的速度更新迭代,新的创业浪潮风起云涌。在这 AI 浪潮下,也让我们有了新的开发思考,探索着在各个环节中“前端 & AI”的应用场景。勇于探索的前端开发者们已经开始挥舞着 AI 的“魔法...

字节跳动技术团队官方博客 1万+

Kotlin 云端差分缓存技术

本文由字节跳动 Buildinfra 团队出品。在我们的工程上线 Monorepo 全源码后,Kotlin 编译成了整个编译中最耗时的步骤,全源码过程中大量的 BuildCache Miss 导致我们的编译数据落后原来多仓二进制时代很多,且业界没有相关的解决方案。本篇文章我们来具体阐述下 BuildInfra 团队自研的解决方案 - Kotlin 云端差分方案的原理和技术实现。一、Monorepo...

字节跳动技术团队官方博客 1万+

字节跳动基础架构SRE-Copilot获得2023 CCF国际AIOps挑战赛冠军

近日,2023 CCF国际AIOps挑战赛决赛暨“大模型时代的AIOps”研讨会在北京成功举办,活动吸引了来自互联网、运营商、科研院所、高校、软硬件厂商等领域多名专家学者参与,为智能运维的前沿学术研究、落地生产实践打开了新思路。决赛中,从初赛两百多支队伍中脱颖而出的十支入围队伍分别展示了各自的方案,并进行了现场答辩,评审专家从选题方向、创新性、实用性、完整度和实验复现结果等多角度进行了综合评定,最...

字节跳动技术团队官方博客 2万+

字节跳动百万级Metrics Agent性能优化的探索与实践

背景metricserver2 (以下简称Agent)是与字节内场时序数据库 ByteTSD 配套使用的用户指标打点 Agent,用于在物理机粒度收集用户的指标打点数据,在字节内几乎所有的服务节点上均有部署集成,装机量达到百万以上。此外Agent需要负责打点数据的解析、聚合、压缩、协议转换和发送,属于CPU和Mem密集的服务。两者结合,使得Agent在监控全链路服务成本中占比达到70%以上,对Ag...

字节跳动技术团队官方博客 1万+

西瓜视频RenderThread引起的闪退问题攻坚历程

背景影响西瓜之前存在过一类RenderThread闪退,从堆栈上看,全部都是系统so调用,给人的第一印象像是一个系统bug,无从下手。闪退集中在Android 5~6上,表现为打开直播间立即闪退。该问题在2022年占据Native Crash Top5,2023年更是上升到到Top1。因此有必要投入时间和精力再重新审视一下这个问题。在历经多周的源码分析和排查后,逐步明确了问题根因并修复,最终取得了...

字节跳动技术团队官方博客 2万+

字节电商双11 大促容量保障是如何做的?

前言Rhino 简介Rhino是字节自研全链路容量评估产品,致力于构建完整的全链路容量评估解决方案(覆盖:容量预估->资源准备->数据准备->容量验证->监控->分析->决策->处理反馈);围绕容量在稳定性、成本、效率 三方面提供业务全方位基础支撑。Rhino 目前已经成为字节各业务容量评估主流解决方案,并且历年来在业务大型活动稳定性保障中(抖音春节项目、...

字节跳动技术团队官方博客 10万+

使用火山引擎 APMPlus 解决抖音Top 1 Java 崩溃的通用优化方案

背景近3个月,抖音 Android 版面临一个多次触发线上报警的崩溃问题,全量版本和灰度版本的异常数据激增,该问题不仅容易触发报警,更成为了 Java Top 1 崩溃问题,带来巨大困扰,急需攻坚解决。本文展现了具体的分析过程、优化思路和解决方案,同时提供了已集成该方案的实用工具。初步分析多维特征我们以某发版期间数据为例进行分析:机型方面:比较分散,有聚集部分samsung sm-s9180 占比...

字节跳动技术团队官方博客 2万+

用 Addon 增强 Node.js 和 Electron 应用的原生能力

前言Node.js Addon 是 Node.js 中为 JavaScript 环境提供 C/C++ 交互能力的机制。其形态十分类似 Java 的 JNI,都是通过提供一套 C/C++ SDK,用于在 C/C++ 中创建函数方法、进行数据转换,以便 JavaScript / Java 等语言进行调用。这样编写的代码通常叫做 Bindings。此外还有基于 C ABI Calling Convent...

字节跳动技术团队官方博客 2万+

火山引擎 ByteHouse 的增强型数据导入技术实践

作为企业数字化建设的必备要素,易用的数据引擎能帮助企业提升数据使用效率,更好提升数据应用价值,夯实数字化建设基础。数据导入是衡量OLAP引擎性能及易用性的重要标准之一,高效的数据导入能力能够加速数据实时处理和分析的效率。作为一款OLAP引擎,火山引擎云原生数据仓库ByteHouse源于开源ClickHouse,在字节跳动多年打磨下,提供更丰富的能力和更强性能,能为用户带来极速分析体验,支撑实时数据...

字节跳动技术团队官方博客 1万+

打造企业级智能问答系统的秘密:如何使用云数据库 PostgreSQL 版实现向量检索

本文就如何利用火山引擎云数据库 PostgreSQL 版和大语言模型技术(Large Language Model,简称 LLM),实现企业级智能交互式问答系统进行介绍。通过本文,你将会了解交互式问答系统的原理,学习 PostgreSQL 的向量化存储和检索技术,以及大语言模型交互技术等。背景在大数据的浪潮下,众多企业建立了自己的知识库,以便于信息检索和知识查询。然而,随着知识库内容的膨胀,传统的...

字节跳动技术团队官方博客 2万+

抖音大型直播的画质优化实践

面临挑战随着抖音内容生态的不断丰富,越来越多的大型赛事在抖音平台进行直播,世界杯/春晚/亚运会等各项赛事节目引来大量用户观看。卡塔尔世界杯期间,抖音提供的稳定高质直播画面为观众带来了完美的观赛体验,决赛的 PCU 高达 3700W+。不同赛事节目涉及链路众多,且不同赛事之间存在差异,如何保障各链路的画质稳定并进一步提升画质,是一个巨大的挑战。如何应对挑战?画质优化链路大型赛事直播涉及链路较长,不同...

字节跳动技术团队官方博客 2万+

抖音直播新一代BVC编码器正式亮相

面临挑战在直播行业发展如火如荼的今天,用户对视频体验的要求也水涨船高。视频基础体验的关键要素包括清晰度、流畅度、低延迟等,而这些要素的“第一性原理”,就是视频本身的编码效率,也就是压缩率。视频编码是整个技术体系的基座,编码效率的显著提升,能够在同等码率下极大提高画质,从而改善用户体验。视频编码效率的重要性不言而喻,但进一步地提升也并非易事,尤其在直播场景中,对编码速度、延迟、码率控制等方面都有很高...

字节跳动技术团队官方博客 7356

Go Metrics SDK Tag 校验性能优化实践

背景Metrics SDK 是与字节内场时序数据库 ByteTSD 配套的用户指标打点 SDK,在字节内数十万服务中集成,应用广泛,因此 SDK 的性能优化是个重要和持续性的话题。本文主要以 Go Metrics SDK 为例,讲述对打点 API 的 hot-path 优化的实践。用户在使用 SDK API 进行打点时,需要传入指标对应的 Tag:tags:=[]m.T{{Name:"foo...

字节跳动技术团队官方博客 1万+

云上智能驾驶三维重建最佳实践

智能驾驶技术的不断发展,正在改变着我们的出行方式和交通系统。作为其中的一个关键技术,三维重建在智能驾驶系统中起着重要的作用。除去车端本身的感知、重建算法,自动驾驶技术的落地与发展需要庞大的云端重建能力支撑,火山引擎多媒体实验室通过行业领先的自研三维重建技术,结合强大的云平台资源与能力,助力相关技术在云端大规模重建、自动标注、真实感仿真等场景的落地与应用。本文重点介绍火山引擎多媒体实验室三维重建技术...

字节跳动技术团队官方博客 6304

火山引擎实时、低延时拥塞控制算法的优化实践

摘要火山引擎智能拥塞控制算法 VICC(Volcano Intelligent Congestion Control)是一种自适应的拥塞控制算法,旨在解决全球不同网络环境下,不同音视频应用对带宽利用率和延时的差异化要求。它结合了传统拥塞控制算法(如 GCC 和 BBR)的优点,并且能够根据不同的网络条件、业务偏好和码率特征进行自适应调整,包括自适应拥塞响应速度、自适应带宽探测幅度、自适应丢包检测策...

字节跳动技术团队官方博客 6735

veImageX 演进之路:Web 图片加载提速50%

背景说明火山引擎veImageX演进之路主要介绍了veImageX在字节内部从2012年随着字节成长过程中逐步演进的过程,演进中包括V1、V2、V3版本并最终面向行业输出;整个演进过程中包括服务端、客户端、网络库、业务场景与优化等多个角度介绍在图像处理压缩、省成本与体验优化的经验与方案;本篇文章重点介绍在web端演进和提供的能力,图片是 Web 站点中的重要元素,图片体积、格式、分辨率以及渲染方式...

字节跳动技术团队官方博客 6589

自研多模态追踪算法 PICO 为「手柄小型化」找到新思路

作者:张韬、林泽一 、闻超 、赵洋研发背景作为头戴的追踪配件,VR手柄可以通过HMD(头戴显示设备)的inside-out光学追踪定位原理,计算出手柄的空间运动轨迹,同时结合6轴传感器实现6DoF空间定位。与此同时,结合手柄控制器的物理按键、马达反馈、摇杆等,用户还能获得逼真、细腻的触觉反馈,进一步增强虚拟现实人机交互的能力以及沉浸感,这也是目前无手柄方案所难以实现的。目前主流VR手柄的追踪技术方...

字节跳动技术团队官方博客 7815

如何利用播放器节省20%点播成本

点播成本节省的点其实涉及诸多部分,例如:CDN、转码、存储等,而利用播放器降本却是很多客户比较陌生的部分。火山引擎基于内部支撑抖音集团相关业务的实践,播放器恰恰是成本优化中最重要和最为依赖的部分。火山引擎的视频团队做了份数据统计,在一个很经典的视频业务中,我们在2022年至2023年大约1年半的时间里,针对这个业务进行了33次成本优化点,其中13次是播放器主导的优化,其余的有12次也是需要播放器强...

字节跳动技术团队官方博客 6176

火山引擎 ByteHouse:ClickHouse 如何保证海量数据一致性

背景ClickHouse是一个开源的OLAP引擎,不仅被全球开发者广泛使用,在字节各个应用场景中也可以看到它的身影。基于高性能、分布式特点,ClickHouse可以满足大规模数据的分析和查询需求,因此字节研发团队以开源ClickHouse为基础,推出火山引擎云原生数据仓库ByteHouse。在日常工作中,研发人员经常会遇到业务链路过长,导致流程稳定性和数据一致性难保障的问题,这在分布式、跨服务的场...

字节跳动技术团队官方博客 6900

抖音集团都在用的画质评估工具,确定不试试吗?

导读本文从抖音集团内部画质评估体系的建设历程着笔,主要分享了画质评测对于业务的重要性、主要应用场景和内部产品的一些典型实践案例。通过分享业务视角遇到的一些问题和我们的解决思路,希望能抛砖引玉,为遇到类似困扰的伙伴们提供有价值的参考。画质评估体系建设历程为何评测画质如此重要?我们通过线上业务大量实验发现,图片画质优劣对点击率、 停留时长等消费类指标有正相关影响,间接影响用户收益指标。因此,建设一套行...

字节跳动技术团队官方博客 7939

VLDB 2023 | CDSBen: 字节跳动 veDB 数据库存储系统性能测试模型

背景随着业务爆炸式增长与云原生技术的日渐成熟,大量云原生分布式数据库产品如雨后春笋般涌现,其中一部分主打 OLTP 场景的分布式数据库强调的是从计算-存储分离架构获得弹性收益;对于业界各种计算-存储分离架构的数据库而言,怎么用真实的端到端数据库 workload 去 benchmark 其底层存储系统一直存在以下难题:对于数据库专用存储系统,不存在如 fio 一样的“事实标准” benchmark...

字节跳动技术团队官方博客 5895
上一篇: 火山引擎 RTC 自研音频编码器 NICO 实践之路
下一篇: Spark AQE SkewedJoin 在字节跳动的实践和优化
 字节跳动技术团队
字节跳动技术团队 企业官方账号 企业官方账号
博客等级 码龄7年 8152粉丝 316原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值