Передача больших данных (например, изображений, видео, JSON) между
Activity требует оптимального подхода, потому что: Intent.putExtra() имеет ограничение по размеру (~1MB). Передача
Bitmap в Intent может вызвать TransactionTooLargeException. Большие данные лучше передавать через
Uri, БД или FileProvider. Неправильный способ (НЕ ДЕЛАТЬ!) –
Bitmap через Intent val bitmap: Bitmap = getBitmap()
val intent = Intent(this, ImageActivity::class.java)
intent.putExtra("image", bitmap) // ❌ ОПАСНО! Может вызвать Exception
startActivity(intent)
Сохраняем изображение во
File и получаем Uri fun saveBitmapToFile(context: Context, bitmap: Bitmap): Uri {
val file = File(context.cacheDir, "image.png")
file.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
return FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
}Передаём
Uri через Intent val uri = saveBitmapToFile(this, bitmap)
val intent = Intent(this, ImageActivity::class.java).apply {
putExtra("image_uri", uri.toString())
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // Даем доступ другому Activity
}
startActivity(intent)
Получаем
Uri в ImageActivity и загружаем изображение val uriString = intent.getStringExtra("image_uri")
val uri = Uri.parse(uriString)
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
imageView.setImageBitmap(bitmap)Если изображение уже хранится в базе данных (
Room), передаём ID записи, а не сам файл. val intent = Intent(this, ImageActivity::class.java)
intent.putExtra("image_id", imageId) // Передаём только ID
startActivity(intent)
Сохраняем путь
val filePath = saveBitmapToFile(this, bitmap).toString()
getSharedPreferences("app_prefs", MODE_PRIVATE).edit()
.putString("last_image", filePath)
.apply()
Читаем путь в
Activity val filePath = getSharedPreferences("app_prefs", MODE_PRIVATE)
.getString("last_image", null)
val bitmap = BitmapFactory.decodeFile(filePath)
imageView.setImageBitmap(bitmap)Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
Использовать remember, derivedStateOf, key и мемоизацию функций. Также важно следить, чтобы State не обновлялся без необходимости, а структура UI не пересоздавалась без причины.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Если профайлер показывает, что рендеринг какого-либо фрейма занял 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
👍1
Минимальные шаги:
1. Создать Activity — она является точкой входа для UI.
2. Установить content view — это может быть XML или программно созданный View.
3. Убедиться, что в манифесте указана MainActivity как LAUNCHER.
4. При запуске устройства система вызывает onCreate(), и в этот момент UI "привязывается" к экрану.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊6🔥1
В Android для наложения (перекрытия) элементов друг на друга используется FrameLayout или Box (в Jetpack Compose).
FrameLayout — это контейнер, в котором все вложенные элементы располагаются в левом верхнем углу, но при этом могут накладываться друг на друга.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/background" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Наложенный текст"
android:textSize="24sp"
android:textColor="#FFFFFF"
android:layout_gravity="center"/>
</FrameLayout>
В Jetpack Compose аналогом
FrameLayout является Box. Он также позволяет располагать элементы друг над другом. Box(
modifier = Modifier.fillMaxSize()
) {
Image(
painter = painterResource(id = R.drawable.background),
contentDescription = "Фон",
modifier = Modifier.fillMaxSize()
)
Text(
text = "Наложенный текст",
fontSize = 24.sp,
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
– Загружать конфигурацию с сервера (JSON, XML),
– Использовать Fragment/View-фабрики,
– Генерировать UI из описания,
– Использовать Jetpack Compose или RecyclerView с различными ViewType.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Сборщик мусора (Garbage Collector, GC) в Android (и в JVM) использует анализ ссылок для определения, можно ли уничтожить объект.
GC работает по принципу "сборки мусора с поиском корней" (Tracing Garbage Collection).
GC ищет "корневые" объекты (Root Objects) – это объекты, к которым точно есть ссылка (например, статические переменные, локальные переменные текущего потока, объекты в стеке).
GC обходит все объекты, к которым есть ссылки (прямые или косвенные).
Если объект не связан с корневыми объектами, он считается "мусором" и удаляется.
fun main() {
var user: User? = User("Alice") // Создаём объект
user = null // Теперь на объект нет ссылок, GC его удалит
}Метод Mark & Sweep – основной алгоритм работы GC.
Mark (Пометка) – GC помечает все достижимые объекты (к которым есть ссылки).
Sweep (Очистка) – GC удаляет непомеченные объекты (на которые нет ссылок).
Root → A → B
→ C
Недостижимые объекты (GC их удаляет)
Root → A → B (C больше недоступен)
C (GC удалит!)
Объекты делятся на молодые (Young) и старые (Old)
Young Generation – новые объекты (большинство умирает быстро).
Old Generation – "долго живущие" объекты (Activity, Singleton).
Утечки памяти (Memory Leaks) происходят, если на объект осталась ссылка, но он больше не нужен.
class Activity {
var button: Button? = null
}
var activity: Activity? = Activity() // Создаём объект
activity?.button = Button() // `Button` ссылается на `Activity`
activity = null // Activity нельзя удалить из-за ссылки на кнопку!Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
- Runtime permissions — запрашиваются во время использования, а не при установке.
- Doze mode — экономия батареи при бездействии.
- Поддержка Fingerprint API.
- Поддержка USB Type-C.
- Новый менеджер памяти (Adoptable Storage).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊6👍2🔥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
- Throwable — базовый.
- Error — критические ошибки JVM (не обрабатываются).
- Exception — обрабатываемые ошибки.
- RuntimeException — unchecked (например, NullPointerException).
- Остальные — checked (например, IOException, SQLException).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Софткод (Softcode, Soft Coding) — это подход к программированию, при котором логика программы хранится в конфигурационных файлах, базе данных или других внешних источниках, а не жёстко (hardcoded) прописана в коде.
Пример Hardcode (жёстко зашито в коде)
val apiUrl = "https://api.example.com" // ❌ Если URL изменится, надо менять код
Пример Softcode (гибко через конфигурацию)
val apiUrl = Config.get("api_url") // ✅ Загружается из настроекКонфигурационные файлы (
config.json, .properties, .xml). База данных (логика, настройки, права пользователей).
API и сервер (получение UI-элементов, бизнес-логики с сервера).
Скриптовые языки (скрипты загружаются динамически).
Softcode через
SharedPreferences (конфигурация в памяти)val sharedPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val theme = sharedPrefs.getString("app_theme", "light") // ✅ Загружаем тему из настроекSoftcode через
remoteConfig (Firebase Remote Config)val minVersion = Firebase.remoteConfig.getInt("min_supported_version")Softcode через JSON-файл (читаем конфиг из assets)
fun getConfigValue(context: Context, key: String): String {
val json = context.assets.open("config.json").bufferedReader().use { it.readText() }
val jsonObject = JSONObject(json)
return jsonObject.getString(key) // ✅ Получаем значение из JSON
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Итератор — это объект, позволяющий поэлементно перебирать коллекцию (список, массив и т.п.).
Он обычно предоставляет методы hasNext() и next() и позволяет абстрагироваться от конкретной структуры.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍2
Являются двумя различными типами коллекций, которые используются для хранения наборов данных. Основные различия между этими двумя структурами данных касаются их внутренней реализации, что влияет на производительность операций добавления, удаления и доступа к элементам.
основан на динамическом массиве. Это позволяет обеспечить быстрый доступ к элементам по индексу, поскольку адрес каждого элемента в памяти может быть вычислен напрямую. Однако, поскольку внутренний массив имеет фиксированный размер, при его переполнении необходимо выделить массив большего размера и скопировать в него все элементы из старого массива, что делает операции добавления и удаления более затратными по времени, особенно для больших объемов данных.
основан на двусвязном списке элементов, где каждый элемент (узел) содержит данные и ссылки на предыдущий и следующий элементы в списке. Это обеспечивает высокую производительность операций вставки и удаления, поскольку требуется лишь изменить ссылки у соседних элементов, но доступ к элементам по индексу занимает больше времени, так как для этого нужно последовательно пройти от начала или конца списка до нужного элемента.
Быстрый доступ к элементам по индексу. Медленные операции добавления и удаления элементов (особенно в начале и середине списка), так как может потребоваться сдвиг оставшейся части массива.
Быстрые операции вставки и удаления элементов, поскольку они требуют только изменения ссылок. Медленный доступ к элементам по индексу, так как для доступа к элементу необходимо пройти по списку.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
– Через Navigation Component,
– через интенты и аргументы,
– с помощью Router или Navigator при использовании архитектур (MVP, MVI),
– в многомодульности — через интерфейсы и зависимости через DI.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Методы
equals() и hashCode() используются для сравнения объектов и их корректной работы в коллекциях (Set, Map). Метод
equals() должен:Рефлексивность:
a.equals(a) → true (объект равен самому себе). Симметричность:
a.equals(b) == b.equals(a). Транзитивность: если
a == b и b == c, то a == c. Согласованность: если
a == b, то a.equals(b) всегда возвращает одно и то же, пока объект не изменится. Сравнение с
null всегда даёт false: a.equals(null) == false. class User(val name: String, val age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true // Проверка на ссылочное равенство
if (other !is User) return false // Проверка типа
return name == other.name && age == other.age // Сравнение полей
}
}val user1 = User("Alice", 25)
val user2 = User("Alice", 25)
println(user1 == user2) // true (потому что переопределён equals)Метод
hashCode() должен:Согласованность с
equals(): если a == b, то a.hashCode() == b.hashCode(). Но обратное не обязательно: два разных объекта могут иметь одинаковый
hashCode(). Хеш-код не должен меняться, если объект не изменился.
class User(val name: String, val age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is User) return false
return name == other.name && age == other.age
}
override fun hashCode(): Int {
return name.hashCode() * 31 + age // 31 - стандартный коэффициент
}
}val userSet = HashSet<User>()
userSet.add(User("Alice", 25))
println(userSet.contains(User("Alice", 25))) // true
Чтобы не писать
equals() и hashCode() вручную, можно использовать data class: data class User(val name: String, val age: Int)
data class автоматически создаёт equals(), hashCode(), а также copy() и toString(). val user1 = User("Alice", 25)
val user2 = User("Alice", 25)
println(user1 == user2) // true (equals)
println(user1.hashCode() == user2.hashCode()) // trueСтавь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
- Статический вложенный класс (static nested) не имеет доступа к членам внешнего класса без явной ссылки.
- Нестатический вложенный класс (inner class) имеет доступ к членам внешнего класса, включая приватные.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Бранчинг (ветвление) — это способ управления кодом в 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
- Normal permissions — автоматически предоставляются (например, доступ к интернету).
- Dangerous permissions — требуют запроса у пользователя (камера, геолокация, контакты).
- Signature permissions — предоставляются только приложениям с той же подписью.
- Special permissions — требуют перехода в системные настройки (например, SYSTEM_ALERT_WINDOW, BATTERY_OPTIMIZATIONS).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1
Да, ошибки (
Exceptions) обрабатываются по-разному в launch и async! launch сразу выбрасывает исключение, и если нет try-catch, корутина завершает родительский CoroutineScope. fun main() = runBlocking {
launch {
println("Начало работы")
throw RuntimeException("Ошибка в launch!")
}
delay(100) // ❌ Код не выполнится, так как `launch` упадёт
println("Этот код не выполнится")
}Вывод в консоль
Начало работы
Exception in thread "main" java.lang.RuntimeException: Ошибка в launch!
Решение
Использовать
try-catch внутри launch. launch {
try {
throw RuntimeException("Ошибка в launch!")
} catch (e: Exception) {
println("Ошибка поймана: ${e.message}")
}
}В
async ошибка не выбрасывается сразу, а сохраняется в Deferred<T>. Она появится только при вызове await(). fun main() = runBlocking {
val deferred = async {
println("Начало работы async")
throw RuntimeException("Ошибка в async!")
}
delay(100) // ✅ Код выполнится, так как ошибка пока не выброшена
println("Этот код выполнится")
deferred.await() // 💥 Ошибка падает только здесь!
}Вывод в консоль
Начало работы async
Этот код выполнится
Exception in thread "main" java.lang.RuntimeException: Ошибка в async!
Решение
Использовать
try-catch при await(). try {
deferred.await()
} catch (e: Exception) {
println("Ошибка поймана: ${e.message}")
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Модификатор final можно применить:
- к переменной — нельзя изменить значение после инициализации;
- к методу — нельзя переопределить в подклассе;
- к классу — нельзя наследовать от этого класса;
- к параметрам метода — параметр нельзя переназначить внутри метода.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3💊1
В Kotlin все классы неявно наследуются от
Any (аналог Object в Java). Это значит, что любой класс в Kotlin имеет 3 базовых метода:
По умолчанию сравнивает ссылки (как
===) class Person(val name: String)
fun main() {
val p1 = Person("Alice")
val p2 = Person("Alice")
println(p1 == p2) // false (разные объекты)
}
Как переопределить
equals() для сравнения по значениям? class Person(val name: String) {
override fun equals(other: Any?): Boolean {
return other is Person && this.name == other.name
}
}
fun main() {
val p1 = Person("Alice")
val p2 = Person("Alice")
println(p1 == p2) // true (теперь сравниваются значения)
}По умолчанию генерируется на основе ссылки
val p = Person("Alice")
println(p.hashCode()) // Разный для каждого объектаКак переопределить
hashCode()? class Person(val name: String) {
override fun hashCode(): Int {
return name.hashCode() // Генерируем хеш-код на основе имени
}
}По умолчанию печатает
ClassName@hashCodeval p = Person("Alice")
println(p.toString()) // Person@4e25154f (неудобный вывод)Как сделать красивый вывод?
class Person(val name: String) {
override fun toString(): String {
return "Person(name=$name)"
}
}
val p = Person("Alice")
println(p.toString()) // Person(name=Alice)data class User(val name: String)
fun main() {
val u1 = User("Alice")
val u2 = User("Alice")
println(u1 == u2) // ✅ true (по значениям)
println(u1.hashCode()) // ✅ Одинаковый для объектов с одинаковыми данными
println(u1.toString()) // ✅ User(name=Alice)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3