Irbisa · Шпаргалка
Производительность Android и память
Профилировщики, leak-и, ANR, baseline profiles, R8, startup, перформанс рекомпозиции
Что такое ANR, из-за чего он возникает и как его избежать?
Этот обработчик клика вызывает ANR на медленной сети, хотя
loadUser()переключается наDispatchers.IO. Почему?class ProfileActivity : ComponentActivity() { private val repo = UserRepository() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profile) findViewById<Button>(R.id.refresh).setOnClickListener { // loadUser() is a suspend fun that uses withContext(Dispatchers.IO) val user = runBlocking { repo.loadUser() } findViewById<TextView>(R.id.name).text = user.name } }}Что такое
StrictModeи как он ловит чтение с диска на главном потоке или незакрытыйCursor?class MyApp : Application() { override fun onCreate() { if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) enableStrictMode() // debug only, never in release super.onCreate() } private fun enableStrictMode() {…Какие частые причины утечек памяти на Android, и как их найти и исправить?
// wrong: ImageCache.context = this (the singleton keeps the whole Activity)ImageCache.context = applicationContext // right: long-lived objects get this // Register and unregister in mirrored callbacksclass MapActivity : ComponentActivity() { private val listener = LocationListener { location -> render(location) }…LeakCanary сообщает, что
ProfileActivityудерживается после каждого поворота экрана. Что в этом коде её держит?object SessionManager { private val listeners = mutableListOf<(Session) -> Unit>() fun addListener(listener: (Session) -> Unit) { listeners += listener } fun removeListener(listener: (Session) -> Unit) { listeners -= listener }} class ProfileActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profile) SessionManager.addListener { session -> findViewById<TextView>(R.id.name).text = session.userName } }}Как LeakCanary находит утечку под капотом?
Как работает сборка мусора на Android, и что такое strong, soft, weak и phantom ссылки?
Этот кастомный график дёргается во время анимации, LeakCanary молчит, а график памяти похож на пилу. Что не так?
class Sparkline(context: Context) : View(context) { var points: List<Float> = emptyList() override fun onDraw(canvas: Canvas) { val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.BLUE; strokeWidth = 4f; style = Paint.Style.STROKE } val path = Path() val step = width / (points.size - 1).coerceAtLeast(1).toFloat() points.forEachIndexed { i, y -> if (i == 0) path.moveTo(0f, y) else path.lineTo(i * step, y) } canvas.drawPath(path, paint) canvas.drawText("max ${points.maxOrNull()}", 8f, 24f, paint) }}Зачем в Android есть
SparseArrayиArrayMapи когда они лучшеHashMap?// Int keys: no Integer boxing, no entry node per itemval names = SparseArray<String>()names.put(42, "Ada")val name = names.get(42) // binary search over a sorted IntArray // Object keys: flat arrays instead of hash buckets…Почему большие bitmap вызывают
OutOfMemoryError, и чем OOM отличается от утечки памяти?// Decode only as many pixels as the view will showfun decodeScaled(path: String, reqWidth: Int, reqHeight: Int): Bitmap? { val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeFile(path, bounds) // reads the header, allocates no pixels var sample = 1…Что такое
onTrimMemory()иonLowMemory(), и как приложению реагировать на нехватку памяти?class App : Application() { private val imageCache: LruCache<String, Bitmap> by lazy { val am = getSystemService(ActivityManager::class.java) val maxBytes = am.memoryClass * 1024 * 1024 / 8 // 1/8 of the Java heap limit object : LruCache<String, Bitmap>(maxBytes) { override fun sizeOf(key: String, value: Bitmap) = value.allocationByteCount…Что такое jank, как измерить пропущенные кадры и что обычно их вызывает?
class FeedActivity : ComponentActivity() { private lateinit var jankStats: JankStats override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { FeedScreen() }…Что такое overdraw, как его заметить и что убрать, чтобы его уменьшить?
// wrong: window background + Surface + Box background paint the same pixelsSurface(color = MaterialTheme.colorScheme.background) { Box(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) { Card(Modifier.padding(16.dp)) { Text("Hi") } }}…Что такое холодный, тёплый и горячий старт, и как измерить и ускорить холодный старт?
// App Startup: library initializers run in one provider, in dependency orderclass AnalyticsInitializer : Initializer<Analytics> { override fun create(context: Context): Analytics = Analytics.init(context) override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()}…Что такое Baseline Profiles, как они ускоряют запуск и как доказать выигрыш?
@RunWith(AndroidJUnit4::class)class BaselineProfileGenerator { @get:Rule val rule = BaselineProfileRule() @Test fun generate() = rule.collect(…Что R8 делает с release-сборкой, что такое keep-правила и что ломается, когда они неверны?
// app/build.gradle.ktsandroid { buildTypes { release { isMinifyEnabled = true // R8: shrink + optimize + obfuscate isShrinkResources = true // requires isMinifyEnabled…Как уменьшить размер загрузки приложения и откуда обычно берётся вес?
Пользователи жалуются, что приложение сажает батарею. Как найти, что оно делает в фоне, и исправить это?
val sync = PeriodicWorkRequestBuilder<SyncWorker>(6, TimeUnit.HOURS) .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresBatteryNotLow(true) .build()…Microbenchmark, Macrobenchmark и JankStats: на какой вопрос отвечает каждый, и как добиться, чтобы цифрам можно было доверять?
@RunWith(AndroidJUnit4::class)class StartupBenchmark { @get:Rule val rule = MacrobenchmarkRule() @Test fun coldStartNoProfile() = measure(CompilationMode.None()) // worst case @Test fun coldStartBaselineProfile() = measure(CompilationMode.Partial()) // what users get…Главный поток укладывается со своей работой над кадром в 5 мс, а пользователи всё равно видят рывки на 120 Гц. Что ещё входит в бюджет кадра?
class MainActivity : ComponentActivity() { private val frameThread = HandlerThread("frames").apply { start() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { AppTheme { Home() } }…Найдите баг: список цен иногда замирает на несколько секунд и заканчивается ANR, хотя
current()всего лишь возвращает закэшированный список.class PriceRepository(private val api: PriceApi) { private val lock = Any() private var cached: List<Price> = emptyList() // Called by a periodic worker on a background thread. fun refresh() = synchronized(lock) { cached = api.fetchPrices() // 2-10 s on a slow network } // Called on the main thread when the list screen binds. fun current(): List<Price> = synchronized(lock) { cached }}Play Vitals показывает, что приложение убивают из-за памяти, но Java-куча в профайлере не превышает 40 МБ. Где остальное?
// The same summary as `adb shell dumpsys meminfo <package>`, from inside the appfun logFootprint(context: Context) { val am = context.getSystemService(ActivityManager::class.java) val info = am.getProcessMemoryInfo(intArrayOf(Process.myPid())).first() val kb = listOf("java-heap", "native-heap", "graphics", "code", "stack", "total-pss")…Тестировщики говорят, что приложение «перезапускается само», когда они в него возвращаются, а в Crashlytics нет крашей. Как выяснить, что произошло?
class MyApp : Application() { override fun onCreate() { super.onCreate() reportLastExit() }…Какие метрики производительности вы отслеживаете в проде и какие пороги требует Google Play?
Что меняет для приложения требование поддержки страниц памяти 16 КБ и как сделать нативный код совместимым?
# Are the native libraries 16 KB-aligned inside the APK? (build-tools 35+)zipalign -c -P 16 -v 4 app-release.apk # ELF alignment of one library: every LOAD segment must show align 2**14 (16 KB)unzip -o app-release.apk 'lib/arm64-v8a/*' -d outllvm-objdump -p out/lib/arm64-v8a/libfoo.so | grep LOAD…