Бранчинг (ветвление) — это способ управления кодом в Git, когда разработчики работают в отдельных ветках (
branches). Основные стратегии бранчинга
Git Flow
GitHub Flow
GitLab Flow
Trunk-Based Development
Основные ветки:
main (стабильная версия, релизы). develop (основная ветка разработки). Временные ветки:
feature/* (новые фичи, мерджатся в develop). release/* (готовится релиз, тестирование, фикс багов). hotfix/* (критические фиксы в main). Схема Git Flow:
main ──── hotfix ─▶️ merge ────▶️ main
│
├── develop ─▶️ release ─▶️ merge ─▶️ main
│ │
├── feature/1
├── feature/2
Только две основные ветки:
main (всегда стабильная версия). Фичи разрабатываются в
feature/* и сразу мерджатся в main. Деплой возможен сразу после мерджа в
main. Схема GitHub Flow
main ────▶️ feature/1 ─▶️ merge ─▶️ main ─▶️ deploy
└── feature/2 ─▶️ merge ─▶️ main ─▶️ deploy
main – стабильная ветка (готовая к продакшену). develop (опционально) – если нужно тестирование перед main. feature/* – для разработки новых фич. production, staging – если нужно разделение сред. hotfix/* – фиксы продакшена. main ────▶️ production
│
├── staging ───▶️ merge ─▶️ main
│
├── feature/1 ─▶️ merge ─▶️ staging
├── feature/2 ─▶️ merge ─▶️ staging
Разработчики работают прямо в
main, без feature/* веток. - Коммиты в
main маленькие и частые. - Используются Feature Flags (фичи включаются/выключаются динамически).
Схема Trunk-Based
main ────▶️ commit ─▶️ commit ─▶️ commit ─▶️ deploy
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2💊1
Основные альтернативы — Dagger (ручная конфигурация), Koin (написан на Kotlin, декларативный), Kodein (устаревающий), Service Locator. Выбор зависит от предпочтений и архитектуры проекта.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1💊1
Ключевое слово
object в Kotlin имеет несколько важных применений, и оно является одной из наиболее мощных и уникальных возможностей языка. Kotlin предоставляет встроенную поддержку для создания singleton-объектов. Это объект, который имеет единственный экземпляр в приложении.
object Database {
val name = "MainDatabase"
fun connect() {
println("Подключение к базе данных $name")
}
}
fun main() {
Database.connect() // Подключение к базе данных MainDatabase
}Ключевое слово
object может использоваться для создания анонимных объектов (объектов без имени). Это полезно, если нужно создать временный объект или реализовать интерфейс/абстрактный класс.fun main() {
val listener = object : ClickListener {
override fun onClick() {
println("Кнопка нажата")
}
}
listener.onClick()
}
interface ClickListener {
fun onClick()
}Ключевое слово
object можно использовать внутри класса для объявления компаньон-объекта. Это позволяет создавать статические методы и переменные в классе.class User(val name: String) {
companion object {
fun createGuest() = User("Guest")
}
}
fun main() {
val guest = User.createGuest()
println(guest.name) // Guest
}Ключевое слово
object может использоваться для объявления объектов в коде прямо "на месте".val myObject = object {
val x = 10
fun printX() {
println(x)
}
}
fun main() {
myObject.printX() // 10
}Вместо написания множества шаблонного кода для Singleton, анонимных объектов или статических методов, вы получаете готовую реализацию из коробки.
В случае
object, компилятор Kotlin автоматически гарантирует, что объект создаётся в потокобезопасном режиме.object можно использовать практически везде: глобально, локально, внутри классов и функций.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Гонки происходят, когда два потока одновременно обращаются к общим данным без синхронизации. Решения:
- synchronized,
- ReentrantLock,
- Atomic-типы,
- volatile — только если нужна гарантия видимости, но не атомарности.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Map – это коллекция пар ключ-значение. Каждый ключ уникален, а значения могут повторяться. В Kotlin Map не наследуется от Collection, но является частью стандартных коллекций.В Kotlin есть два основных типа
Map: Map (неизменяемая) – нельзя добавлять/удалять элементы после создания. MutableMap (изменяемая) – можно добавлять, удалять и изменять элементы. Создаётся с помощью
mapOf()val users = mapOf(1 to "Alice", 2 to "Bob", 3 to "Charlie")
println(users[1]) // Выведет: Alice
Создаётся с помощью
mutableMapOf()val users = mutableMapOf(1 to "Alice", 2 to "Bob")
users[3] = "Charlie" // Добавляем элемент
users[1] = "Alex" // Изменяем значение по ключу
users.remove(2) // Удаляем элемент
println(users) // {1=Alex, 3=Charlie}
map[key] – получить значение (или null, если ключа нет). map.getValue(key) – получить значение (или исключение, если ключа нет). map.getOrDefault(key, defaultValue) – вернуть значение или defaultValue, если ключа нет. map.getOrElse(key) { default } – если ключа нет, выполнить лямбда-выражение. val users = mapOf(1 to "Alice", 2 to "Bob")
println(users[1]) // Alice
println(users.getOrDefault(3, "Unknown")) // Unknown
map.containsKey(key) – есть ли ключ? map.containsValue(value) – есть ли значение? println(users.containsKey(2)) // true
println(users.containsValue("Charlie")) // false
Перебор элементов
for ((key, value) in users) {
println("ID: $key, Name: $value")
}Фильтрация
Mapval filtered = users.filter { (key, value) -> key % 2 == 0 }
println(filtered) // {2=Bob}mapOf() – не гарантирует порядок. linkedMapOf() – сохраняет порядок добавления. Обычный
HashMap (mutableMapOf()) – O(1) для поиска по ключу. TreeMap – O(log n), но поддерживает сортировку по ключам. val sortedUsers = sortedMapOf(3 to "Charlie", 1 to "Alice", 2 to "Bob")
println(sortedUsers) // {1=Alice, 2=Bob, 3=Charlie}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В Kotlin инициализаторы используются для выполнения кода при создании экземпляра класса.
Первичный (primary) конструктор
Вторичные (secondary) конструкторы
Инициализационный блок (
init) Это основной способ инициализации класса в Kotlin. Он объявляется в заголовке класса.
class User(val name: String, val age: Int)
Если нужно выполнить дополнительную логику во время создания объекта, используют блок
init: class User(val name: String, val age: Int) {
init {
println("Создан пользователь: $name, возраст: $age")
}
}Они объявляются с помощью
constructor и нужны, если:Нужно несколько способов создания объекта.
Надо вызвать другой конструктор (
this(...)). class User {
var name: String
var age: Int
constructor(name: String) {
this.name = name
this.age = 18 // Значение по умолчанию
}
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}Можно создать объект так
val user1 = User("Алекс") // возраст будет 18
val user2 = User("Иван", 25) Если есть первичный конструктор, вторичный должен его вызывать через
this(...)class User(val name: String, val age: Int) {
constructor(name: String) : this(name, 18)
}init выполняется всегда при вызове первичного конструктора. Вторичный конструктор создаёт альтернативный способ создания объекта.
class User(val name: String, val age: Int) {
init {
println("Создан пользователь: $name, возраст: $age")
}
constructor(name: String) : this(name, 18) {
println("Вызван вторичный конструктор")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Да, существуют специализированные реализации мэп, оптимизированные под примитивы, например:
- SparseIntArray — аналог Map<Int, Int>, но без обёртки Integer.
- SparseBooleanArray, SparseArray<T> и другие.
Они используются для экономии памяти и производительности, избегая автоупаковки (autoboxing).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
data class — класс для хранения данных с автогенерацией equals(), hashCode(), copy(). sealed class — ограниченная иерархия классов, используется для when. data class автоматически создаёт: equals() и hashCode() → сравнение объектов по значениям. copy() → удобное копирование с изменением параметров. toString() → красивый вывод. data class User(val id: Int, val name: String)
fun main() {
val user1 = User(1, "Alice")
val user2 = user1.copy(name = "Bob") // Создаём копию с новым именем
println(user1) // User(id=1, name=Alice)
println(user2) // User(id=1, name=Bob)
}
sealed class используется, когда есть фиксированное число подклассов. sealed class NetworkState {
object Loading : NetworkState()
data class Success(val data: String) : NetworkState()
data class Error(val message: String) : NetworkState()
}
fun handleState(state: NetworkState) {
when (state) {
is NetworkState.Loading -> println("Загрузка...")
is NetworkState.Success -> println("Данные: ${state.data}")
is NetworkState.Error -> println("Ошибка: ${state.message}")
}
}Да! Это лучший вариант для управления состояниями:
sealed class Result {
object Loading : Result()
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Это один из ключевых принципов объектно-ориентированного программирования (ООП). Оно позволяет одному классу (наследнику) унаследовать свойства и методы другого класса (родителя). Это мощный инструмент, но использовать его нужно с умом, чтобы избежать лишней сложности и проблем с поддержкой кода.
Если у вас есть несколько классов, которые имеют общие свойства или методы, наследование позволяет вынести эти общие элементы в родительский класс. Это помогает избежать дублирования кода.
open class Animal(val name: String) {
fun eat() {
println("$name ест.")
}
}
class Dog(name: String) : Animal(name) {
fun bark() {
println("$name лает.")
}
}
class Cat(name: String) : Animal(name) {
fun meow() {
println("$name мяукает.")
}
}
// Использование:
val dog = Dog("Бобик")
dog.eat() // "Бобик ест."
dog.bark() // "Бобик лает."
Наследование отлично подходит для задач, связанных с созданием деревьев типов или категорий. Например, у вас есть базовый класс
Shape (Форма), и от него наследуются конкретные фигуры: Circle, Rectangle, Triangle. open class Shape {
open fun draw() {
println("Рисуется форма.")
}
}
class Circle : Shape() {
override fun draw() {
println("Рисуется круг.")
}
}
class Rectangle : Shape() {
override fun draw() {
println("Рисуется прямоугольник.")
}
}
// Использование:
val shapes: List<Shape> = listOf(Circle(), Rectangle())
for (shape in shapes) {
shape.draw()
}
Полиморфизм позволяет использовать родительские классы для работы с объектами наследников. Это полезно, если у вас есть методы, которые работают с разными типами объектов, но с единым интерфейсом. Вызов методов
draw() у всех объектов, не задумываясь об их конкретном типе, благодаря полиморфизму. fun render(shape: Shape) {
shape.draw()
}
render(Circle()) // "Рисуется круг."
render(Rectangle()) // "Рисуется прямоугольник."
Если наследник действительно является вариантом (подтипом) родительского класса, наследование логично. Например: Собака является животным (
Dog is an Animal). Прямоугольник является фигурой (Rectangle is a Shape).Если объекты не имеют строгого отношения "является", наследование использовать нельзя. Например: Класс
Car (Машина) и Boat (Лодка) лучше объединить через общие свойства (интерфейсы или композицию), чем делать лодку наследником машины. open class Bird {
open fun fly() {
println("Птица летит.")
}
}
class Penguin : Bird() {
override fun fly() {
throw UnsupportedOperationException("Пингвин не умеет летать!")
}
}
Если ваш класс должен обладать несколькими несвязанными функциями, лучше использовать композицию или интерфейсы вместо наследования.
class Car : Engine, Wheels
Используйте композицию:
class Car {
private val engine = Engine()
private val wheels = Wheels()
}
Если вы создаете слишком глубокие или разветвленные иерархии, код становится сложным для понимания, тестирования и изменения. Например, изменение в базовом классе может неожиданно затронуть всех наследников.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
При создании классов по сравнению с Java произошли несколько значительных изменений и упрощений. Kotlin предлагает более лаконичный и выразительный синтаксис, что делает код более читаемым и удобным.
В Kotlin объявление классов и их конструкторов значительно упрощено.
В Java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}В Kotlin
class Person(val name: String, val age: Int)
В Java для объявления статических членов используется ключевое слово
static. В Kotlin вместо этого используются companion object.В Java
public class MyClass {
public static final String CONSTANT = "constant";
public static void staticMethod() {
// Some code
}
}В Kotlin
class MyClass {
companion object {
const val CONSTANT = "constant"
@JvmStatic
fun staticMethod() {
// Some code
}
}
}Kotlin предоставляет специальный тип классов —
data классы, которые автоматически генерируют методы equals(), hashCode(), toString(), copy(), и componentN().В Java
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
// Implementation
}
@Override
public int hashCode() {
// Implementation
}
@Override
public String toString() {
// Implementation
}
}В Kotlin
data class User(val name: String, val age: Int)
В Kotlin свойства объявляются напрямую, и методы доступа (геттеры и сеттеры) генерируются автоматически.
В Java
public class Rectangle {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}В Kotlin
class Rectangle(var width: Int, var height: Int)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
В Kotlin нет checked исключений, все исключения — unchecked. Это уменьшает кодовую нагрузку (throws, catch) и упрощает API. В Java нужно явно обрабатывать checked исключения.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В MVI (Model-View-Intent) есть проблема: события, которые не нужно хранить в
State, могут повторно отобразиться при пересоздании экрана (например, при повороте экрана или навигации назад). Показ
Toast / Snackbar Навигация (
openScreen()) Ошибки, которые нужно показать один раз
Это
LiveData, которая отправляет событие только один раз и не пересылает его новым подписчикам. class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { value ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(value)
}
}
}
override fun setValue(value: T?) {
pending.set(true)
super.setValue(value)
}
}ViewModel с
SingleLiveEvent class MyViewModel : ViewModel() {
val eventShowToast = SingleLiveEvent<String>()
fun onButtonClicked() {
eventShowToast.value = "Привет, это Toast!"
}
}Activity/Fragment подписывается
viewModel.eventShowToast.observe(viewLifecycleOwner) { message ->
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}В
StateFlow все события хранятся в State (что не подходит для одноразовых событий). Вместо этого используем
SharedFlow с replay = 0, чтобы событие не повторялось. ViewModel с
SharedFlow class MyViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun onButtonClicked() {
viewModelScope.launch {
_events.emit(UiEvent.ShowToast("Привет, это Toast!"))
}
}
}
sealed class UiEvent {
data class ShowToast(val message: String) : UiEvent()
object NavigateToNextScreen : UiEvent()
}Подписка в UI:
lifecycleScope.launchWhenStarted {
viewModel.events.collect { event ->
when (event) {
is UiEvent.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
is UiEvent.NavigateToNextScreen -> findNavController().navigate(R.id.nextFragment)
}
}
}Если в
State всё-таки нужно хранить событие, но не показывать его повторно, можно использовать EventWrapper. class EventWrapper<out T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) null else {
hasBeenHandled = true
content
}
}
}ViewModel с
StateFlow: data class UiState(val message: EventWrapper<String>? = null)
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
fun onButtonClicked() {
_state.value = UiState(message = EventWrapper("Привет, это Toast!"))
}
}
UI подписывается и проверяет
EventWrapper: viewModel.state.collect { state ->
state.message?.getContentIfNotHandled()?.let { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Да, все компоненты, которые не должны быть доступны извне, могут не указываться, особенно:
- Activity, используемая только внутри;
- BroadcastReceiver или Service, регистрируемые динамически;
- ContentProvider — если не требуется внешнего доступа.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1🤔1
Подключение
BroadcastReceiver в Android состоит из двух основных шагов: создание самого ресивера и его регистрация. Ресивер можно зарегистрировать как статически в манифесте, так и динамически в коде.Создадим простой
BroadcastReceiver, который будет реагировать на определённое событие, например, на получение SMS.import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Сообщение получено!", Toast.LENGTH_SHORT).show();
}
}
Можно зарегистрировать ресивер в файле
AndroidManifest.xml. Это удобно, когда вы хотите, чтобы ресивер всегда был активен и слушал определённые системные события, например, перезагрузку устройства или получение SMS.<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
Иногда нужно регистрировать ресивер только на время выполнения определённой активности или службы. В этом случае лучше использовать динамическую регистрацию.
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBroadcastReceiver = new MyBroadcastReceiver();
}
@Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(myBroadcastReceiver, filter);
}
@Override
protected void onStop() {
super.onStop();
unregisterReceiver(myBroadcastReceiver);
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Главный плюс MVVM — разделение обязанностей:
- UI (View) отделён от логики (ViewModel);
- бизнес-логика тестируется отдельно;
- упрощает масштабирование и поддержку;
- особенно эффективно в Android с LiveData, StateFlow, DataBinding.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1🤔1
Android-приложение состоит из четырёх основных компонентов:
Activity – UI-экран приложения.
Service – фоновая работа без UI.
BroadcastReceiver – слушает системные и пользовательские события.
ContentProvider – делится данными между приложениями.
Отображает интерфейс пользователя.
Обрабатывает взаимодействие (нажатия, свайпы, ввод текста).
Управляется системой через жизненный цикл (
Lifecycle). Пример
Activity class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // Загружаем XML-разметку
}
}Выполняет длительные фоновые задачи (музыка, загрузка файлов).
Может работать даже если приложение закрыто.
НЕ имеет UI (не рисует
View). Пример
Serviceclass MyService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Thread {
// Долгая фоновая задача
}.start()
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}Запуск сервиса
startService(Intent(this, MyService::class.java))
Остановка сервиса
stopService(Intent(this, MyService::class.java))
- Получает события системы (
BOOT_COMPLETED, BATTERY_LOW). - Слушает события других приложений.
- Может быть динамическим (через код) или статическим (
AndroidManifest.xml). Пример
BroadcastReceiver (отключение Wi-Fi) class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION) {
println("Сеть изменилась!")
}
}
}Регистрируем
BroadcastReceiver в AndroidManifest.xml <receiver android:name=".NetworkReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
Можно зарегистрировать динамически в коде
val receiver = NetworkReceiver()
registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
- Дает доступ к данным *другим приложениям.
- Позволяет безопасно работать с БД (
Room, SQLite). - Используется для
Contacts, MediaStore, Calendar. Пример
ContentProvider (чтение контактов) val cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
println("Контакт: $name")
}
}
Запрос данных через
Uri val uri = Uri.parse("content://com.example.provider/table")
contentResolver.query(uri, null, null, null, null)Пример:
Activity запускает Service через Intent startService(Intent(this, MyService::class.java))
Пример:
Service отправляет Broadcast, а Receiver его ловит sendBroadcast(Intent("com.example.CUSTOM_EVENT"))Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Поворот экрана вызывает изменение конфигурации (orientation, screen size). Android по умолчанию пересоздаёт Activity, чтобы загрузить соответствующий ресурсный файл и адаптировать интерфейс.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
Если профайлер показывает, что рендеринг какого-либо фрейма занял 120 миллисекунд, это означает, что этот фрейм выполнялся слишком долго, что приводит к фризам и лагам в пользовательском интерфейсе.
В Android интерфейс обновляется 60 раз в секунду (частота 60 FPS). Это значит, что каждый кадр (фрейм) должен рендериться не дольше 16,67 мс (1000 мс / 60 FPS).
Если рендеринг кадра занимает 120 мс, то за это время устройство должно было бы нарисовать 7 кадров (120 / 16,67 ≈ 7). Однако оно успело обработать только один, что приводит к заметному подтормаживанию.
Тяжёлые вычисления в основном потоке (UI Thread) – например, сложные математические операции, работа с JSON, парсинг файлов.
Долгие операции с рендерингом – сложные векторные изображения, перегруженные
Canvas.draw() или анимации. Синхронные вызовы I/O (чтение файлов, базы данных, сети) – если, например, в
onDraw() идёт обращение к диску или базе данных. Неоптимальный layout – глубокая иерархия
ViewGroup, частые перерасчёты макетов (measure/layout). Coroutines, Executors, WorkManagerдля поиска "узких мест".
избегать сложных
onDraw(), использовать ViewStub, RecyclerView. убрать ненужные
ViewGroup, использовать ConstraintLayout.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM