Android构建的过程,其中很重要的部分就是资源的构建,AGP通过把项目中资源进行link、complie、merge…等这些操作,最终把需要的资源打包进入APK中。如果大家熟悉AGP流程它是把这些操作封装各种Task来完成,但除此之外AGP还提供很多方便的API帮助我们动态化的构建。
gradle用到的脚本是*.gradle.kts格式,插件语言也是使用的kotlin
添加资源SourceSet 很多开发者在项目都会用到SourceSet来设置java/kotin文件,其实资源文件也可以使用同样的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 android{ sourceSets { getByName("main" ){ java{} kotlin{} res{ srcDir("test/res" ) srcDirs("test1/res" ,"test2/res" ) } resouces{} } } }
经过上面这段代码设置以后,Android的项目结构目录会为新建的文件生成资源文件夹的标志: 这种直接添加了完整的文件夹方式,在实际的项目开发中常用来解决工程上的问题:
模块组件的合并,比如业务和架构的改动,致使原来分散在不同组件的代码要合并成一个,Java和Kotlin的部分,只要通过包名来区分即可。而资源文件完全可以通过sourcesSets的方式来解决。
动态资源的构建。假如一个工程是依赖云端某些资源包(文字、图片、安全密钥等等)的下发。一些面向国际化大型app,各个组件需要用到各个国家的多语言,作为组件的开发者最佳方案应当不用关注用来那个国家的语言,只需文字的key,然后项目根据key动态生成多语言。
1 2 3 4 5 6 7 8 9 10 11 androidExtension.onVariants{variant-> val variantCapitalizedName=appVariant.name.capitalized()\ afterEvaluate { project.tasks.named("pre${variantCapitalizedName} Build" ) .configure { doFirst { } } } }
AndroidManifest.xml占位与合并 AGP中有一个针对AndroidManifest.xml的使用特性——占位符与值替换;当AndroidManifest.xml中某些值不能再开发的时确定,而依赖于渠道信息、构建类型、甚至是编译时动态从环境中获取信息,我们完全可以使用占位符预占坑,再通过AGP的相关DSL针对行具体情况配置不同的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="xxxx" > <application ... > <activity android:name =".MainActivity" ... > <intent-filter > ... <data android:scheme ="https" android:host ="${hostName}" /> </intent-filter > </activity > </application > </manifest >
1 2 3 4 5 buildTypes{ getByName("debug" ){ manifestPlaceHolders["hostName" ]="debug.bar.com" } }
更灵活的方式就是通过Variant组合进行操作,
1 2 3 4 5 6 7 8 9 appExtension.onVariants( appExtension.selector() .withBuildType("debug" ) .withFlavor(Pair("server" ,"production" )) ){variant-> variant.manifestPlaceholders .put("hostName" ,"pre-live.bar.com" ) }
常规XML健值对资源插入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 appExtension.finalizeDsl { appExt -> with(appExt) { buildTypes { getByName("debug" ) { resValue("string" , "app_token1" , "123" ) } } } } appExtension.onVariants(appExtension.selector() .withBuildType("debug" ) .withFlavor(Pair("server" , "production" ))) { variant -> val key = variant.makeResValueKey("string" , "app_token" ) val value = ResValue("1234" , "comment" ) variant.resValues.put(key, value) }
为APK/AAR添加额外资源文件 前面讲的大部分都是/res文件夹下的Android资源,如果我们想要添加单纯的资源比如密钥、raw、json等文件,常见的做法就是放入到assert和resource目录下。 在resouce目录下添加的文件,对于APK会把文件添加到APK压缩包的根目录下:app.apk/app-appended-file.txt,对于AAR则会加入到内部jar包目录下:lib.aar/classes.jar/lib-appended-file.txt,AAR打包进APK后,添加的文件也会在APK的根目录下。 除此之外向AAR添加文件,还可通过如下方式:
1 2 3 4 5 6 7 8 9 10 libExtension.onVariants{variant-> afterEvaluate { project.tasks.named("bundle${variant.name.capitalized()} Aar" ) .configure { val bundle=this as com.android.bundle.gradle.tasks.BundleAar bundle.from(file("lib-appended-file-arr-root.txt" )) } } }
这是因为AAR的BundlerAar 打包任务 ,该任务继承了Gradle原生的Zip任务,而其上游AbstractCopyTast提供了from接口能方便的添加家外部文件的需求,但是在AAR打包进APK的过程中,添加文件会被忽略掉,没发现有什么其它用处。 作为Android开发者,assert文件都很熟悉,它是一个重要的存放资源的文件夹。如果文件是在编译时动态生成下发。我们可以结合MultipleArtifact.ASSETS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 appExtension.onVariants { variant -> val capitalizedName = variant.name.capitalized() val genAssetTaskProvider = project.tasks.register<GenAssetTask>("genAssetTask${capitalizedName} " ) { group = "asset" additionModuleFileProp.set (project.file("app_module.json" )) } val addAssetTaskProvider = project.tasks.register<AddAssetTask>("addAssetTask${capitalizedName} " ) { group = "asset" additionModuleFileProp.set (genAssetTaskProvider.flatMap { it.additionModuleFileProp }) } variant.artifacts .use(addAssetTaskProvider) .wiredWith(AddAssetTask::outputDirectoryProp) .toAppendTo(MultipleArtifact.ASSETS) } abstract class GenAssetTask : DefaultTask () { @get: OutputFile abstract val additionModuleFileProp: RegularFileProperty @TaskAction fun getAsset () { additionModuleFileProp.get ().asFile.apply { createNewFile() writeText("{\"module\":\"app_key\"}" ) } } } abstract class AddAssetTask : DefaultTask () { @get: InputFile abstract val additionModuleFileProp: RegularFileProperty @get: OutputDirectory abstract val outputDirectoryProp: DirectoryProperty @TaskAction fun getAsset () { val moduleFile = additionModuleFileProp.get ().asFile val assetDir = outputDirectoryProp.get ().asFile assetDir.mkdirs() moduleFile.copyTo(File(assetDir, moduleFile.name), overwrite = true ) } }
为APK/AAR移除资源文件 在新版本AGP中对资源移除做了API的限制:
res/下的排出选项被取消
packageOptions.exclues被细分成resource jinLibs dex三个选项。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 android{ sourceSets { androidResources{ } } packagingOptions { dex{ } resources { excludes+="res/layout/lib_layout.xml" } jniLibs{ } } }