android/gradle-recipes全部読む #Android - Qiita

  • GoogleのAndroidチームにはGradle職人がいる(はず)
  • Android Gradle Pluginを身体に馴染ませるために職人が用意してくれたサンプルを全部読む
  • 各READMEに全部書いていると言えば、それはそう
  • 基本は抑えている前提でこんな書き方もありなのか、という観点で読む

agp-8.9

https://github.com/android/gradle-recipes/tree/dd275c878633c4ad272883d4bc70f1ab3c8ed0fa

addBuildTypeUsingDslFinalize

  • AndroidComponentsExtensionを使って、buildTypesを追加するサンプル

    • AndroidComponentsExtensionは実行順序を持っているので、それに合わせて実装してねというもの
    • 実行順序については公式のAndroid build flow and extension pointsに書いてある
  • build-loginでAGP全部(com.android.tools.build:gradle)をimplementationせずにcom.android.tools.build:gradle-apiのみをimplementationしている

  • 依存解決の量が少ないので初期化が若干早いかも(最終的にビルドするときには本体自体も必要になるので、最終的には同じ)


com.android.tools.build:gradle-api
    └── com.android.tools.build:builder-test-api
        ├── com.android.tools:annotations
        ├── com.android.tools:common
        └── com.android.tools.ddms:ddmlib

addCustomBuildConfigFields

  • AndroidComponentsExtensionを使ってBuildConfigFieldを追加するサンプル
  • buildTypesproductFlavorsで追加するコードのほうが良く見かけるがAndroidComponentsExtension登場移行のAGP7系以降のbuild type, product flavor依存のものはAndroidComponentsExtensionに寄せるほうが思想とマッチしているかもしれない
  • AndroidComponentsExtensionを使うと生成コードにコメントを追加できる
  • GradleのProvider APIを使って、buildConfigFieldの値を動的に変更することができる

build.gradle

import com.android.build.api.variant.BuildConfigField

// よくみるやつ:コメント追加不可
android {
    buildTypes {
        release {
            buildConfigField("String", "BUILD_TIME", ""${minutesSinceEpoch}"")
        }
        debug {
            buildConfigField("String", "BUILD_TIME", ""0"")
        }
    }
}

// AndroidComponentsExtension:コメント追加可
androidComponents.onVariants { variant ->
    variant.buildConfigFields.put(
        "FloatValue",
        new BuildConfigField(
            "Float",
            "12.0f",
            "Float Value Comment"
        )
    )
}

BuildConfig.java

public final class BuildConfig {
  // Float Value Comment
  public static final Float FloatValue = 12.0f;
}

addCustomSourceType

  • AndroidComponentsExtensionでregisterSourceTypeを使って追加するsource typeを宣言する
  • AGPでは認識されないがAndroidComponentsExtensionで認識されるsource typeを追加することができる
  • addStaticSourceDirectory, addGeneratedSourceDirectoryを使って、source typeに追加するsource folderを追加する
  • アプリに梱包しないファイル類を管理する際に便利(だが標準のGradle APIを使ってもそんな変わらない気がする)

build.gradle.kts

androidComponents {
    registerSourceType("toml")
    onVariants { variant ->
        variant.sources.getByName("toml").also {
            // adding custom folders (static and generated) to `toml` source type
            it.addStaticSourceDirectory("third_party/${variant.name}/toml")
            it.addGeneratedSourceDirectory(addSourceTaskProvider, AddCustomSources::outputFolder)
        }
    }
}
  • Google Play Services Pluginでも使われている

addGeneratedSourceFolder

  • AndroidComponentsExtensionで自動生成ファイルをassetsに追加するサンプル
  • addGeneratedSourceDirectoryを使って、source folderを追加する
  • サンプルではassetsに追加しているが、javakotlinのsource folderにも追加できる

addMultipleArtifact

  • com.android.build.api.artifact.MultipleArtifactのサンプル(≒AABにファイルを追加する)

    • MultipleArtifact.MULTIDEX_KEEP_PROGUARD: proguardルールを追加する
    • MultipleArtifact.NATIVE_DEBUG_METADATA: debug metadata(.dbg)を追加する
    • MultipleArtifact.NATIVE_SYMBOL_TABLES: debug symbol tables(.sym)を追加する
  • CheckBundleTask, outputを設定しないとup-to-dateにならないので使わなくとも宣言している

  • variant.artifacts.**

    • variant.artifacts.get: SingleArtifact(apk, aab…etc)を取得する
    • variant.artifacts.getAll, variant.artifacts.addStaticDirectory: MultipleArtifact(debug metadata, symbol tables…etc)を取得、追加する MultipleArtifactはReplaceable, Transformableなartifact
    • variant.artifacts.forScope: Gradle Taskを使って汎用的にScopedArtifactをartifactを操作する
    • variant.artifacts.use: Gradle Taskを使って汎用的にartifactを操作する

allProjectsApkAction

  • apkファイルを収集するtop levelのタスクを作るサンプル
  • Variant.artifactsをタスクのinputに設定しているので暗黙的にタスクの依存関係が構築される(≒トップレベルのタスクを実行するとapkを生成するタスクが実行される)
var taskProvider: TaskProviderAllProjectsApkTask>? = null
taskProvider = project.tasks.registerAllProjectsApkTask>("allProjectsAction") {
    outputFile.set(project.layout.buildDirectory.file("list_of_apks.txt"))
}

androidComponents.onVariants(selector().withBuildType("release")) { variant ->
    taskProvider?.configure { task ->
        task.inputDirectories.add(
            variant.artifacts.get(SingleArtifact.APK)
        )
    }
}

appendToMultipleArtifact

  • addMultipleArtifactと同じようなことをするがこちらは自動生成ファイルを追加する
  • com.android.build.api.artifact.Artifacts#useを使うと設定したタスクがMultipleArtifact.NATIVE_DEBUG_METADATAを収集するタスクの依存関係に組み込まれる
variant.artifacts.use(generateNativeDebugMetadataTask)
    .wiredWith(GenerateNativeDebugMetadataTask::output)
    .toAppendTo(MultipleArtifact.NATIVE_DEBUG_METADATA)

appendToScopedArtifacts

  • ScopedArtifactsを追加する
  • ScopedArtifacts.Scope

    • ALL: インポートされたプロジェクト、外部依存込み
    • PROJECT: プロジェクトのみ
  • ScopedArtifact

    • CLASSES: classファイル、Jacocoを除くASMの結果も含む
    • JAVA_RES: 所謂普通のjavaプログラムにおけるresouceファイル e.g.)kotlin-tooling-metadata.json

applyFusedLibraryPlugin

  • 複数のartifactを単一のaarにまとめる
  • gradle.propertiesandroid.experimental.fusedLibrarySupport=trueを追加することで、AGPがFusedLibraryPluginを適用する
  • FusedLibraryPluginはAGP8.5以降、Android Developerからは消えているっぽい
  • AOSPにはまだ実装が存在しているので表にでないだけでAGP8.5以降も使用可能
  • dependencies句にincludeを追加することでaarに含める内容をコントロールするが、aar or jar以外はコンパイル済みのclassファイルが梱包される(イメージとしてはstatic library)

    • fused libraryをpublishした際のpom.xmlを見る限り内部で使用しているライブラリの依存が表示されない

asmTransformClasses

  • compile後にASMを使ってclassファイルを変換するサンプル

  • instrumentation#setAsmFramesComputationModeでASMのスタックフレームと最大スタックサイズの扱いを指定する

    • FramesComputationMode.COMPUTE_FRAMES_FOR_ALL_CLASSES: オリジナルをスキップして計算もしない
    • FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_CLASSES: オリジナルをスキップしてゼロから計算する
    • FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS: 変更もしくは追加されたメソッドを計算する
    • FramesComputationMode.COPY_FRAMES: オリジナルをそのまま使用する
  • 始めはTraceClassVisitorを使いながら実装するとよい

TraceClassVisitorのoutput

// class version 61.0 (61)
// access flags 0x31
public final class com/example/android/recipes/sample/SomeSource {

  // compiled from: SomeSource.kt

  @Lkotlin/Metadata;(mv={1, 9, 0}, k=1, xi=48, d1={"u0000u0012nu0002u0018u0002nu0002u0010u0000nu0002u0008u0002nu0002u0010u000enu0000u0018u00002u00020u0001Bu0005u00a2u0006u0002u0010u0002Ju0008u0010u0003u001au00020u0004Hu0002u00a8u0006u0005"}, d2={"Lcom/example/android/recipes/sample/SomeSource;", "", "()V", "someMethod", "", "app_debug"})

  // access flags 0x1
  public ()V
   L0
    LINENUMBER 18 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object. ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/android/recipes/sample/SomeSource; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x12
  private final someMethod()Ljava/lang/String;
   L0
    LINENUMBER 20 L0
    LDC "something"
    ARETURN
   L1
    LOCALVARIABLE this Lcom/example/android/recipes/sample/SomeSource; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}

createSingleArtifact

  • AndroidComponentsExtensionを使ってビルド時にAndroidManifest.xmlを生成するサンプル
  • サンプルでは丸っと新規でAndroidManifest.xmlを生成している(toCreate)が、単に記載をちょっと変える(toTransform)こともできる
androidComponents.onVariants { variant ->
    // create new manifest
    variant.artifacts.use(produceManifestTask)
        .wiredWith(ProduceAndroidManifestTask::outputManifest)
        .toCreate(SingleArtifact.MERGED_MANIFEST)

    // modify existing manifest
    variant.artifacts.use(produceManifestTask)
        .wiredWithFiles(
            ProduceAndroidManifestTask::mergedManifest,
            ProduceAndroidManifestTask::outputManifest
        )
        .toTransform(SingleArtifact.MERGED_MANIFEST)
}

disableTests

  • AndroidComponentsExtension#beforeVariantsを使って、testandroidTestを無効化するサンプル

  • テストコードがないモジュールなどそもそもテストする気がないvariantやmoduleに対して使うと有効

    • そもそもKotlin Compileなどが実行されなくなる
    • androidTestにコードがない場合に./gradlew connectedAndroidTestを実行するとハングしたりすることが稀にある
  • HostTestBuilder(unit test, screenshot test), HasDeviceTests(instrumented test)それぞれを柔軟に有効・無効化できる

extendingAgp

  • AndroidComponentsExtension#registerExtensionを使ってbuild type、product flavorを慮った独自Extensionを作成するサンプル
  • サンプルではVerifierTaskで独自Extensionの値を参照しているだけだが、実際には独自Extensionの値を使ってAndroidManifestやresourceをごにょったっていい

getMultipleArtifact

getScopedArtifacts

getSingleArtifact

legacyTaskBridging

  • 通常のGradle TaskをAndroidComponentsExtensionに組み込むサンプル
  • Copyタスクそのままだと依存を組み込めないのでサブクラスを作成して組み込んでいる

listenToArtifacts

  • artifactを生成するTaskを改変せずにartifactを取得するサンプル
  • toListenTo: artifactを生成するTaskをHookする
  • artifactのパスをそのままTaskに渡さず、BuiltArtifactsLoaderをTaskに渡して取得する部分は通常のGradle Taskとは違う点になる
// 👇みたいに自前でTaskのoutputに対して依存を構築しなくてよい
def assembleTask = project.tasks.named("assembleDebug")
tasks.register("printArtifact") {
    doLast {
        println assembleTask.get().outputs.files.singleFile
    }
}

listenToMultipleArtifact

onVariants

perVariantManifestPlaceholder

  • onVariantsと似たような話
  • AndroidComponentsExtension#onVariantsを使って、variantごとにmanifest placeholderを変更するサンプル
  • 詳細はmanage-manifests
  • app/build.gradleにごちゃごちゃ書くより、プラグインとして別出しした方が管理しやすくなるかもしれない

registerPreBuild

  • variant.lifecycleTasks.registerPreBuildを使うと任意のタスクをbuildの前に実行することができる
  • AGP8.10にはvariant.lifecycleTasks.registerPreInstallationが追加されている
    • 任意のタスクをアプリをインストールする前に実行することができる(たぶん)

selectVariants

  • variant filterはdeprecatedになったので、AndroidComponentsExtension#beforeVariantsを使ってねというサンプル
  • build-variants#filter-variantsにも同じ話が書かれている

testFixtures

  • testFixturesを有効化するサンプル
  • HasTestFixturesBuilderを使ってvariantごとに有効無効も切り替えられる
  • AGP自体はandroid.experimental.enableTestFixturesKotlinSupport=trueを設定することでKotlinをTestFixturesで使用することができるがKotlinとGradleの問題でGradle Hostedなテストではまだ動作しない

transformAllClasses

  • asmTransformClassesとは別アプローチでclassファイルを変換するサンプル
  • ASMを使用せずにjarファイルからclassファイルを読み取り、Javassistを使ってclassファイルを変換している

transformDirectory

  • createSingleArtifactと似たような話
  • toTransformを使って、assetの中身をビルド時に変換するサンプル

transformManifest

  • createSingleArtifactと似たような話
  • toTransformを使って、AndroidManifest.xmlをビルド時に変換するサンプル

transformMultiple

variantDependencySubstitutionTest

variantOutput

workerEnabledTransformation

  • ArtifactTransformationRequest#submitを使うとGradle Worker APIを使用できるよというサンプル
  • 通常、Gradle Workerを使う場合はinjectしたWorkerExecutorを使用してWorkQueueを取得し、Taskをsubmitするがその辺りがAGP側に委譲する必要があるので注意が必要
  • 似たような形でGradle Build Servicesを使うこともできる(はず)



フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link