From c2d884b7c53474e4d64641fcab3b59d0add2bb18 Mon Sep 17 00:00:00 2001 From: Ajesh R Date: Sun, 5 Jul 2026 22:55:20 +0530 Subject: [PATCH] Changes for R8 Configuration analyzer example --- app/build.gradle.kts | 10 +++++ .../apps/nowinandroid/NiaApplication.kt | 36 ++++++++++++++++-- .../samples/apps/nowinandroid/NiaBuildType.kt | 1 + .../core/network/di/NetworkModule.kt | 6 ++- .../nowinandroid/core/ui/NewsResourceCard.kt | 31 ++++++++++++++-- mylibrary/build.gradle.kts | 37 +++++++++++++++++++ mylibrary/consumer-rules.pro | 12 ++++++ mylibrary/proguard-rules.pro | 21 +++++++++++ .../mylibrary/ExampleInstrumentedTest.kt | 24 ++++++++++++ mylibrary/src/main/AndroidManifest.xml | 4 ++ .../java/com/example/mylibrary/MyLibrary.kt | 17 +++++++++ .../com/example/mylibrary/ExampleUnitTest.kt | 17 +++++++++ settings.gradle.kts | 12 ++++++ 13 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 mylibrary/build.gradle.kts create mode 100644 mylibrary/consumer-rules.pro create mode 100644 mylibrary/proguard-rules.pro create mode 100644 mylibrary/src/androidTest/java/com/example/mylibrary/ExampleInstrumentedTest.kt create mode 100644 mylibrary/src/main/AndroidManifest.xml create mode 100644 mylibrary/src/main/java/com/example/mylibrary/MyLibrary.kt create mode 100644 mylibrary/src/test/java/com/example/mylibrary/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2f0253943..d8b164ea2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -56,6 +56,15 @@ android { // Ensure Baseline Profile is fresh for release builds. baselineProfile.automaticGenerationDuringBuild = true } + + create("benchmark") { + initWith(getByName("release")) + matchingFallbacks += listOf("release") + applicationIdSuffix = NiaBuildType.BENCHMARK.applicationIdSuffix + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } } packaging { @@ -79,6 +88,7 @@ dependencies { implementation(projects.feature.search.api) implementation(projects.feature.search.impl) implementation(projects.feature.settings.impl) + implementation(projects.mylibrary) implementation(projects.core.common) implementation(projects.core.ui) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt index 4975e5d65..787e3a112 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.nowinandroid import android.app.Application +import android.content.ComponentCallbacks2 import android.content.pm.ApplicationInfo import android.os.StrictMode import android.os.StrictMode.ThreadPolicy.Builder @@ -26,12 +27,21 @@ import com.google.samples.apps.nowinandroid.sync.initializers.Sync import com.google.samples.apps.nowinandroid.util.ProfileVerifierLogger import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject +import com.example.mylibrary.StartupTask +import com.example.mylibrary.TaskRunner +import android.util.Log + +class NiaStartupTask : StartupTask { + override fun run() { + Log.d("NiaStartupTask", "Running startup task from mylibrary") + } +} /** * [Application] class for NiA */ @HiltAndroidApp -class NiaApplication : Application(), ImageLoaderFactory { +class NiaApplication : Application(), ImageLoaderFactory, ComponentCallbacks2 { @Inject lateinit var imageLoader: dagger.Lazy @@ -41,11 +51,10 @@ class NiaApplication : Application(), ImageLoaderFactory { override fun onCreate() { super.onCreate() - setStrictModePolicy() - // Initialize Sync; the system responsible for keeping data in the app up to date. Sync.initialize(context = this) profileVerifierLogger() + TaskRunner.execute(NiaStartupTask::class.java) } override fun newImageLoader(): ImageLoader = imageLoader.get() @@ -57,6 +66,27 @@ class NiaApplication : Application(), ImageLoaderFactory { return 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE } + override fun onTrimMemory(level: Int) { + super.onTrimMemory(level) + // 1. Explicitly clear or trim our ImageLoader's memory cache + // (Note: Coil automatically listens to ComponentCallbacks2, but this serves as a good + // example of how to manually trim caches based on system memory signals). + if (level == TRIM_MEMORY_UI_HIDDEN){ + // The user has navigated away from the app and the UI is no longer visible. + // This is a good place to release large UI-related resources that you + // won't need while in the background + imageLoader.get().memoryCache?.trimMemory(level) + } + + if(level == TRIM_MEMORY_BACKGROUND) { + // The app is in the background and the system is running low on memory. + // As the level increases to COMPLETE, the likelihood of the app process + // being killed increases. Aggressively clear caches here. + imageLoader.get().memoryCache?.clear() + } + + } + /** * Set a thread policy that detects all potential problems on the main thread, such as network * and disk access. diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt index f4d6f038b..89e5388e5 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt @@ -22,4 +22,5 @@ package com.google.samples.apps.nowinandroid enum class NiaBuildType(val applicationIdSuffix: String? = null) { DEBUG(".debug"), RELEASE, + BENCHMARK(".benchmark") } diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt index a97540f2b..b1e042583 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt @@ -81,7 +81,11 @@ internal object NetworkModule { ): ImageLoader = trace("NiaImageLoader") { ImageLoader.Builder(application) .callFactory { okHttpCallFactory.get() } - .components { add(SvgDecoder.Factory()) } + .memoryCache(null) // ❌ Bug 1: Memory cache disabled + .diskCache(null) + // ❌ Bug 2: Disk cache disabled + .allowHardware(false) // ❌ Bug 3: Forces bitmaps onto Java heap + .bitmapConfig(android.graphics.Bitmap.Config.ARGB_8888) // Assume most content images are versioned urls // but some problematic images are fetching each time .respectCacheHeaders(false) diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index 862aec6b2..c877af51c 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.nowinandroid.core.ui import android.content.ClipData +import android.graphics.Bitmap import android.os.Build.VERSION import android.os.Build.VERSION_CODES import android.view.View @@ -53,7 +54,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draganddrop.DragAndDropTransferData import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource @@ -64,6 +68,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter import com.google.samples.apps.nowinandroid.core.designsystem.R.drawable @@ -189,10 +194,29 @@ fun NewsResourceHeaderImage( }, ) val isLocalInspection = LocalInspectionMode.current + + val context = LocalContext.current + + val perRowBitmap = remember { + Bitmap.createBitmap( + 2000, 2000, Bitmap.Config.ARGB_8888 + ).apply { + val canvas = android.graphics.Canvas(this) + + // Now you can use 'context' here to load the drawable + val drawable = ContextCompat.getDrawable( + context, + drawable.core_designsystem_ic_placeholder_default + ) + + drawable?.setBounds(0, 0, width, height) + drawable?.draw(canvas) + }.asImageBitmap() + } Box( modifier = Modifier .fillMaxWidth() - .height(180.dp), + .height(320.dp), contentAlignment = Alignment.Center, ) { if (isLoading) { @@ -205,15 +229,16 @@ fun NewsResourceHeaderImage( ) } + Image( modifier = Modifier .fillMaxWidth() - .height(180.dp), + .height(320.dp), contentScale = ContentScale.Crop, painter = if (isError.not() && !isLocalInspection) { imageLoader } else { - painterResource(drawable.core_designsystem_ic_placeholder_default) + BitmapPainter(perRowBitmap) }, // TODO b/226661685: Investigate using alt text of image to populate content description // decorative image, diff --git a/mylibrary/build.gradle.kts b/mylibrary/build.gradle.kts new file mode 100644 index 000000000..2ff32ef71 --- /dev/null +++ b/mylibrary/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "com.example.mylibrary" + compileSdk = 36 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext) + androidTestImplementation(libs.androidx.test.espresso.core) +} \ No newline at end of file diff --git a/mylibrary/consumer-rules.pro b/mylibrary/consumer-rules.pro new file mode 100644 index 000000000..42b39ecf9 --- /dev/null +++ b/mylibrary/consumer-rules.pro @@ -0,0 +1,12 @@ + +# Declare that the default constructor is called reflectively. +-keep class * implements com.example.mylibrary.StartupTask { (); } + +-keep class * { + public *; + } + + + +-dontwarn com.example.mylibrary.StartupTask +-dontwarn com.example.mylibrary.TaskRunner \ No newline at end of file diff --git a/mylibrary/proguard-rules.pro b/mylibrary/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/mylibrary/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mylibrary/src/androidTest/java/com/example/mylibrary/ExampleInstrumentedTest.kt b/mylibrary/src/androidTest/java/com/example/mylibrary/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..9bc1b73e3 --- /dev/null +++ b/mylibrary/src/androidTest/java/com/example/mylibrary/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.mylibrary + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.mylibrary.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/mylibrary/src/main/AndroidManifest.xml b/mylibrary/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/mylibrary/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mylibrary/src/main/java/com/example/mylibrary/MyLibrary.kt b/mylibrary/src/main/java/com/example/mylibrary/MyLibrary.kt new file mode 100644 index 000000000..c47f68e36 --- /dev/null +++ b/mylibrary/src/main/java/com/example/mylibrary/MyLibrary.kt @@ -0,0 +1,17 @@ + + +package com.example.mylibrary + +interface StartupTask { + fun run() +} + +// The library object that loads and executes the task. +object TaskRunner { + fun execute(taskClassFromApp: Class) { + // R8 will remove the class specified by this string. + val taskClassInstance = + taskClassFromApp.getDeclaredConstructor().newInstance() as StartupTask + taskClassInstance.run() + } +} diff --git a/mylibrary/src/test/java/com/example/mylibrary/ExampleUnitTest.kt b/mylibrary/src/test/java/com/example/mylibrary/ExampleUnitTest.kt new file mode 100644 index 000000000..e9c2e27a3 --- /dev/null +++ b/mylibrary/src/test/java/com/example/mylibrary/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.mylibrary + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 73a1d9d6a..ed7151940 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,6 +15,17 @@ */ pluginManagement { + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:9.3.7-dev") + } + } includeBuild("build-logic") repositories { google { @@ -81,6 +92,7 @@ include(":lint") include(":sync:work") include(":sync:sync-test") include(":ui-test-hilt-manifest") +include(":mylibrary") check(JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { """