📋 Daftar Isi
1. Mengapa Performance Penting?
Performa aplikasi mobile secara langsung memengaruhi user retention, rating di app store, dan konversi bisnis. Studi menunjukkan bahwa penundaan 1 detik dalam loading time dapat menurunkan konversi hingga 7%.
| Metrik | Target | Dampak |
|---|---|---|
| Cold Start | < 2 detik | 53% user abandon jika > 3 detik |
| Frame Rate | 60 fps (16.6ms/frame) | Jank terlihat di bawah 50 fps |
| Memory | < 200 MB (target) | OOM crash di device low-end |
| Battery drain | < 5% per jam aktif | User uninstall app boros baterai |
| Network | < 500ms API response | User percepat app lambat |
2. Memory Profiling
Memory leak adalah penyebab utama crash di mobile. Profiling tools membantu mendeteksi alokasi berlebihan dan leak.
Bash — Android Memory Profiling
# Menggunakan Android Studio Profiler # 1. Buka View > Tool Windows > Profiler # 2. Pilih device & process # 3. Click "Memory" row # Menggunakan adb untuk quick check adb shell dumpsys meminfo com.myapp # Output contoh: # App Summary: # Java Heap: 15,234 KB # Native Heap: 8,456 KB # Code: 12,789 KB # Stack: 1,234 KB # Graphics: 25,678 KB # Total PSS: 63,391 KB # Memory heap dump adb shell am dumpheap com.myapp /data/local/tmp/heap.hprof adb pull /data/local/tmp/heap.hprof # Analyze dengan Android Studio atau MAT (Memory Analyzer Tool)
Swift — iOS Memory Debugging
// Instruments > Leaks
// Xcode > Debug Memory Graph
// Detect retain cycles
class ViewController: UIViewController {
var onComplete: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
// ❌ Retain cycle: self -> onComplete -> self
onComplete = {
self.view.backgroundColor = .red
}
// ✅ Fix: gunakan weak self
onComplete = { [weak self] in
self?.view.backgroundColor = .red
}
}
}
// Memory warning handler
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Clear caches
ImageCache.shared.removeAll()
URLCache.shared.removeAllCachedResponses()
}
2.1 Common Memory Issues
| Issue | Penyebab | Solusi |
|---|---|---|
| Retain Cycle | Circular reference | weak/unowned reference |
| Large Bitmap | Load gambar tanpa resize | Downsample & cache |
| Leaked Activity/Fragment | Callback ke destroyed view | Lifecycle-aware component |
| Cache overflow | Tanpa eviction policy | LRU cache dengan max size |
3. Rendering Performance
Kotlin — RecyclerView Optimization
// ❌ Buruk: inflate baru setiap kali
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_complex, parent, false)
return ViewHolder(view)
}
// ✅ Baik: gunakan ListAdapter + DiffUtil
class ItemAdapter : ListAdapter<Item, ItemAdapter.ViewHolder>(DiffCallback()) {
class DiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(old: Item, new: Item) = old.id == new.id
override fun areContentsTheSame(old: Item, new: Item) = old == new
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class ViewHolder(private val binding: ItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.title.text = item.title
// ✅ Gunakan placeholder & cache untuk gambar
Glide.with(binding.root)
.load(item.imageUrl)
.placeholder(R.drawable.placeholder)
.override(200, 200) // Resize!
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(binding.image)
}
}
}
Dart — Flutter Rendering Tips
// ❌ Buruk: rebuild semua widget
State build(BuildContext context) {
return Column(
children: items.map((item) => ItemWidget(item: item)).toList(),
);
}
// ✅ Baik: gunakan ListView.builder (lazy build)
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ItemWidget(item: items[index]);
},
)
// ✅ Cache expensive computations
Widget build(BuildContext context) {
return RepaintBoundary(
child: CustomPaint(
painter: ChartPainter(data: data), // Hanya repaint jika data berubah
),
);
}
// ✅ Gunakan const widgets
const Text('Hello') // Tidak rebuild
Text('Hello') // Bisa rebuild
4. Network Optimization
Kotlin — OkHttp Caching
val client = OkHttpClient.Builder()
.cache(Cache(
directory = File(context.cacheDir, "http_cache"),
maxSize = 50L * 1024 * 1024 // 50 MB
))
.addInterceptor { chain ->
val request = if (isNetworkAvailable()) {
chain.request().newBuilder()
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
} else {
chain.request().newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
}
chain.proceed(request)
}
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control", "public, max-age=300")
.build()
}
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
4.1 Network Best Practices
| Praktik | Manfaat |
|---|---|
| HTTP caching (Cache-Control) | Kurangi request ke server |
| Request batching | Satu request untuk banyak data |
| Pagination / infinite scroll | Tidak load semua data sekaligus |
| Compress response (gzip) | Kurangi payload size |
| Retry with exponential backoff | Handle transient failures |
| Prefetch data | Load sebelum user butuh |
5. Battery Optimization
Kotlin — Battery-Efficient Background Work
// Gunakan WorkManager untuk background task
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// Sync data
val data = api.fetchUpdates()
database.saveAll(data)
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
// Schedule dengan constraints
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only
.setRequiresBatteryNotLow(true)
.build()
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
repeatInterval = 6, repeatIntervalTimeUnit = TimeUnit.HOURS
)
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"data_sync", ExistingPeriodicWorkPolicy.KEEP, syncRequest
)
⚠️ Hindari WakeLock
Jangan gunakan WakeLock langsung. Gunakan WorkManager atau AlarmManager untuk scheduled tasks. Android akan menghentikan app yang menyalahgunakan WakeLock.
6. Cold Start Optimization
XML — Android Splash Screen
Kotlin — Lazy Initialization
class MyApplication : Application() {
// ❌ Eager init — lambat di startup
val heavyService = HeavyService()
// ✅ Lazy init — init saat pertama kali diakses
val analytics by lazy { AnalyticsService(this) }
val database by lazy { Room.databaseBuilder(this, AppDb::class.java, "db").build() }
override fun onCreate() {
super.onCreate()
// ✅ Hanya init yang benar-benar diperlukan
initCrashReporting() // Penting: harus duluan
// ❌ Jangan init semua di sini
// initAnalytics()
// initImageLoader()
// initPushNotification()
}
}
6.1 Cold Start Timeline
| Fase | Yang Terjadi | Optimasi |
|---|---|---|
| Process creation | OS fork process | Tidak bisa dioptimasi |
| Application.onCreate | Inisialisasi library | Lazy init, defer non-essential |
| Activity creation | Layout inflate | ViewStub, reduce hierarchy |
| First frame | Render pertama | Shimmer/placeholder |
| Data loaded | API response | Cache, prefetch |