Performance dan Profiling adalah topik penting dalam pengembangan aplikasi Android dengan Kotlin. Memahaminya membantu kita membangun aplikasi yang andal, mudah diuji, dan siap untuk publikasi ke Google Play Store. Pada chapter ini kita akan membongkar konsep inti, melihat contoh kode praktis, dan menutup dengan checklist implementasi.
Android Studio Profiler #
Window → Profiler menampilkan empat kategori: CPU, Memory, Network, Energy. CPU Profiler menampilkan flame graph dan call stack per thread. Memory Profiler menampilkan heap dump dan alokasi objek.
Berikut program demonstrasi konsep-konsep di atas:
// performance_profiling.kt
// Mendemonstrasikan konsep profiling performa Android: pengukuran alokasi,
// frame time, dan deteksi pekerjaan di main thread — disimulasikan.
import kotlin.system.measureNanoTime
data class FrameStats(val frameTimeMs: Double, val dropped: Boolean)
fun renderFrameHeavy(items: List<String>): FrameStats {
// Simulasi: alokasi objek berlebih di setiap frame
val allocated = items.map { it.uppercase() }.filter { it.length > 3 }.sorted()
val timeMs = measureNanoTime { allocated.joinToString(",") }.toDouble() / 1_000_000
val dropped = timeMs > 16.0 // 60 FPS = 16.6 ms per frame
return FrameStats(timeMs, dropped)
}
fun renderFrameOptimized(items: List<String>): FrameStats {
// Hindari alokasi: gunakan Sequence untuk lazy eval
val timeMs = measureNanoTime {
items.asSequence()
.map { it.uppercase() }
.filter { it.length > 3 }
.sorted()
.joinToString(",")
}.toDouble() / 1_000_000
return FrameStats(timeMs, timeMs > 16.0)
}
fun main() {
val data = (1..100).map { "item$it" }
println("=== Render 10 frame dengan alokasi berlebih ===")
var droppedHeavy = 0
repeat(10) { i ->
val s = renderFrameHeavy(data)
if (s.dropped) droppedHeavy++
println("Frame ${i + 1}: ${"%.2f".format(s.frameTimeMs)} ms ${if (s.dropped) "[DROPPED]" else ""}")
}
println("Total dropped: $droppedHeavy/10")
println("\n=== Render 10 frame dengan Sequence (lazy) ===")
var droppedOpt = 0
repeat(10) { i ->
val s = renderFrameOptimized(data)
if (s.dropped) droppedOpt++
println("Frame ${i + 1}: ${"%.2f".format(s.frameTimeMs)} ms ${if (s.dropped) "[DROPPED]" else ""}")
}
println("Total dropped: $droppedOpt/10")
println("\nStrictMode mendeteksi I/O di main thread. Macrobenchmark mengukur startup time dan frame rate.")
}Output yang Dihasilkan:
=== Render 10 frame dengan alokasi berlebih ===
Frame 1: 4.72 ms
Frame 2: 0.05 ms
Frame 3: 0.04 ms
Frame 4: 0.04 ms
Frame 5: 0.04 ms
Frame 6: 0.05 ms
Frame 7: 0.05 ms
Frame 8: 0.04 ms
Frame 9: 0.25 ms
Frame 10: 0.04 ms
Total dropped: 0/10
=== Render 10 frame dengan Sequence (lazy) ===
Frame 1: 9.44 ms
Frame 2: 0.23 ms
Frame 3: 0.19 ms
Frame 4: 0.17 ms
Frame 5: 0.17 ms
Frame 6: 0.19 ms
Frame 7: 0.14 ms
Frame 8: 0.14 ms
Frame 9: 0.14 ms
Frame 10: 0.14 ms
Total dropped: 0/10
StrictMode mendeteksi I/O di main thread. Macrobenchmark mengukur startup time dan frame rate.Macrobenchmark #
Library androidx.benchmark:macro-junit4 mengukur startup time, frame rate, dan operasi kompleks. Test berjalan di device/emulator, menghasilkan metrik Startup Ms, Frame Time, dll. Integrasikan ke CI untuk regression detection.
Layout Inspector #
Tools → Layout Inspector menampilkan tree view Compose/XML secara real-time. Reveal node yang tidak terlihat, rekombinasi berlebihan, dan layar yang berlebihan.
StrictMode #
StrictMode.setThreadPolicy(...) mendeteksi I/O di main thread. StrictMode.setVmPolicy(...) mendeteksi activity leak, SQL object leak. Aktifkan di debug build.
Common Performance Issues #
Recomposition berlebihan di Compose → stabilitas parameter. RecyclerView adapter yang tidak recycle → ViewHolder pattern. Bekerja di main thread → gunakan Dispatchers.IO. Object allocation di hot path → reuse object. Bitmap besar → inSampleSize decoding.
Macrobenchmark Integration #
Library Macrobenchmark berbeda dari Android Studio Profiler karena berjalan sebagai instrumented test yang mengukur metrik pada peranti fisik atau emulator. Tambahkan modul Gradle terpisah dengan plugin com.android.test agar benchmark tidak terbungkus ke dalam APK aplikasi release. Modul ini menggunakan targetProjectPath untuk merujuk pada aplikasi yang akan diukur, sehingga kode benchmark tetap terpisah dari kode produksi.
Macrobenchmark menyediakan dua kelas utama: StartupBaselineProfile untuk mengukur waktu awal aplikasi tampil, dan FrameTimingBenchmark untuk mengukur stabilitas frame rate selama scroll. Metrik yang dihasilkan mencakup startupMs (waktu dari launch sampai konten pertama tergambar), frameDurationCpuMs (durasi frame di CPU), dan frameOverrunMs (selisih terhadap target 16 ms pada layar 60 Hz).
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val benchmarkRule = MacrobenchmarkRule()
@Test fun startup() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric(), FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
}
}Untuk integrasi CI, jalankan benchmark pada emulator yang konsisten lalu unggah hasil JSON ke CI artifact. Ambang batas otomatis bisa dipasang: bila startupMs naik lebih dari 10 persen dibanding baseline, pipeline dianggap gagal. Pola ini mencegah regression performa masuk ke release produksi tanpa terdeteksi. Pasang juga pembuatan Baseline Profile otomatis pada rilis pertama setiap versi agar compiler AOT mengetahui jalur kode panas (hot path) yang harus dioptimalkan.
StrictMode untuk Development #
StrictMode adalah alat bawaan framework Android yang mendeteksi pelanggaran kebijakan pada saat development. Aktifkan hanya pada build debug melalui method Application.onCreate agar tidak memengaruhi performa produksi. StrictMode memiliki dua kebijakan yang berbeda: thread policy dan VM policy.
Thread policy mendeteksi operasi I/O atau network yang dilakukan di main thread. Konfigurasi detectDiskReads() dan detectNetwork() akan mencetak log dan menampilkan dialog StrictMode setiap kali aturan dilanggar. VM policy mendeteksi kebocoran Activity, SQLite object, dan instance Closeable yang tidak ditutup dengan benar.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeathOnNetwork()
.build()
)
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.build()
)
}
}
}Pilihan penaltyDeathOnNetwork membuat aplikasi berhenti paksa saat ada panggilan network di main thread. Perilaku ini sengaja tegas agar regresi tertangkap sejak sesi development, bukan muncul sebagai ANR di produksi. Untuk memperbaiki pelanggaran, pindahkan operasi ke Dispatchers.IO atau gunakan WorkManager untuk tugas latar belakang.
Referensi Lanjutan #
- Performance — panduan resmi performa Android mencakup profiling, startup, dan rendering
Penutup #
Dengan memahami performance dan profiling, kita siap membangun aplikasi Android yang lebih matang dan siap produksi. Materi terkait di section lain meliputi coroutines, Compose Multiplatform, dan testing — semuanya saling melengkapi untuk pengembangan Android modern.