Mobile Development 18 min read

Practical Lessons from Upgrading a Large Android Codebase to AGP 8.2.2

This article details a three‑month, multi‑step migration of a large Android monorepo from AGP 7.2.2 to 8.2.2, covering Transform API deprecation, namespace handling, Gradle and JDK upgrades, build‑feature toggles, third‑party library adaptations, performance optimizations, and the troubleshooting of numerous compilation pitfalls.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Practical Lessons from Upgrading a Large Android Codebase to AGP 8.2.2

Introduction

In 2024 I am still doing native Android development without KMP or any "leading‑edge" tools. The current upgrade of the Android Gradle Plugin (AGP) from 7.2.2 to 8.2.2 spans multiple apps and business modules, lasting three months and proceeding in three major phases with many smaller steps.

Upgrade Plan and Pitfalls

The migration moves from 7.2.2 to 8.2.2 . The biggest change in AGP is the deprecation of the Transform Api and the activation of default compilation features. Gradle version and DSL also change significantly, so the upgrade is split into three incremental steps:

Step 1: Upgrade to 7.4.2 to ensure basic compilation works and migrate all Transform interfaces.

Step 2: Upgrade to 8.2.2 , resolve compatibility issues, and collect data on APK size, build time, and R8 compatibility.

Step 3: Fine‑tune features and address any regression in build metrics.

Transform Api

We first identified plugins that still use the Transform Api (four in total, three internal and one Huawei push) and scheduled their migration.

afterEvaluate {
    if (project.plugins.hasPlugin('com.android.application')) {
        project.gradle.buildFinished {
            def app = project.extensions.getByType(com.android.build.gradle.AppExtension.class)
            logger.quiet("buildFinished, project(${project.name}) has ${app.transforms.size()} transforms")
            app.transforms.forEach {
                logger.quiet("transforms: ${it.name} -> ${it.getClass().name}")
            }
        }
    }
}

Namespace

AGP 8 requires enabling namespace . A script can strip the old package from XML and add the new namespace declaration in build.gradle . This step can be released independently and is a prerequisite for enabling nonTransitiveRClass .

BuildConfig

By default AGP 8 does not generate BuildConfig . The feature is globally disabled and can be turned on per‑module when needed.

android.defaults.buildfeatures.buildconfig=true

buildFeatures { buildConfig = true }

nonFinalResIds

AGP 8 enables nonFinalResIds globally; it cannot be enabled per‑module. In our monorepo we had to disable it for a module that still used butterknife and later re‑enable it after migrating to databinding :

./gradlew app:assembleRelease -Pandroid.nonFinalResIds=false

JVM‑related Changes

AGP 8 requires JDK 17 and updates the default Gradle target compatibility. The following snippet sets Java 1.8 compatibility for existing code while configuring Kotlin to target JVM 1.8:

