Lonjakan Application Not Responding (ANR) pasca-rilis adalah insiden kritis pada arsitektur React Native di Android. ANR terjadi ketika UI thread (Main Thread) terblokir lebih dari 5 detik saat menangani input pengguna, atau lebih dari 10 detik saat mengeksekusi BroadcastReceiver. Pada runtime React Native, isolasi thread antara JavaScript thread (Hermes) dan Android Main Thread sering kali menyamarkan penyebab utama freeze.

1. Observability: Native Watchdog Timer & Korelasi Stack Trace Hermes

Android crash reporter bawaan hanya menangkap native stack trace saat ANR terpicu via file /data/anr/traces.txt. Untuk mendeteksi thread freeze secara real-time sebelum OS menghentikan proses, aplikasi membutuhkan mekanisme watchdog di sisi native.

Implementasi Native Watchdog Timer

Pola paling efisien adalah mem-post task penanda (tick) ke Looper.getMainLooper() secara berkala dari dedicated background thread. Jika task tersebut tidak dieksekusi dalam batas waktu ambang (misalnya 4000 ms), Main Thread dipastikan mengalami stall.

package com.monitoring.anr

import android.os.Handler
import android.os.Looper

class ANRWatchdog(
    private val timeoutMs: Long = 4000L,
    private val onAnrDetected: (Thread, Array<StackTraceElement>) -> Unit
) : Thread("ANR-Watchdog-Worker") {

    private val mainHandler = Handler(Looper.getMainLooper())
    @Volatile private var tick = 0L
    @Volatile private var isRunning = true

    override fun run() {
        while (isRunning) {
            val startTick = tick
            mainHandler.post { tick = startTick + 1 }

            try {
                sleep(timeoutMs)
            } catch (e: InterruptedException) {
                return
            }

            // Jika tick tidak berubah, UI thread terblokir selama >= timeoutMs
            if (tick == startTick) {
                val mainThread = Looper.getMainLooper().thread
                onAnrDetected(mainThread, mainThread.stackTrace)
            }
        }
    }

    fun terminate() {
        isRunning = false
        interrupt()
    }
}

Korelasi Hermes JS Stack Trace di Sentry

Ketika Main Thread terblokir, root cause sebenarnya bisa berada di JavaScript Thread (mqt_js) yang membanjiri message queue atau memanggil method JSI/Bridge secara sinkron. Konfigurasi @sentry/react-native harus mengaktifkan tracking ANR native sekaligus Hermes sampling profiler:

import * as Sentry from "@sentry/react-native";

Sentry.init({
  dsn: "https://[email protected]/0",
  enableAppHangTracking: true, // Deteksi app hang di iOS
  enableNativeFramesTracking: true,
  // Integrasikan ANR detection native Android
  anrDetection: {
    enable: true,
    timeoutInterval: 4000,
    captureScreenshots: false,
  },
  _experiments: {
    profilesSampleRate: 0.2, // Profiler Hermes untuk melacak execution stack JS
  },
});

Saat native ANR watchdog mentransmisikan exception ke Sentry, Sentry mengkorelasikan native thread dump dengan sampling profiler Hermes terakhir. Ini memperlihatkan fungsi JavaScript mana yang berjalan tepat sebelum event freeze terjadi.

2. Postmortem: Root Cause Tipikal ANR di React Native

Berdasarkan data runtime, ANR pada React Native umumnya terbagi menjadi dua skenario:

A. Sinkronisasi Data Berat pada Render Phase

Komponen React Native yang melakukan parsing payload JSON besar (misalnya cache lokal SQLite/MMKV) langsung di dalam siklus render() atau synchronous layout evaluation. Ketika ratusan node komponen ter-mount bersamaan, Hermes runtime membebani bridge untuk pengiriman instruksi layout ke Yoga engine di native thread, menghabiskan alokasi waktu Choreographer.

B. Blocking Bridge / TurboModule Sinkron

Penggunaan metode native module beranotasi @ReactMethod(isBlockingSynchronousMethod = true) atau fungsi C++ TurboModule via JSI yang memanggil I/O (disk/network) langsung di thread pemanggil tanpa memindahkannya ke thread pool terpisah (seperti Dispatchers.IO).

3. Prosedur Mitigasi Cepat dan Rollback

Saat terjadi lonjakan ANR mendadak pasca-rilis, investigasi kode membutuhkan waktu. Lakukan containment segera menggunakan dua jalur paralel:

Halt Staged Rollout via Google Play Developer API

Jika rilis dilakukan via staged rollout (misal 10%), hentikan rollout melalui API untuk mencegah paparan ke lebih banyak user tanpa menunggu pembukaan UI browser console:

PUT https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/edits/{editId}/tracks/production
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "releases": [
    {
      "name": "Release-2.1.0",
      "versionCodes": ["20100"],
      "status": "HALTED"
    }
  ]
}

Eksekusi Remote Kill-Switch

Jika fitur penyebab ANR terisolasi di JavaScript layer (misalnya flow checkout baru), jangan tunggu proses review binary Google Play yang membutuhkan waktu 12–48 jam. Nonaktifkan logic menggunakan Remote Feature Flag / Config service:

function ProductCheckoutFlow(props) {
  const isNewEngineEnabled = useFeatureFlag("kill_switch_checkout_engine_v2");

  if (!isNewEngineEnabled) {
    return <LegacyCheckoutFallback {...props} />;
  }

  return <OptimizedCheckoutV2 {...props} />;
}

4. Tindakan Pencegahan & Rekayasa Reliabilitas

Terapkan guardrail struktural agar regresi ANR terdeteksi sebelum aplikasi dipromosikan ke tahap rilis publik:

  • Offload Komputasi ke Background Worklet: Operasi manipulasi array besar atau parsing data intensif harus dialihkan keluar dari JS event loop utama menggunakan pustaka seperti react-native-worklets-core atau offloading ke Native Worker threads.
  • Guardrail Android Vitals di CI/CD: Google Play Console menetapkan bad behavior threshold untuk ANR pada angka 0.47% (per-user basis). Integrasikan Play Developer Reporting API ke pipeline alert tim untuk mentrigger rollback otomatis bila metric melewati ambang 0.35%.
  • Profiling Frame Drop di QA Stage: Validasi user journey kritis menggunakan Flashlight atau Android dumpsys gfxinfo dalam script integration test (e.g., Maestro) untuk mendeteksi jank dan Looper stalls sebelum masuk ke tahap deployment:
# Eksekusi profiling jank frame via ADB
adb shell dumpsys gfxinfo com.example.app reset
# Jalankan interaksi automated test...
adb shell dumpsys gfxinfo com.example.app
Evaluasi metriks Janky Frames dan Number of Slow UI Thread frames pada output reporting. Frame execution time di atas 700ms pada Main Thread adalah indikator awal potensi ANR di perangkat low-end.