前言
成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。
五、AppPlugin 构建流程
为了能够查看 Android Gradle Plugin 与 Gradle 的源码,我们需要在项目中添加 android gradle plugin 依赖,如下所示:
compile'com.android.tools.build:gradle:3.0.1'$ } ;
众所m U / F H s &周知,我们能够将一个 moudle 构建为一个 Android 项目是由于在 buiy ^ : lld.gradle 中配置了如下的插件应J _ j q c用代码:
applyplugin:'com.android.application'
当执行到 apply plugin: ‘com.android.application’ 这行配置时,也就开始了 AppPlugin 的构建流程,下面我们就来分析下? T % 0 p { AppPlugin 的% z j 1 !构建流程。
‘com.android.application’ 对应的插件 properties 为 ‘com.android.internal.application’,内部标明的插件u h | {实现类如下所示:
implementation-class=com.android.build.gradle.internal.plugins.ApZ & l x G dpPlugin
AppP) n n B o f A Z 3lugin 中的关键实现代码如下所示:
/**Gradlepluginclassfor'applicat| a ~ J l hion'projeca O K h g 0 ts,appliedonthebaseapplicationmodule*/
publicclassAppPluginextend) x nsAbstractAppPlugin{
...
//应用指定的plugin,这里是一个空实现
@] D H I X Q G 9Override
protectedvoidpluginSp2 R zecg p H m @ : 9ificApply(@5 l g g 7 v 2 GNonN) C * K & OullProjectproject){
}
...
//获取一个扩展类:应用ap2 h tplicationplugin都会提供一个与之对应的anH b M droidext, | 1 # z jension
@Overa % ? A & T 8 Qride
@NonNull
protect0 c I S , V I kedClass<?extendsAppExtS K q 5 G L 6ens- y v C X K 5 ; @ion>getExtensionClass(){
returnBac J 1 R * ^seAppModuleExtension.class;
}
...
}
那 apply 方法是在什么地方被调用的呢?6 o Y R Y i
首先,我们梳理下 AppP O A B 4lugin 的继承5 g . $ G D关系,如下所示:
AppPlugin => AbI u U mstractAppPlugin =>4 0 Z { #; BasePlugin
而 apply 方法就在r & BasePlugin 类中,BasePlugin 是一个应用于所G O C 有 Andro1 7 Tid Plugin 的基类,在 apply 方法中会预先进行L U {一些准备工作。
1、准备工作
当编译器执行到 apply plugin 这行 grU | + N ; ) [ + jo7 = R v bovy 代码时,gradle 便会最终回调 BasePlugin 基类 的 apply 方法,如下所示:
@Override
publicfinalvoidapply(@NonNullProjectproje: m ^ { u ` 3 zct){
CrashReporting.runAction(
()->{
basePluginApply(projeL 7 ) 5 ~ct);
pluginSpecificApply(project)z T { ; M L S _;
});
}
在 apply 方法中调用了 basePO o C Z l r ` sluginApply 方法,其源码如下所示:
privatevoidbW 7 B =asePluginApply(@NonNullProjectproject){
.., u ) ( N t 3 ~.
//1、Dependn 1 N } ) I u JencyResolutionChecks会检查并确保在配置阶段不去解析依赖。
DependencyResolutionChecks.registerDependencyCheck(project,projectOptions);
//2、应用一个AndroidBasep z P - S ) & SPlugin,目的是为了让其他插件作者区分当前应用的是一个Android插件。
projectf 8 R X 6 ? { 7.getPl/ s O Y S wuginManager().apply(AndroidBasePlugin.class);
//3、检查project路径是否有错误,发生错误则抛y 7 H -出StopExecutionException异常。
checkPathForErrors();
//4、检查子moudle的结构:目前版本8 L 4 T ;会检查2个模块有没有相同的标识(组+名称),如果有则抛出StopExecutionException异常。(f I 7 D b @ B 组件化在不同的moudle中需要给资源加prefix前缀) 2 i * ~
checkModulesForErrors();z A m j | W O @ _
//5、插件初始化,必须立即执行。此外c v : q a D D ] y,需要注意,GradleDeamon永远不会同时执行两个构建流程。
PluginInitializer.initialize(projeR = 2 q ict);
//6、初始化用于记录构建过程中配置信息的工厂实例ProcessProfilek ( m a Q .WriterFact# - f T P E ? }ory
RecordingBuildListenerbuildListener=ProfilerInitializer.init(project,projectOptions);
//7、给project设置androidpluginversion、插件类型、插件生成器、project选项
ProcessProfileWf M p ) p 9riter.getProject(project.getPn B F 3ath())
.setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
.setAndroidPlugin(getAnalytI , # * v hicsPluginType())
.setPlugt s M K n (inGeneration(GradleBuildProject.PluginGeneration.FIRSTV j c 5 I w 7)
.setOptions(AnalyticsUtil.toProto(projectOptions));
//配置工程
thre9 5 : - T yadRecorder.record(
ExecutionA h BType.BASE_PLUGIN_PROJECT_CONFIGURE,
project.getPath(),
null,
this::configureProjeU 9 5 J Cct);
//配置ExtensioB a ] J -n
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
project.getPath(),
null,
this::configureExte) S Z { i 7nsion);
//创建Tasks
thM ? O q u G 4 *readRecorder.record. | I $ H(
ExeF S kcuN T R 1tionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
project.getPaW G U fth(),
null,
this::createTasks);
}
可以看到,前 4 个步骤都是一些检查操作,而后 3 个步骤则是对插件进行初始化与配置。我们 梳理下 准备工程 中的任务
,如下所示:
-
1、 插件检查操作
-
1)、使用 DependencyResolutionChecks 类去检查并确保在配置阶段不去解析依赖。 -
2)、应用一个 AndE . f o G I R {roidBasePluk 2 h x M rgin,目的是为了让其他插件作者区分当前应用的是一个 Android| L V x e 插件。 -
3)、检查 project 路径是否有错误,发生错误则抛出 StopExecutionExceptis i ,on 异常。 -
4)、检查子 moudle 的结_ r u L 6 E #构,目前版本会检查 2 个模块有_ { B没有相同的p $ K o J h A r标识(+ H 1 s d S组 + 名称),如果有则抛出 StopExecu5 Z RtionException 异常。(联想到组件化在不同的 moudle 中需要给资源加 prefix 前缀)
-
-
2、 对插件进行初始化与配置相关信息
-
1)、立即执行插件初始化。 -
2)、初始化 用于记录构建过程中配置信息的工厂实例 ProcessProfileWriM & pterFactory。 -
3)、; ( e G – # . k #给 project 设置 androy n w 0id plugin version、插件类型、插件生成器、project 选项。
-
2、configureProject 配置项目
Plugin 的准备工程完成之后,就会执行 BasePlugin 中的 confV u ` cigureProject 方法进行项目的配置了,其源码如下所示:
privatevoidconfigureProject(){
...
//1、创建DataBindX Q B ingBuilder实例。
d{ x iataBindingBuilder=newDatV , ; * b + - GaBindingBuilder();
dataBindingBuilder.setPF F [ @rintD u o # HMach4 F C J 6 [ , ( Vine. & C ( n , NReadableOutput(
SyncOptions.getErrorFormatMode(projectOptions)==ErrorFormatM- & yode.MAs 9 d P M XCHINE_PARSABLE);
//2、强制使用不低于当前所支持的最小插件版本,否则会抛出异常。
GradlePluginUtils.enforceMinimumVersionsOfPlugins(project,syncIs/ / o n B 5 zsueHandler);
/. - S P r C/3、应用J s K l 8 havaPlugin。
project.getPlugins().f k ) 4 c * papply(JavaBasePlugin.cU X n c d p _ Mlass);
/v t i Z q/4、如果启Y / ? . D动了构建缓存选项,则会创建buildCache实例以便后面能a P Y k 1 e Y k重用缓存。
@Nullable
FiB ? - eleCachebuildCachM Q [ ` F !e=BuildCacheUtils.createBuildCacheIfEnabled(projec[ r C k 3 g zt,projeR 8 f 7 k ^ctOptions);
//5、这个回调将会在整个project执行完3 } / g Q p 9 ;成之后执行(注意不是在当前moudle执行完成之后执行),因为每一个project都会调用此回调,所以它可能会执行多次。
//在整个0 6 u s P Pproject构建完成之后,会进行资S q & : l N源回收、缓存清除并关闭在此过程中所有启动的线程池组件。
gU : 8radle.addBuildListener(
newBuildAdapter(){
@Override
publicvoidbuildFinished(@NonNullBuildResultbuildResult){
//DonotrunbuildFinishedforincludedprojectincompositebuild.
if(buildResult.getGradle().getParent()!=null)T y L v c o ]{
return;
}
ModelBuilG N 8 : | c &der.clearCac- e . Jhes();
Worker1 ; d X M Es.INSTANCE.shutdown();
sdkComponents.unload();
SdkLocator.resetCache();g 4 i q
Constrai$ ~ p p $ ? . rntHandler.clearCache();
CachedAnnotationProl u D } _cessorDetector.clearCache();
threadRecordeT ` R ~ P ) 5 [ Pr.record(
ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
project.getPath(),
null,
()->{
if(!projectOptions.get(
BooleanOption.KEEP_SERVICES_BETWEEN_BUILDS)){
Wor- Z $kerActionS J D B 6erviceRegistry.INSTANCE
.shutdownAllRegisteredServices(
ForkJoinPool.commonPM t ool());
}
Main.clearInternTables();
});
DeprecationRz V ueporterImpl.Companion.clean();
}
});
...
}
最后,我们梳理: ( m X @ Y下 configureProject 中所执行的 五项主要任务,如下所示:
-
1)、创建 DataBindingBuilder 实例。 -
2)、强制使用不低于当前所支持的最小~ + g f 2插件版本,否r A r n ) h ` B =则会抛出异常。 -
3)、应用 Java Plugin。 -
4)、如果启动了 构建缓存 选项,则会创建 buildCache 实例以便后续能重用缓存。 -
5)、这个回调将会在整个 project 执行完成之后执行(注意不是在当前 moudle 执行完成之后执行),因; Z ? (为每一个 project 都会调用此回调, 所以它可能会执行多次。最后,在整个 project 构建完成之后,会进& S H行资源回收、缓存清除并关闭在此过程中所有启动的线程池L ) 1 ! ) ;组件。
3、configureExtension 配置 Extension
然后,我们看到l Y E 7 p 0 e H BasePlugin 的 configureExtension 方法,其核心源码如下所示:
privB l catevoidconfigureExtension(){
//1、创建盛放buildType、productFlavor、signingConfig的容器实例。
...
finalNamedDomainObjectConta- ^ v 1 r [ Ciner<BaseVariantOutput>buildOutputs=
project.v # *container(Ba1 1 [seVariantOutput.class);
//2、创建名为buildOutputs的扩展属性配置。
project.getExtensions()Y J i _ L x S q.add("buildOutputs",buildOutputs);
...F S 8 ) ,
//3、创建androidDSL闭包。
extension=
createExtension(
project,
projectOptions,
glo4 @ [ kbalScope,
buildTypeContaI e S X diner,
productFlavorContaineG 4 1r,
signingConfigContainer,
buildOutputs,
sourceSetManager,
extraModelInfo);
//4、给全局域设置创建好的! . ] * ) B | bandroidDSL闭包。
globalScope.setExtension(extension);
//5、创建一个ApplicationVariantFacl ! R . U / | |tory实例,以用于生产APKs。
variantFactory=createVariantFactory(globa/ O D QlScope);
//6、创建一个ApplicationT8 Z Y 3 T s . ]askManager实例,负责为Android应用工程去创建Tasks。
taskManager=
createTaskManager(
globalScope,
project,
projectOptions,
dataBindingBuilder,
extension,
variantFactory,
registryy 4 } 8 W # ),
threadRC # ( Y _ 8 _ecorder);
//7、创建一个5 I &VariantManager实例,用于去创建与管理Variant。
variantManager=
newVariantManager(
globalScope,
proje~ ~ l g +ct,
projectOp^ $ Y F J z 8 4 !tions,
extension,
v] T 8 Z ~ eariantFactory,
taskManager,
sourceSetManager,
threadRecorder);
//8、将whenObjectAddedcallbacks映射到singi: u Y n S I JngCo4 ^ fnfig容器之中。
signingConfi? k r !gContainer.whenObjecQ * V T O L o =tAdded(variantManager::addSigningConfig);
//9、[ 6 e Y I ) & 如果不是DynamicFeatur| q ` ) /e(负责H | L Q 5 ` 添加一个可选的APK模块),则会初始化一个debugsigningConfigDSL对象并设置d # [ E 3给默认的buildTypeDSL。
buil? z J q * _ L tdTypeContain? - 2 R { | 8er.whenObjectAdded(
buildType->{
if(!5 Q w t . y $this.getClass().isAssignableFrom(DynamicFeaturePlugin.class)){
SigningCon: Z 4fiC s # - M S xgsigningConfig=
signingConfigContainer.findByName(BuilderConstants.DEBUG);
bui. 8 q p 2 %ldType.init(| Z 2 { S D | 9signingConfig);
}else{
//initializeitwithoutthesigningConfigfordynamic-features.
buildType.iM W @ W | l tnit();
}
variantManager.addBuildType(buildType);
});
//10、将whenObjeP d D / ` @ 3 y ;ctAddedcallbacks映射到productFlavor容器之中。
productFlavorCV 3 ^ ; K `ontainer.whenObjectAdded(variantManager::addProductFlavor);
//11、将w1 S X e 6 : JhenObjectRemoved映射在容器之中,当w` } b 1 H v Q &henObjectRemoved回调执行时,会抛出UnsupportedAction异常。
signingConfigContainer.whenObjectRemoved(
newUnsupportedAction("RemovingsigM v c g oningConfigsisnotsupported."));
buildTypeContainer.r 7 6whenObjectRemoved(
newUnsupportedAction("Removingbuildtypesisnotsupported.")e p j a t n l f);
productFlavorContainer.whenObju c ? ; / R Z !ectRemoved(
newUnsupportedAction("Removingproductflavorsisnotsupported."));
//12、按顺序依次创建sigM V y !ningConfigdebug、buildTypedebue # F {g、buildY = H #Typer, 4 Rell P ; R d r *ease类型的DSL。
variantFactory.createDefaultComponents(
buildTypeContainer,productFlavorContainer,signingConfigContainer);
}
最后,我们梳理下 configureExtension 中T : @ &的任务,如下所示:
-
1)、创建盛% ^ W q @ 3放 buildType、productFlavor、signingConfig 的容器实例。 -
2)、创建名为 buiz w | ^ / V 1 yld& s Y % EOutputs 的扩展属性配置。 -
3)、创建 android DSL 闭3 ^ W包。 -
4* 0 w )、给全局域设置创建好的 android DSL 闭包。 -
5)、创建一个 ApplicationVariantFactory 实例,以用于生h I d l b * N产 APKs。 -
6)、创建一个 ApplicationTaskManager 实例,负责为 Android 应用/ o S ;工程去创建 Tasks。 -
7)、创建一个 VariantManager 实例,用于去创建与管理 Variant。 -
8)、将 whenObjectAdded callbacks 映射到 singingConfig 容器之中。 -
9)、将 whenObjectAdded callbacks 映射到 buildType 容器之中。如果不是 DynamicFeature(负责添加一个可选的 APK 模块),则会初始化一个 debug signin3 e L t ) ( P K 3gCon[ T B J ; 7 )fig DSLP e q [ a ; s ? 对象并设置给默认的 buildType DSL。 -
10)、将 whenObjectAdded callbacks 映射到 productFlavor 容器之中。 -
11)、将 whenObjectRemoved 映射在容器之中,当 whenObjectRemoved 回调执行时,会抛出 Unsupportedg 5 x cAction 异常。 -
12)、按顺序依次创建 signingConfig debug、bui7 p ; D 4 d A Y ~ldType debug、buildType release 类型的 DSLV h =。
其中 最核心的几项处理可以归纳为如下 四点:
-
1)、创建 AppExtension,即 build.gradle 中的 an] $ } k } . Pdroid DSL。 -
2)、依次创建应用A _ A V的 variant 工厂、Task 管理者,variant 管理者。 -
3)、注s P Q O g O册 新增/移除配置 的 callback,依次包括 signingConfig,buildType,productFlavor。 -
4)、依次创建默认的 debug 签名、创建 debug 和 release 两个 buildType。
在 BasePlugin 的 apply 方法最后,调用了 createT* 0 5 – % ( Zasks 方法来创建 Tasks,该方法如下所示:
privatevoidcrea} [ $ 6 T ) l C vteT J W 0 . 2 _ m Uasks(){
//1、在evaluate之前创建Tasks
threadRecorder.record(
ExecutionType.TASK_MANAGER_CREATE_TA} e G mSKS,
project.getPath(),
null,
()->taskManager.createTasksBefoG X L 2 ireEvaluate());
//2、创建AndroidTasks
project.afterEvaluate(
CrashReporting.aftv i _ Q _ ^ = ! gerEvaluate(
p->{
sourceSetManager.runBuildableF 8 / | R 5 6ArtifactsActions();
threadRecorder.record(
ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
project.getPath(),
null,
thisF a F k ; p C ~ ^::createAndroidTasks);
}));
}
可以看到,createTasks 分为两种 Task 的创建方式,即 createTa: ( ) =sksBeforeEvaluate 与 createAndro0 0 X t A v i 4 eidTasks。
下面,我们来详细分析下其实现过程。
4、TaskManager#createTasksBeforeEvaluate 创建不依赖 fla1 & { h |vor 的 task
TaskManager 的 createTasksBeforeEvaluate 方法给 Task 容器中注册了一系列的 Task,包括 uninst@ ? # i L jallAllTask、deviceCheckTask、connectedCheckTf r v O t sask、preBuild* n G、es O } ~ 8 V KxtractProguardFiles、sourceSetsTask、assemble– 2 ? G B ^ Z ^AndroidTest、compileLintTask 等等。
5、BasePlugin#creatH M I H E [ ? QeAndroidTasks 创建构建 t! j 1 s x $ –ask
在 BasePlugin 的 createAndroidTasks 方法中主要
是生成 flavors 相关数据,并根据 flavor 创建与之对应的 Ta; B d u , jsk 实例并注册进 Task 容器之中。其核心源码如下所示:
@VisibleFod K ` ? : 7 z TrTesting
finalvoidcreateAndroidTasks(){
//1、CompileSdkVersion、插件配置冲突检测(如JavaPlugin、retrolambda)。
//创建一些基础或通用的Tasks。
//2v 9 @ u o、将ProjectPath、CompileSdk、BuildToolsVersM + / { L A 1 5ion
、Splits、KotlinPluG / y ` + v / F rginVe/ { @rsion、FirebasePerformancePluginVersion等信息写入Project的配置之中。
ProcessProfileWriter.getProV , o Wjecp B r u Y Bt(project.getPath({ ~ g p H Q))
.setCompileSdk(extension.g5 ) wetCompileS( F ` l j g NdkVersion())
.setBuildToolsVersion(extension.getBuildToolsRevisio| o R 4n().tod U * E Y ~StringY e p v 8 u L ())
.setSplits(AnalyticsUtil.toProto(extension.getSplits()));
Stu , NriC B = $ T K ?ngkotlinPluginVersion=ge` r y @ e AtKotlinPlugi~ } _ e 5nVersion();
if(kotlinPluginVersion!=null){
ProcessProfileWriter.geZ C s FtProject(project.getPath())
.s{ W n x ~ v n fetKotlinPluginVersion(kotlinPluginVersion);
}
AnalyticsUtil.recordFirebasePerformancePluginVersion(project);
//3、9 ! , f M = Z创建应用的Tasks。
List<VariantScope>variantScopes=vari] d z w l M 4 3antManager.createAndroidTasks();
//创建一些基础或P U m z J通用的Tasks与做一些通用的处理y : C A。
}
在 createAndroidTaV x ( ! D # ] sks 除了创建一些基础或通用的 Tasks 与做一些通用的处理之外, 主要做了三件事,如下所示:E + J z h d q D
-
1)、CompileSG % K j Z p { Z 1dkVersion、插件配置冲突检测(如 JavaPlugin、retrolambda 插件)。 -
2)、将 Project Path、t B PCompileSdk、BuildToolsVersion、Splits、KotlinPluginVersion、` 1 c .FirebasePerformancePluginVersion 等信息写入 Project 的配置之中。 -
3)、创建9 y s [ + 4 ! U f应用的 Tasks。
我们需要 重点关注 variantManager 的 createAndroidTasks 方法,去核心源码如下所示:
/**Variant/Taskcreationentrypoint.*/
publicList<VariantScope>createAndroidTasks(){
...
//U / :1、创建工程级别的测试任务。
tJ 7 & Y TaskManager.createTopLevelTestTasksp c `(!productFlavors.isEmpty());
//2、遍历所有variantScope,为其变体数据创建对应的Tas} R - i 7 D Xks。
for(finalVariantScw 0 M OopevariantScope:variantSr ? m ccopes){
createTasksForVariantData(variantScope);
}
//3、创建报告相关的Tasks。
taskManager.createReportTasks(vaa Y 3 ) friantScopes);
returo T q | Mnvariant P / = #tScopes;
}
可以看到,在 createAndroidTasks 方法中有 三项处理
,如下所示:
-
1)、创建工程级别的测试任务。 -
2)、遍历所有的 variantScope,a X ,为其变体数据创建对应的z U g $ [ G n Tasks。 -
3)、创建报告相关的 Tasks。
接着,我们继续看看 createTasksForVariantData 方法是如何为每一个指定的 Variant 类型创建对应的 Tasks 的,其核心源码如下所示:
//: 8 c + I为每一个指定的V( / Z 6ariant类型创建与之对应的Tasks
publicvoidcreateTasksForVariantData(finalVariantScopevariantScope){
finalBaseVariz L A X ^ ? / _antDatavariantData=variantSc~ j H 5 c d ?ope.getVariantData();
finalVariantTypevariantType=variantData.getType();
finalGradleVariantConfigurati+ z c c $ 8 donvariantConfig=variantScope.getVariantConfiguration();
//1、创建AssembleTask。
taskManager.createAssembleTask(variantData);
//2、如果variantType是basemoudle,则会创建相应的bundleTask。需要注意的是,basemoudle是指包含功能的moudle,而用于test的moudle则是不包含功能的。
if(variantType.isBaseModuleE 9 Z X , r e 3()){
taskManager.createBundleTask(variantData);
}
//3、如果variantType是一个testmoudle(其作为一个test的组件),则会创建相T O $ a q应x M 2的testvariantm j u 4 p。
if(variantType.isTestComponent()){
//1)、将vD 3 O xariant-specific~ } 8 y i y,; T M , y / 4bui= m a B Dldtypemulti-flavor、defaultConfig这些依赖添加到当前的0 7 `variantData之中。
...
//2)、如果支持渲染脚本,则添加渲染脚本的依赖。
if(testedVariantData.getVy q ] = 7 v s V DariantConfiguration().getRenderscriptSupportModeEnabled()){
project.getDependencies()
.add(
variantDep.getCompileClasT q ~ % `spath().getName(),
projA w J L ?ec6 B o u ! z i 2t.files(
globa1 v B AlScope
.getS) 3 ? 2 I ] , NdkCP $ k . yomponents()
.getReQ u ) ] = l o 4 {nderScriptSupportJarProvider()));
}
//3)、如果当前Variant会输出一个APK,即当前是执行的一个Androidtest(一般用来进行UI自动化测试),则会创建相应的AndroidTestVariantTask? j $ E z。
if(variantType.isAG 8 X n Q ) 0pk()){//ANDROID_TEST
if(j ~ d 7variantConfig.isLegacyMultiDexMode()){
StringmultiDexInstrum | N e _ ! Kmentp . l + X S S Matio4 ( % U i ( XnDU : v X W s Eep=
globalSq c acope.getProjectOptions().get(BooleanOpti2 { r 6 V I yon.USE_ANDROID_X)
?ANDROIDX_MULTIDEX_MULTIDEXv | Q !_INSTRUMENTATION
:COMd u ] ( o 8 O P_ANDROID_SUPL ! : I E B _ $PORT_Mo n wULTIDEX_INSTRUMENTATION;
project.getDepp f D 3endencies()
.add(
variantDep.getCompileClay n u 5 : ` = tsspath().getName()m d ( ] y j J [ B,
multiDexInstrumentationDep);
project.getDependencies()
.add(
variantDep.getRuntimeClasspath().getName(),
multiDexInstrume+ ^ B Q h V K 6ntatio? 3 l * 3nDep);
}
taskManager.createAv 9 d b ? 4 _ G %ndroidTestVariantTas5 j m [ s K 8 Lks(
(TestVariantData)variantData,
variw { 3 _ d @antScopes
.stream()
.filter(TaskManager::isLintVariants I # j X q R S)
., 9 i 5 n 6 |collect(Collectors.toList()));
}else{//UNIT_TEST
//4)、否则说明该Testmoudle是用于执行单元测试的,则会创建UnitTestVariantTask。taskManager.createUnitTestVariantTam = t = K % w H sks((TestVariantData)variantData);
}
}else{
//4、如N + d a a 7 # 4果不是一个Testmoudle,则会调用ApplicationTaskManagy n X | j F I j Ger的[ k - bcreateTasksForVariM w uantScope方法。
taskManager.createTasksForVariantScope(
variantScope,
variantScopes
.stream()
.filter(TaskManager::isLin g + K R stVariant)
.collect(Collectors.toList()));
}
}
在 createTask ) [ . z gsForVariantData 方法中为每一个指P e / n定的 Variant 类型创建了与之对应的 Tasks,6 8 u该方法的处理逻辑如下所示:
-
1、创建 Assemble Task。 -
2、如果 vaH / | x z , CriantType 是 base moudle,则会创建相应的 bundle Task。需要注意C E & l & ^的是,base moudle 是指包含功能的 moudle,而用于 test 的 moudle 则是不包含功能的。 -
3、如果 variantType 是一个 testl 6 n { 0 0 + F moudle(其作为一个 test 的组件),则会创建相应的 test varianb p k ! +tk d O B s o。 -
1)、l D p q d :将 variant-spP ^ D q M g ! { Recific, build type mul; c V ^ 8 K 9 : Cti-flavor、defa^ f u v ) y G ^ultConfig 这些依赖添加到当前的 variants u y g q 8Data 之中。 -
2)、如果支持渲染脚本,则添加渲染脚本的依赖。 -
3)、如果当前 Varia x ] l $ Wnt 会输出一个 APK,即当前是执行的一个 Android test(一般用来进行 UI 自动化测试),则会创建相应的 AndroidTestVariantTask。 -
4)、否则说明该 Test moudle 是用于执行单元测试的,则会创建 UnitTestVariantTask。
-
-
4、如果不是一个 Test moudle,则O 9 _ P会调用 ApplicationTaskManager 的 createTasksForVariantScope 方法。
最终,会执行到 ApplicationTaskManager 的 createTasksForVariantScope 方法,在这个方法里面创建了适用于应用构建的一系列 Tasks。( 3 e h # / X
下面,我们就通过 assembleDebug 的打包流程来分析一下这些 Tasks。
六2 x W、assemblf 3 Y ` r u D | XeDebug 打包流程浅析
在对 assembleDebug 构建过程中的一系列 Task 分析之前,我们需要先回顾一下 Android 的打W X 7 a包流程(对这块非常熟悉的同R o ~ d学可以跳过)。
1、Android 打包流s E L l 4 0 c程回顾
Android 官方的编F ( ~ o ~ K a , –译打包流程图如下所示:
比较粗略的打包流程可简述为如下 四个步骤:
-
1)、编) E 6 % W 6 +译器会将 APP 的源代码转换成 DEX(Da? 8 C B c ylvik Executable) 文件(其中包括 Android 设备上运行的字节码),并将所有其: ) _ B u他内容转换成已编译资源。 -
2)、APK 打包器将 DEX 文件和已编译的资源合并成单个 APK。 但是,必须先签署 APK,才能将应用安装并部署到 AF 1 W 2 % a f y 7ndroid 设备上。 -
3)、APK 打包器会使用相应的 keysX ` 0 v Q ctore 发布密钥库去签署 APK。 -
4)、在生成最终的 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时] V h占用的内存。
为了 了解更多打包过程中的细节,我们需要查看更加详细的旧版 APK 打包流程图 ,如下图所示:
比较详细的打包流程可简述M T 3为如. p H U | )下 八个步骤:
-
1、首先,.aidl(Androids z b I Y @ Interface Descripti* t Xon Language)文件需要通过 aidl 工具转换成编译器能够处理的 Java 接口文件。 -
2、同时,资源文件(包括 AndroidN * 8 9 & + rManife$ f ( ~ + @ /st.xml、布局文[ Z h ^件、各种 xml 资源等等)将被 AAPT(Asset Packaging Tool)(Android Gradle Plugin_ O a w : t m 3.0.0 及之后使用 AAPT2 替代了 AAPT)处理为最终的 resources.arsc,并生成 R.C w Gjava 文件以保证源码编写时可以方便地访问到这些资源。 -
3、然后,通过 Java Compiler 编译 R.java、Java 接口文件、Java 源文件,最终它们会统一被编译成 .class 文件。 -
4、因为 .class$ % = $ m 并不是 Android 系统所能识别的格式,所以还需要通过 dex 工具将它们转化为相应的 Dalvik 字节码(包含压缩常量池以及清除冗余信息等工作)。这个过程中还会加入应用所依赖的所有 “第三方库”。 -
5、下一步,通过 ApkBuilder 工具将资源文件、DEX 文件打包生成 APK 文件。 -
6、接着,系统将上面生成的 DEX、资源包以及其它资源通过 apkbuilder 生成初始的 APK 文件包。 -
7、然后,通过签$ A B o = i # E名工具 Jarsigner 或者其它签名工具对 APK 进行签名得到签名后的 APK。如果是在 Debu8 I ~ j [ p ? 6g 模式下,签名所用的 keystore4 , $ v 9 w # , 是系统自带的默认值,否则我们需$ 9 9 r N要提供自己的私钥以完成签名过程。 -
8、最后,如果是正式版的 APK,还会利用 ZipAlign 工具进行对齐处理,以提高程序的加载和运行速度。而对齐的过程M H R T I O就是将 APK 文件中所有的资源文件距离文件的起始位置都偏移4字节的整数= K z / S ) c倍,这样通过 mmap 访问 APK 文件的速度会更快,并且会减少其在设备上运行时的内存占用。
至此,我们已经了解了整个 APK 编译和打包的流程。
那么,为什么 XML 资源文件要从文本格式编译成二进制格式?
主要基于以下 两点原因
:
-
1、
空间占用更小
:因为所有 XML 元素的标签、( ) Z ] J X v属性名称、属性值和内容所涉及到的字符串都会被统一收集到一个字符串资源池9 | b D J中,并且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而可以减少@ e ? B文件的大小。 -
2、
解析效率更高
:二进制格式的 XML 文件解析速度更快。这是由于二进制格式的 XML 元素里面不再包含有字符串值,因e D U v T k 1此就避免了进行字符串V | B解析,从而提高了解析效率。
而 Android 资源管理框架又是如何快速定位到最X c q 5匹( f + R K R配资源的?
主要基于两个文件,如下所示:
-
1、 资源 ID 文件U ( N 1 ~ N R.java
:赋予每一个非 assets 资源一个 ID 值,这些 ID 值以常量的形式定义在 R.java 文件中。 -
2、 资源索引表 resources.arsc
:用来描述那些具有# G ? ID 值的资源的配置信息。
2、assmableDebug 打包流程浅析
我们可以通过下面的命令来获取打包一个 Debug APK 所需– c C L要的执行的 T3 F 8 I 2 0 B 0ask,如下所示:
quchaoC i j D@quchaodeMacD 8 7Book-ProCustomPlugin%./gradlewT w {app:assembleDebug--console=plain
...
>Task:app:preBuildUP) v | ) # %-TO-DATE
>Task:app:preDebugBuildUP-TO-DATE
>Task:app:generateDebugBuildConf? ] l Y l O rig
>Task:app:javaPreCompileDebug
>Task:app:mainApkListPersistenceDebug
>Task:app:generateDebugResValues
>Task:app:c{ ^ y { ( 5 F K createDebugCompatibleScreenManifests
>Task:app:extr ; H + _ ^ %actDeepLinksDebug
>Task:app:compileDebugAidlNO-SOURCE
>Task:app:compileDebugRendersd / u t - # { criptNO-SOURCE
>Task:app:generateDebugResources
>Task:app:processDebugManifest
>Task:app:mergeDebugResources
>Task:app:proF ( 8 & wcessDebugResources
>Task:app:: $ @ * I mcompileDebugJavaWithJavac
>Task:app:compileDebugSources
>Task:app:mergeDebugShaders
>Ta1 w C ^ u dsk:app:compileDebugShaders
>Task:app:genery } } F A L h x lateDebugAssets
>Task:apY g A y Y p C x }p:mergeDebugAssets
>Tat , S Ysk:app:proy c ~ c /cessDebugJavaResNOp ( e O s 2-q R ` { B }SOURCE
>Task:app:checkDebugDuplicateClasses
&gQ k m F : 9 v } jt;Task:app:dexBuilderDebug
>Task:app:mergeLibDexDebug
>Task:ap% - S ( 5 Ip:merge _ K K $ 6 F : pDebz C * Q fugJavaResT 7 I A 3 1 Z dource
>Task:app:mergeDebux ; ? c ogJniLibFolders
&_ : Z X [ Agt;Task:app:vaS r M | N c a ClidateSigningDebug
>Task:app:mergeProjectDexDebug
>r 7 ? ` N q s;Task:app:mergeDe( O [bugNativeLibs
>Task:app:stripDebugDebugSymbols
>Task:app:desugarDebugFileDependencies
>Task:app:mergeExtDexDebug
>Task:apu d O F ~ i % Vp:packageDe U mbug
>Task:app:assembleDebug
在 TaskManager 中,主要有两种方法用来去创建 Task,它们分别为 createTasksBeforeEvaluate
方法与 createTasksForVariantScope
方法。需要注意的是,createTasksForVariantScope 方法是一个抽象方法,其具体的创建 Tasks 的任务分发给了 TaskManager 的子类进行处理,其中最常见的子类要数 Applicat5 h + g c PionTaskManager 了,它就是在 Android 应用程序中用于创建 Tasks 的 Task 管理者。
其中,打包流程中的大部分 tasks 都在这个目录之下:
com.android.buiZ % j T l b C {ld.gradle.internal.tasm C # , + 5 sks
下面,我们看看 assembleDebug 打包流程中所需的各个 Task 所对应的实现类与含义,如下表所示:
Task | 对应实现类 | 作用 |
---|---|---|
preBuild | AppPreBuildTasku j 2 3 * b b . A | 预先创建的 task,用于做一些 application Varia: ? w ] ~ C H 9nt 的检查 |
preDebug] d 1 B T h n + NBuild | 与 preBuild 区别是这个 task 是用于在 Debug 的环境下的一些 Vrariant| ] K # S 检查 | |
generateDebugBuildCon{ % {fig | GenerateBuildConfig | 生成与构建目标相关的 BuildConfig 类 |
javX 8 7aPreCompileDebug | JavaPreCompileTask | 用于在 Javae @ O 编译之前执行必要的 action |
mainApkListPersistenceDebug | MN N 7ainApkListPersistence | 用于持久化 APK 数D . 4 据 |
generated s M + ! m 7 – =DebugRk ? o a Q A # p (esValues | GenerateResValues | 生成 Res 资源y $ h d D X类型值 |
createDebugCompatiu f ; w q D l vbleScreenManifests | Compt g z A [ & katibleScreensManifest | 生成具有给定屏幕密度与尺寸列表的 (兼容屏幕)节点清单 |
extractDeepO ! s _ + ,LinksDebug | ExtractDeepLinksTask | 用] o [ , Y于抽取一系列 Deef 8 C dpLink(深度链接技术,主要应用场景是通过Web页面直接调用Android原生app,m [ ] [ O !并且把需要的参数通过Uri的形式,直接传递给app,节省用户的注册成本) |
compileDebugAidl | AidlCompile | 编译 AIDL 文件 |
compileDebugRenderscript | Renderscrip* l h ] L [ EtCompile | 编译 Renderscript 文件 |
generateDebugResourcesF I X | 在F H / N O P u n TaskManager.cj f = D { K F [reateAnchorTasks 方法中通过 taskFacto$ l p C ury.register(taskName)的方式注册一个 task | 空` D o I 2 } task,锚点 |
processDebugManifest | P, u v c J 3rocessApplicationManifest | 处理 manifest 文件 |
mergeDebugResourcy | 2 6 +esv 3 E b a # 9 | MergeResources | 使用 AAPT2 合并资源文件 |
processDebugResources | ProcessAndroidResources | 用于处理资源并生P L M 8 e成 R.class 文件 |
compileDebugJavaN j m m | 3WithJavac | JavaCompileCreationAction(这里是一个 Actioz 4 4 s } 5n,从 gradle 源码中可以看到从 Task4 { N !Factory 中注册一个 Action 可以得到与之对应的 Task,因此,Task 即 Action,Action 即 Task) | 用于执行 Java 源码的编译 |
cJ f ~ pompileDebugSources | 在 TaskManager.createAncp ` = z c OhorTasks 方法中通过 taskFactory.! d – e v Z + R ~register(taskName)的方式注册一个 task | 空 task,锚点使用 |
mergeDebugShaders | MergeSourceSetFolders.MergeShaderSourceFol– 0 – X ` X 8dersCreationC _ i – | ) + UAction | 合并 Shader 文件 |
compileDebugShaders | ShaderCompile | 编译 Shaders |
generateDebugAssets | 在 TT 6 &askMana[ 5 1 @ Tger.createAnchorTasks 方法中通过 taskFactory.register(taskName)的方式注册一个 task | 空 task,锚点 |
mergeDebugAssets | MergeSourceSetFolders.Mg 2 b & CergG = E ` K 3eAppAssetCreationAction | 合并 assets 文件 |
processDebugJava] t o c n % fRes | ProcessJavaResConfigAction | 处理 Java Res 资源 |
checkDebugDuplicateClasses | CheckDuplicateClassesTask | 用于检测工程外部依赖,确保不包U O X p = e含重复类 |
dexBuilderDebug | DexArchiveBuilderTask | 用于将 .class 文件转换成 dex archives,即 DexArchive,Dex 存档,可以通过 addFile 添Z ] I加一个 DEX 文件 |
mergeLibDexDebug | DexMergingTask.DexMergingActC D Oion.MERGE_LIBRARY_PROJECT | 仅仅合并库d K = ~ U ] @ x 2工程中的 DEX 文件 |
merg, } O T ` r = :eDebugJav) @ paResource | MergeJavaResourceTask | 合并来自多个 moudle 的 Java 资源 |
mergeDebugJniLibFolders | MergeSourceSetFolderU o K ] V [ m :s.MergeJniLibFoldersCreav g f 5 3 R r A ttionAction– s ` | 以合适的优先级合并 JniLibs 源文件夹 |
validateSigningDebug | ValP i M H xidateSigningTask | 用于检查W – x a 3 O N当前 Variant 的签名配置中是否存在密钥库# 0 8 : ! – `文件,如果当前密钥库默认是 debug keystore,即使它不存d M ` F 2 J 1 e y在也会进行相应的创建2 ] c 2 |
mergeProjectDexDebuI – % . ! 3 I ig | DexM9 8 lergg d A V jingTask.DexMergingAction.MERGE_PROJECT | 仅仅, g ) } , I v U T合并工程的 DEX 文件 |
mergeDebugNativeLl ] –ibs | M5 6 vergeNativeLibsTask | 从多个 moudle 中合并 native 库 |
stripDebugDebugSymbols | StripDebugSymbolsTask | 从 Native 库中移除 Debug0 p l B # = V 符号。 |
desugarDebugFt $ @ p 3 t ^ DileDepp l b 9 ,end x } l D ~ | aden_ F [ E j Dcies | DexFileDependenciesTask | 处理 Dex 文件的依赖关系Z b – l |
mergeExtDexDebug | DexMergingTask.DexMergingAc3 Y %tion.MERGE_EXTERNAL_LIBS | 仅仅用于合并外部库的 DEX 文件 |
packageDebug | PackageApplication | 打包 APK |
assembleDebug | Assemble | 空 task,锚点使用 |
目前,在 Gradle Plugin 中主要有三种类型的 Task,如下所示:
-
1)、 增量 Task
:继承于 NewIncrd 1 f Z j u 3 U )ementalTask 这个增量 Task 基类,需要重写 doTaskAction 抽象方法实现增量功能。 -
2)、 非增量 Task
:继承于 N[ , h u | ~ –onIncrementw n 4 D _ G N yalTask 这个非增量 Task 基类,重写 d– % ^oTaskAction 抽象方法$ o ?实现全量更新功能。 -
3)、 Transform Task( % = b | 1
:我们编写的每一个自定义 Transform 会在调用 appExtension.registerTransform(new CustomTransform()) 注册方法时将其保存到当前的` E U . c 5 | N | Extension 类中的 transforms 列表中,当 LibraryTaskManager/TaskManager 调用 createPostCompilationTasks(负责为给定 Variant 创建编译后的 task)方法时,会取出相应 Extension 中的 tranforms 列表t o a进行z p 3 ? Z遍历,并通过 Transfoh X q SrmManager.addTransform 方法将每一个 Transform 转换为与之对应的 Tra/ | | 6 gnsformTask 实例,而该方法内部具体是通过 new TransformTasc j K . I Wk.CreationAction(…) 的形式进行创建。
全面了解了打包过程中涉及到的一系列 Task~ ~ h t s * ? | Rs 与 Task 必备的一些基础知识之后Q b . W Y I H V Y,我们再来对其中最重要的几个 Task 的实现来进行详细分析。
七、重要 TaG u H Hsk 实现源码分析
1、资源处理相关 Task
1)、processDebugManifest
processDebugManifest 对应的实现类为 ProcessApplicationManifest Task,它继承了 IncrementalTask,但是没有实现 is_ 1 u * a ~ | cIncremental 方法,因此我们只需n 2 s T @ : H看其 doFu| + p y z 9llTg b Z *askAction 方法即可。
调用链路
processDebugManifest.dofFullTaskAcq d ytion => ManifestHelperKt.mergeManifestsForApplication => Manife] 7 % # ( v S | %stMerge2.merge
主要流程分析
这个 task 功能主要是 用于合并所有的(包括 modu) S +le 和 flavor) mainfest,其过程主要是利用 MergingReport,ManifestMerger2 和 XmlDocument 这三T d j个实例进行j d 1 _ G 9 Z /处理。
我们直接关注到f ] F I K b a ManifestMerger2.merge 方法的 merge 过程C H } X a i A,看看具体的合并是怎样的。其主体步骤如下所示:? ] D
1、获取主 manifest 的信息,以做一些必要的检查,这里会返回一个 LoadedManifestInfo 实例。
//loadthemainmanifestfiletods L f W 8 % V ]osomecheckingalonM 8 ? C 0 w Ugtheway.
LoadedManifestInfb 7 = / j A {oloadedMainMan; K x v 5ifestInfo=
load(
newManifestInfoH E ^(
mManG y 4 T ` I H -ifestFile.getName(),
mManifestFile,
mDocu0 F g Z $ #mentType,
Optional.absent) ] 4 { L()/*mainManifestPackageName*/),
selectors,
mergingReportBuilder);
2、执行 Manifest 中的系统属性注入:将主 Manifest 中定义的某些属性替换成 gradle 中定义的属2 ) } E性,例如 pac` ? I $ Rkage, version_code, version_name, m) P N i w 7in_sdk_versin 、t9 7 H a w x C 3 karget_sdk_version、max_sdk_version 等等。
/T % @ 4/performsystempropertyinjection
performSystemPropertiesInjection(mergingReportBuilder,
loadedMainManifesp - ) [ } ` xtInfo.getXmlDocument());
/**
*Perform{@lin7 $ +kManifestSystemProperty}injection.
*@parammergingReporttologactionsR L 5 2 8 0 oanderrors.
*@paramxmlDocumentthexp 1 r ( $ 1mldocumenttq g 8 u ? 5 Roinjectinto.
*/
protectedvoidperformSyst# d % Q _ ~ 4 XemPropertiesIn^ - t % J b .jection(
@NonNullMergingReport.BuildermergingReport,
@NonNullXmlDocumentxmlDocu$ N s kment){
for(ManifestSy- _ = - 0 VstemPropertymanifestSystemProperty:Manif~ j ~ B D 0 r (estSystemProperty.values()){
StringpropertyOverride=mSystemPropertyResolver.getValue(manifestSystemProperty);
if(propertyOverride!=null){
manifestSystemProperty.addTo(
mergingReport.getActionRecorder(),xmlDocument,propertyOverride);
}
}
}
3、合并 fle T y )avors 并且构建与之对应的 manifest 文件。
for(FileinputFile:mFlavorsAndBuildTypeFiles){
mLogger.verbose("Mergingflavorsandbuildmanifest%sn",inputFileS D # a ` :.getPath());
LoadedManifestInf] Q x # , m 4 9 JooverlayDocumenJ N , V G ] 0 Rt=
load(
newManifestInfo(
null,
inpud l ? W k . VtFile,
XmlDocument.Type.L # Z $ U l .OVERLAY,
mainPackageAttribute.transform(itc C q # z _->it.getValue()I 3 O ( ) C))- a Q / b 9 R ~,
selectoo I S 8rs,
mergingReportBuilder);
if(!mFeatureName.isEmpty()){
overlayDocu} v E T L b Jment=
removeDynamicFeatu. r 9 BreManifesA E & $tSplitAttributeIfSpeck U O 5 L ified(
overlayDocument,mV Z i K :ergingReportBuilder);
}
//1、检查packa{ j F 6 C ? f z gge定义
Optional<XmlAttribute>pacG R b M I = ! [ !kageAttribute=
overlaB b x W T h ( FyDocument.ge( C [ X htXmlDocument().getPackage();
//ifbotG 0 q =hfilesdeclareapan E u w ! l pckagename,itshouldbethes) t )ame.
if(loadedMainManifestInfo.getOriginalPackageName().isPresent()&&a0 q w Qmp;
packageAttribute.isPresent()
&&!loadedMainManifestInfo.getOriginalPackageName().get().equals(
packageAttribute.get().getValue())){
//2、如果package定义重复的话,会输出m F a ~下| | [ $面信息
Stringmessage=mMergeType==MergeType.APPLICATION
?String.format(
"Overlaymanifest:packageattributedeclaredat%1$svalue=(%2$s)n"
+"thasadifferentvalue=(%3$s)"
+"declaredinmainmanifestat%4$sn"
+"tSuggestion:removetheoverlay) / a 2 2 i s { ideclarationat%5$s"
+"tandplaceitinthebuild.gradle:n"
+e o V 7 B"ttflavorName{n"
+"tttapplicationId="%2$s"n"
+"tt}S P _ % C g",
packageAttribu[ r ]te.get().printPosition(),
packageAttribute.get().getValue(),
mainPackageAttribute.get().getValue(),
mainPackageAttribute.get().printPosition(),
packageAttribute.get(8 & { q 4 * m).getSourceFile().print(true))
:String.format(
"Overlaymanifest:packageattributedeclaredat%1$svalue=(%2$s)n"
+"4 I 2thasadifferentvalue=(%3$s)[ = = o y @ l"
+"declaredinmainmanifestat%4$s",
packageAttribute.get().printPosition(),
packageAttribute.get().getValue(),
mainPackageAttribute.get().getValue(),
mainPackageA ( : . /Attribute.geo d 7 Rt().printPosition());
mergingReportBuilder.adD I P ~ U I ^ PdMessage(
ovi + T zerlA h V ; j t w nayDocumen! t j 0 lt.getXmlDocument().getSous + ] frceFile(),
MergingReport.Record.Severity.ERROR,
message);
returnmergingr y j 7 M 5ReportBuilder.build();
}
...
}
4、合并库中的 manifest 文件
for(LoadedManifestInfolibraryDocument:loadedLibraryDocuments){
mLogger.verbose("Merginglibrarymanifest"+libraryDocument.getLocation());
xmlDock z F k 5 qumentOptional=merge(
xmlDocumentOptional,libraryDocument,mergingReportBuilder);
if(!xmlDocumentOptional.isPresent()){
returnmergingReportBuilder.c r cbuild(8 a ? L f );
}
}
5、执行 manifest 文件中的 placeholder 替换
performPlaceHolde5 . *rSubstitu [ h | / e ( b 1ution(
loadedMainManifestInfo,
xmlDocumentOptional.get(),
mergingReportBuilder,
severity);
6、之后对最终合并后的 manifest 中的一些属性进行一次替换,与步骤 2 类似。
7、保存 manifest 到 build/intermediates/merged_manifests/flavorName/AndroidManifest.xml,至此,已生成最终的 Manifest 文件。
2)、mergeDebugResources
mergeDebugReO I 0sources 对应的是 MergeResources Task,它 使用了 AAPT2 合并资源。
调用链路
MergeR & ^ A B ?esources.doFu % A mullTaskAction=>ResourceMerger.mergeData=&gR ; V + a b Ct;MergedResourceWriter.ene R d dd=>mResourceCompiler.submitj 4 c u & FCompile=>AaptV2P ] BCommandBuilder.makeCompileCommand
主体流程分析
MergeResoz D p a $ 3urces 继承自 IncrementalTask,对于 增量 Task 来说我们只需看如下三个方法的] X g e u g l ]实现:
-
isIncremental
-
doFT ( R ! : qullTaskAction
-
doIncrementalTaskAction
1、首先查看 isIncremental 方法。
//说明MergeReso[ V ] Q Z MurcesTask支持增量,肯定重写了doIncrementalTas[ B W Y i O J } qkAction方法
protectedbooleanisIncremental(){
returntrue;
}
2、然后,查看 doFullTaskAction 方法,内部通过 getConfiguredResourceSets 方法获取了 resourceSets,包括了自己的 res 和/ @ O ? Q ] p依赖库的 res 资源以及 build/generated/res/rs。
List<ResourceSet>@ 8 % k 3 rresourceSe( ) o [ Ets=getConfiguredResourceSets(prepro= % ; rcessor);
3、创o ) + ( ) d @ P建 ResourceMerger,并使用 resourceSets 进行F V n I Q x 1 4 e填充。
R# o @ L ` K b TesourceMergermerger=newResourceMerger(minSdk.get());
4、创建 ResourceCompilationService,它使用了 aapt2。
//makeAapt中使用aapt2,然后返回ResourceCompilationService实例.
Res0 m 2 ~ p e : =ourceCompilationServiceresourceCompiler=
getResourceProcessor(
getAapt2FromMaven(),
workerExecutorFacade,
errorFormatMode,
flags,
pr] . ~ MocessResources,
getLogger()J ^ * , a # v m n)){
5、将第 2 步获取的 resourceSet4 i 1 H q v n 加入至 ResourceMerger 中。
for9 p H 2 y O j w r(ResourceSe3 5 a $ ) - N 3tresourceSet:resourceSets){
resourceSet.loadFromFiles(newLogR 2 _ Z l O ; [ 0gerWrapper(getLogger()));
merger.addDataSet(resourceSet);
}
6、创建 MergedResourceWriter
MergedResourceWriterwrites 9 N @ fr=
newMergedResourceWriter(
wor% o B n 0 ]kerE0 j v } ? -xecutorFacad$ ! E o @ : _ ce,
destinatioi , MnDir,
publicFile,
mergingLog,
preprocessor,
resourceCompiler,
getIncrementalFolder(),
dataBindingLayoutProcessor,
merge% R / & # : odNotCompiledReso. _ z l } 9 * PurcesOutputDirectory,
pseudoLocalesu Y G : [ # O ?Enable+ j 2 ^ f r 5 dd,
getCrunchPng());
7、调用 ResourceMerger.mergeData 方法对资源进行合并。
merger.mergeData(writer,false/*doCleanUp*/);
8、调用 Mergh U g 1edResourceWriter 的 start,ignoreItemInMerge、removeItem、addItem,end 方法,其中 item 中包括了需要处} G B , 7 f u理的资Q y T源,包括K D e r y d / { ] xml 和 图片资源,每一个 item 对应的文件,都会创建一个与之对应的 CompileResourceRequest 实例,并加入到 mCompileResourceRequests 这个 ConcurrentLinkedQueueI q R e v 队列中。
9、调用 mReso5 : C f : K A ? hurceCompiler.submitCompile 方法处理资源。
//MergedResourceWriter.end()
mResouq . h g w 2 r ZrceCompiler.submitCompile(
newCompileResourceRequest(
fileToCompile,
request.getOutputDirectory(),
request.getInputDirectoryName(),
request.getI} Z wnputFileIsFromDependency(),
pseudoLocalesEnabled,
crunchPng,
Imo . n c % ) }mutabX / 2 S b m 2 ` hleMap.of(),
request.getInputFile()));
mCompiledFileMap.put(
fj C { f O 6 $ileToCompile.getAbsolutePath(),
mResourceCompiler.compileOutputFor(request).getAbsolutePath());
在 submi] Y B t UtCompile 中最终会使用 AaptV2Comma9 z 7 ^ 5 Z 4 NndBuilder.makeCompileCommand 方法生成 aapt2 命令去处理资源。
10、最后,对 doIncrementalT9 c –askAction 的实现我这里就不赘述了,因为增量 task 的实现过程和全量实现差异不大,仅仅是使; { u 0 ~ | p用修改后的文件去获取 resourceSets 。
2、将 Class 文件打包成 Dex 文件的过程
即 dexBuilderDebug,它具1 ? N体对应的是 DexArchiveBuilderTask,用于将 .class 文件转换成 dex archives,即 Dex 存档,它可以通过 addFile 添加一个 DEX 文件。
调用链路
DexArchiveBuilderTask.doTaskAction=>DexArchiveBuilderTaskDelegate.doProcess=>H m # E p C;DexArchive n HeBuilderTaskDelegate.processClassFromInput=>DexArchiveBuilderA ` $ | l u &TaskDelegate.convert[ ` _ToDexArchive->Dev S j 9 sxArchiveBuild( X *erTaskDelegate.launchProcessing->DexArcR 1 KhivW z K m geBuilder.convert
主体流程分析
在 DexArchiveBuildg 8 n * ) OerTask 中,对 class 的处理方式分为两G ! 6 ] 7 s 2 h F种,一种是对 目录下的 cln O ` Iass 进行处理,一种是对 .jar 里面的 class 进行处理。
那么,这里为什么要分为这两种方式呢?
因为 .jar 中的 class 文件通常来说都是依赖库,基本, ) 6 u / c ` 0 X上不会改变,所以 gradle 在这里就可以实现一个缓存操作。
1、convertJarToD6 H 2 R ( & x ~exArchive 处理 jar
在处理 jar 包的时候,Gradle 会对 jar 包中的每# C B } Q = 2 q ~一个 class 文件都单独打成一个 DEX 文件,然后再把它们放回 jar 包之中。
privatefunconvertJarToDexArchive(
jarInput:File,
outputDir:File,
bootclasspath:ClasspathServiceKeE Q G q / e ; Jy,
classpath:ClasspathServiceKe: H e W _ [ } ` =y,
cacr & ] x k eheInfo:D8DesugaringCacheInfo
):Liste X<File>{
if(cacheInfo!==DesugaringDont: ! B @ C X eCache){
valcachedVersion=cacheHandler.getCachedVersionIfPrj W u $ j C 4 / ^esent(
jarInput,cacheInfo.orderedD8DesugaringDependencies
)
if(cachedVersion!=null){
//如果有缓存,直接使用缓存的jar包。
valoutputFile=getOutputForJar(jarInput,outputDir,null)
Files.copy) } ) y p 3 ((
cachedVersion.H 9 L V G - G T KtoPath(),
outputFile.toPath(),
StandardCopyOption.REPLACEd N c_EXISTING
)
//noneedtotrytocacheanalrem ] U 8 = ? 3 - sadycachedversion.
returnlistOf()
}
}
//如果没有缓存,则v e ` u调用convertToDexArchive方法去生成dex。
returnconvertToDexArchive(
jarInput,
outpup { {tDir,
false,
bootclasspath,
classpath,
setOf(),
setOf()
)
}
2、使用 convertToDexArchive 处理 dir 以及 jar 的后续处理
内部会调用 lar i PunchProcessing 对 dir 进行处理,代码如下所示:
privatefunlaunchProcessing(
dexConversionParameters:DexArchiveBuilderTaskDelA F degate.DexConversionParameters,
outStream:OutputStream,
erc Z S g 7 r R 7 rStream:OutputStream,
receiver:MessageReceiver
){
valdexArchiveBuilder=dexConversionParam3 9 U + u Yeters.getDexAr- 1 V [ } T YchiveBuilder(
outStream,
errStream,
receiver
)
valinputPath=dexConversionParameters.input.toPath()
valhasInc] s f m _rementalInfo=
dexConversionParameters.input.isD~ ) ) F D Hirectory&&dexConversionParameters.isIncremental
//p E a r如果class新增||修改过,就进行处理
funtX P 8oProcess(path:String):BoS * p 4 ^olean{
if(!dexConversionParameters.belongsToThisBucket(path))returnfalse
if(!hasIncrementalInfo){
retur@ r H n b f g )ntrue
}
valresolved=inputPath.resolve(path).toFile% t . u + j a ] H()
returnresolvedindexConversionParameters.addiR Z 1 @tionalA V ]Paths||resolvedindexConv0 + Z m g [ } 2 hersi+ t J i YonParameters.changedFiles
}
valbucketF5 ] n Q ! Gilter={name:String->toProcesx q s ( 4 ( ,s(naS V . b l z Pme)}
logY y e ager 0 T s oWrapper.verbose("Dexing'"+inputPath+"'to'"+dexConvS a s EersionParameters.output+"'")
try{
ClassFileInputsY m v _ E / G.fromPath(inputPath).use{input->
input.entries(buc[ x ?ketFilter).use{entries->
//内部会调用dx||d8去生成dex文件
dexArch! L m $ n i Q ^iveBuilder.convert(
entries,
Paths.get(URI(dexConversionParameters.output))n N m V m a,
dexConversionParameters.input.isDir. R H $ectory
)
}
}
}catch(ex:DexArchiveBuilderException){
throwDexArchiveBuilderException("Failedtoprocess$inputPath",ex)
}
}
可以看v R + % H到,在 DexArchiveBuil* U d v C S x k .der 有两个子类,它们分别如下所示:
-
D8DexArchiveBuilder
:调用 D8 去生成 DEX 文件。 -
DxDexArch5 A L NiveBuilder
:| { @ t调用 DX 去生成 DEX 文件。
我们这里就以 D8DexArchiveBuilder 为例来说明其是如何调用 D8 去生成 DEX 文件的。其源码如下所示:
@Override
publicvoidconvert(
@NonNullStream<Cx / hlassFileEntry>input,@NonNullPathoutput,booleanisIncrementn 8 } , } ? 4 $ xal)
throwsDexArchiveBuilderExceptio j Cn{
//1、创建一个D8诊断信息处理器实例,用于发出不同级别的诊断信息,共分为三类,由严重程度递减分别为:error、warning、info。
D8Diagnostz o v A p T % #icsHandld K perd8Diag[ m ] x W ( 2nosticsHandler=newInterceptingDiagnosticsHandler();
try{
//2、创建一个$ ( q QD8命令构建器实例。
D8# X WCommand.Builderbuilder=D8Command.builder(d8DiagnosticsHandler);
AtomicIntegerentryCount=newAtomicInteger();
//3、遍历读取每一个类的字节数据。
inputk k V D Q I g ) k.forEach(
entry->{
builder.addClassProgramData(
readAlv V % 7 S A .lBytes(entry),D8DiagnosticsHandler.gY + % t I Q Y 1etOrigin(entry));
entryCount.incrementAndGet();
});
if(entryCount.get()==0){
//3、如果没有可遍历的数据,则直接return。这里使用Atom? @ YicIntegerN p i v . d W 4类来实现了是否有遍历了数据的区分处理。v z j m i : r )
retuC M J yrn;
}
OutputModeoutputMode=
isIncremental?OutputMoJ . l i T @de.DexFilePerClassS q ] ? G File:OutputMode.DexIn4 ~ Ad! e + .exed;
//4、给D8命令构建器实例设置一系列@ | % - n P的配置,例如编译模式、最小Sdk版本等等。
builder.setMode(compilationMode)
.setMinApiLevel(mS U a O 4 ^ : v pinSdkVersion)
.setIntermediate(true)
.setOutput(output,outputMode)
.setIncludeClassesChecksum(compilatiI M b I Z oonMode==compilationMode.DEBUG);
if(desugaring){
builder.addLibm r A ~raryResourceProvider(bootClasspath.getOrderedProvider());
builder.addClasspathResourceProvider(classpath.getOrderedPrT S 5 w Z 3 oovider());
ifD G @ E 2 Z A {(libConfiguratj T = gion!=null){
builder.addSpecialLibraryCos y e 2 ~ Infiguration(libConfiguration);
}
}else{
buildea * j @ & n T * ,r.setDisableDesugaring(true);( % 7 v | T g
}
//5、使用com.android.tools.r8工具包中的D8类的run方法运行组装后的D8命令。
D8.run(builder.build(),MoreE) Z o , : | I @xecutors.n} ) 6 q iewDirectExecutorService());
}catch(Throwablee){
throwget{ x AExceptionToRethrow(e,d8DiagnosticsHaf x b Yndler);
}
}
D8DexArchiveBuildeY m n ^r 的 convert 过程可以归纳为 五个步骤
,如下所示:
-
1)、创建一个 D8 诊断信息处理器实例,用于发出不同级别的诊断信息,共分为三类,由严重程度递减分别为:erroM E Q q ar、warning、info。 -
2)、创建f [ c一个 D8 命令构建器实例。 -
3)、遍历读取每一个类的字节数据。 -
4)、给 D8 命令构建器实例设置一系列的配置,例如 编译模式、最小 Sdk 版本等等。 -
5)、使用 com.andro c $ w `id.tools.r8 工具包中的 D8 类的 run 方法运行组装后的 D8 命令。
八、总结
我们再回头看看开篇时的那一幅 Gradle 插件的整体实现h ` *架构图,如下所示:
最后的最后
我们可以z ( ( 根据上面这幅图,由下而上细细地思考回忆一下,? R ` k 2 2每一层的主要流程是什么?其# # g +中涉及的一些关键细节具体有哪些?此时,你是4 P 4 h # f { % K否觉得已经真正地理解了 Gradle 插件架构的实现原理呢?
参考链接:n % 9
1、And$ 5 y v W + Groid Gradle Plugin V3.6.2 源码
2、GV ) . J I ` Hradle V5.6.4 源码
3、And! X 8 n O – u =roid Plugin DSL Reference
4、Gradle DSL Reference
51 T + Y W $ G w S、designing-gradle-plugin] G As
6、andrX * o 1oid-tra2 m }ining => gradle
7、连载 | 深入理解Gradle框* f C M架之一:Plugin, Extension, buildSrc
8、连载 | 深入理解g; W % * T U Zradle框架之二:依赖实现分析
9、连载 | 深入理解gradle框架之三:artifacts的发布
10、AndroiG # & l ; id Gradle Plugin 源码解析(上. Q M ; 2 6 _ s f)
11、Android Gradle% n ] 7 , Plugin 源码解析(下)
12、Gradle 庖丁解牛(构建源头源码浅析)
13、GradU w |le 庖丁解牛(构建生命周期核心委托对象创建源码浅析)
ContI 9 v } T ; *anct Me
● 微信:
欢迎关注我的微信:
bcce5360
● 微信群:
由于微D P 2 m信群已超过 200 人,麻烦大家想进微信群的朋友们,加我微信拉你进群。
● QQ群:
2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎大家加入~
About me
-
Email: chao.qu521@gmail.com
-
Blog: jsonchao.github.io/
-
掘金: juejin.im/user/5a3ba9…