allprojects {
    if (project.plugins.hasPlugin('com.android.application') || project.plugins.hasPlugin('com.android.library')) {
        android.compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    if (project.plugins.hasPlugin("kotlin-android")) {
        android.kotlinOptions {
            jvmTarget = "1.8"
            freeCompilerArgs += ['-Xjvm-default=all']
        }
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
        kotlinOptions {
            jvmTarget = "1.8"
            freeCompilerArgs += ['-Xjvm-default=all']
        }
    }
}

Gradle Upgrade

Gradle was upgraded from 7 to 8, requiring updates to plugins such as unit‑test, lint, and jcocoa. The official upgrade guide (https://docs.gradle.org/current/userguide/upgrading_version_7.html) was consulted. A Mac‑specific tool‑chain issue was also discovered.

// settings.gradle

buildscript {
    dependencies {
        classpath "org.gradle.toolchains:foojay-resolver:${foojay_version}"
    }
}

apply plugin: "org.gradle.toolchains.foojay-resolver-convention"

Third‑Party Libraries

Most third‑party libraries already support AGP 8, but a few (e.g., greendao ) required special adaptation:

// https://github.com/greenrobot/greenDAO/issues/1110#issuecomment-1734949669
tasks.configureEach { task ->
    if (task.name.matches("\\w*compile\\w*Kotlin")) {
        task.dependsOn('greendao')
    }
    if (task.name.matches("\\w*kaptGenerateStubs\\w*Kotlin")) {
        task.dependsOn('greendao')
    }
    if (task.name.matches("\\w*kapt\\w*Kotlin")) {
        task.dependsOn('greendao')
    }
}

Okio 1.x was upgraded to 3.x, causing deprecation errors that were suppressed with @Suppress("DEPRECATION_ERROR") and custom lint checks.

@Deprecated(
    message = "moved to extension function",
    replaceWith = ReplaceWith(
      expression = "file.sink()",
      imports = ["okio.sink"]
    ),
    level = DeprecationLevel.ERROR,
  )
fun sink(file: File) = file.sink()
// Temporary suppression of okio compilation error
@Suppress("DEPRECATION_ERROR")

Further Transform Details

A simple Transform example registers a task bound to the Variant lifecycle:

val ac = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
ac.onVariants { variant ->
    if (extension.enableTfTask()) {
        val taskProvider = project.tasks.register
("${variant.name}TfNothing")
        taskProvider.configure { it.extension = extension }
        variant.artifacts.forScope(ScopedArtifacts.Scope.ALL)
            .use(taskProvider)
            .toTransform(
                ScopedArtifact.CLASSES,
                TfNothingTask::allJars,
                TfNothingTask::allDirectories,
                TfNothingTask::output
            )
    }
}

abstract class TfNothingTask : DefaultTask() {
    @get:InputFiles abstract val allJars: ListProperty
@get:InputFiles abstract val allDirectories: ListProperty
@get:OutputFile abstract val output: RegularFileProperty
    @Internal lateinit var extension: TfNothingExtension
    @TaskAction fun taskAction() { }
}

When multiple Transforms are defined, only the last output jar is populated, which can cause full task re‑execution and loss of incremental builds. To mitigate this, we split the large jar into smaller chunks, apply DexBuilder on each chunk in parallel, and merge the results in groups, drastically reducing build time.

val jos = JarOutputStream(FileOutputStream(tmpOutput).buffered())
jos.setLevel(Deflater.NO_COMPRESSION)
fun addEntry(jos: JarOutputStream, name: String, data: ByteArray) {
    val entry = JarEntry(name)
    entry.time = 0
    jos.putNextEntry(entry)
    jos.write(data)
    jos.closeEntry()
}

Enabling Partial Compilation Features

nonTransitiveRClass

Because the project is huge, the official migration assistant cannot be used. We wrote a custom plugin that collects each module's packageName or namespace from generated R‑def.txt / R.jar files, aggregates them into a JSON, and then replaces R imports via PSI.

// color.json
{
  "auto_night_shade": ["com.xxx.lib.widget"],
  "avatar_color_transparent": ["com.xxx.lib.widget", "com.xxx.lib.accountsui"]
}

// id.json
{
  "abTestResultTv": ["com.xxx.gripper.app"],
  "anr_btn": ["com.xx.gripper.app"]
}
// Replace R in Java
private fun replaceExpression(expression: PsiReferenceExpression, s: String) {
    val e2 = psiFactory.createReferenceFromText(s, null)
    expression.replace(e2)
    logger.println("replaceExpression = $expression, newExpression = $s")
}

// Replace R in Kotlin
private fun replaceExpression(expression: KtDotQualifiedExpression, s: String) {
    val e2 = psiFactory.createExpression(s)
    expression.replace(e2)
    logger.println("replaceExpression = $expression, newExpression = $s")
}

Data‑binding modules require special handling because the R value is an ID, not a raw color.

# before
android:textColor="@{vm.success? @color/color1 : @color/color2}"
# fix 1
android:textColor="@{vm.success? com.xxx.R.color.color1 : com.yyy.R.color.color2}"
# fix 2
android:textColor="@{util.getResColor(vm.success? R.color.color1 : R.color.color2)}"

R8 Issues

Full‑mode R8 is currently disabled; we plan to enable it after further testing. Some C++ code calling Java suffers from missing constructors after R8 optimization, which can be avoided by adding the flag -Dcom.android.tools.r8.disableApiModeling=1 :

./gradlew :app:assembleRelease -Dcom.android.tools.r8.disableApiModeling=1

Mapping file format changes and the removal of META-INF/**_release.kotlin_module entries required custom packaging rules and a hook on the mergeReleaseJavaResource task.

packaging {
    exclude 'META-INF/**_release.kotlin_module'
}
// Remove or modify whenTaskAdded logic to avoid missing base.jar
tasks.whenTaskAdded { task ->
    // custom handling
}

Data Degradation and Mitigation

The three‑month upgrade caused noticeable degradation in compile time and artifact size. Reducing jar compression, using buffered I/O, multithreaded processing, and grouping Dex merges helped flatten the regression. Enabling nonTransitiveRClass also reduced debug APK size from ~250 MB to ~200 MB.

Conclusion

Despite many twists and challenges, the migration to AGP 8.2.2 was completed successfully, demonstrating that a systematic, incremental approach combined with targeted tooling can overcome the complexities of large‑scale Android build upgrades.

Appendix

Plugin updates: https://developer.android.com/build/releases/gradle-plugin-api-updates?hl=zh-cn

AGP 8.2.0 release notes: https://developer.android.com/build/releases/gradle-plugin?hl=zh-cn

AGP 8.1.0 release notes: https://developer.android.com/build/releases/past-releases/agp-8-1-0-release-notes?hl=zh-cn

AGP 8.0.0 release notes: https://developer.android.com/build/releases/past-releases/agp-8-0-0-release-notes?hl=zh-cn

AGP 7.4.0 release notes: https://developer.android.com/build/releases/past-releases/agp-7-4-0-release-notes?hl=zh-cn

AGP 7.3.0 release notes: https://developer.android.com/build/releases/past-releases/agp-7-3-0-release-notes?hl=zh-cn

D8 and Kotlin support: https://developer.android.com/build/kotlin-support?hl=zh-cn

Gradle 7 & 8 upgrade guide: https://docs.gradle.org/current/userguide/upgrading_version_7.html

Androidbuild optimizationAGPGradleKotlinjdk17Transform API
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.