Зависит от типа CoroutineScope:
- Если корутина не обернута в try-catch, то весь scope завершится с ошибкой.
- Если используется supervisorScope, падение одной корутины не остановит другие.
- Если в launch ошибка не обработана, то scope отменится.
- Если используется async с await(), исключение выбросится при вызове await().
Для устойчивости лучше оборачивать код в try-catch или использовать supervisorScope.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊4👍1🔥1
Чтобы чат работал быстро и без лагов, нужно:
Оптимизировать
RecyclerView (ViewHolder, DiffUtil, Payloads). Минимизировать работу в
UI Thread (использовать Coroutines, Paging 3). Оптимизировать загрузку изображений (Glide, Coil).
Использовать Room + Flow / LiveData для офлайн-кеша.
Вместо
notifyDataSetChanged() лучше использовать ListAdapter, который сам обновляет только изменённые сообщения. class ChatAdapter : ListAdapter<Message, ChatViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Message>() {
override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean =
oldItem == newItem // data class сравнит автоматически
}
}
}Если, например, только статус сообщения изменился, не нужно перерисовывать всё сообщение!
override fun getChangePayload(oldItem: Message, newItem: Message): Any? {
return if (oldItem.status != newItem.status) "STATUS_CHANGED" else null
}Пример в
onBindViewHolder() override fun onBindViewHolder(holder: ChatViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains("STATUS_CHANGED")) {
holder.updateStatus() // Обновляем только статус
} else {
super.onBindViewHolder(holder, position, payloads)
}
}Запросы в БД в
Worker Thread (Coroutines + Room) fun loadMessages(): Flow<List<Message>> = messageDao.getMessages()
Используем
Paging 3 для подгрузки сообщений val pager = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { messageDao.getPagedMessages() }
).flow.cachedIn(viewModelScope)
Если чат содержит аватары или изображения, они должны загружаться лениво и в фоновом потоке.
Glide.with(context)
.load(user.avatarUrl)
.circleCrop()
.placeholder(R.drawable.placeholder)
.into(imageView)
Чтобы сообщения не загружались из сети при каждом запуске, сохраняем их в Room.
@Dao
interface MessageDao {
@Query("SELECT * FROM messages ORDER BY timestamp DESC")
fun getMessages(): Flow<List<Message>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMessages(messages: List<Message>)
}
Используем
setHasFixedSize(true), если сообщения не меняют размер recyclerView.setHasFixedSize(true)
Используем
setItemViewCacheSize(), чтобы кэшировать ViewHolder recyclerView.setItemViewCacheSize(20)
Отключаем анимации
RecyclerView (если лагает) (recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Сборка Android-приложения проходит несколько этапов:
Компиляция (
.kt/.java → .class → .dex) Объединение ресурсов (
XML, PNG → R.java) Сборка APK/AAB
Подписывание и оптимизация
Установка и запуск на устройстве
Компиляция Kotlin/Java в байткод JVM (
.class) Kotlin-код (
.kt) → Java-байткод (.class) Java-код (
.java) → .class - Используются компиляторы:
-
kotlinc (Kotlin Compiler) -
javac (Java Compiler) // Kotlin-код (MainActivity.kt)
fun main() {
println("Привет, Android!")
}
kotlinc MainActivity.kt -d MainActivity.class
Преобразование
.class в `.dex* Android не использует байткод JVM, а преобразует
.class в Dalvik Executable (.dex). d8 MainActivity.class --output classes.dex
Android Gradle Plugin (AGP) обрабатывает ресурсы
-
res/layout/*.xml → UI файлы -
res/drawable/*.png → Иконки -
AndroidManifest.xml → Метаинформация public final class R {
public static final class layout {
public static final int activity_main = 0x7f0a0000;
}
}APK (старый формат) и AAB (новый формат Google Play)
classes.dex + res/ + AndroidManifest.xml объединяются Проходит обфускация (
ProGuard / R8) APK/AAB упаковывается в ZIP-архив
zipalign -v 4 app.apk signed_app.apk
APK/AAB должен быть подписан перед установкой
apksigner sign --ks my-release-key.jks --out signed_app.apk app-release.apk
Установка и запуск
adb install signed_app.apk
adb shell am start -n com.example.app/.MainActivity
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
- Thread — низкоуровневый механизм. Требует ручного управления.
- Проблемы:
- Утечки памяти (если не завершён правильно).
- Сложность отмены.
- Нет управления жизненным циклом.
- Потенциальная блокировка UI, если использовать неосторожно.
Поэтому чаще рекомендуются Handler, Executor, Coroutine, WorkManager, которые безопаснее и проще в управлении.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1💊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💊1
- Intent с setPackage — для вызова компонента другого приложения.
- ContentProvider — как общий интерфейс работы с данными.
- Broadcasts — обмен событиями.
- FileProvider — обмен файлами.
- AIDL/Bound Services — сложный межпроцессный обмен.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Подключение
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="https://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
Через open и :. Родительский класс должен быть open, а дочерний объявляется с class B : A().
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Являются двумя различными типами коллекций, которые используются для хранения наборов данных. Основные различия между этими двумя структурами данных касаются их внутренней реализации, что влияет на производительность операций добавления, удаления и доступа к элементам.
основан на динамическом массиве. Это позволяет обеспечить быстрый доступ к элементам по индексу, поскольку адрес каждого элемента в памяти может быть вычислен напрямую. Однако, поскольку внутренний массив имеет фиксированный размер, при его переполнении необходимо выделить массив большего размера и скопировать в него все элементы из старого массива, что делает операции добавления и удаления более затратными по времени, особенно для больших объемов данных.
основан на двусвязном списке элементов, где каждый элемент (узел) содержит данные и ссылки на предыдущий и следующий элементы в списке. Это обеспечивает высокую производительность операций вставки и удаления, поскольку требуется лишь изменить ссылки у соседних элементов, но доступ к элементам по индексу занимает больше времени, так как для этого нужно последовательно пройти от начала или конца списка до нужного элемента.
Быстрый доступ к элементам по индексу. Медленные операции добавления и удаления элементов (особенно в начале и середине списка), так как может потребоваться сдвиг оставшейся части массива.
Быстрые операции вставки и удаления элементов, поскольку они требуют только изменения ссылок. Медленный доступ к элементам по индексу, так как для доступа к элементу необходимо пройти по списку.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
- Использовать overridePendingTransition() для задания анимации входа и выхода.
2. Fragment переходы:
- Установить FragmentTransaction.setCustomAnimations() или использовать shared element transitions.
3. Navigation Component:
- Указать анимации в nav_graph.xml или через NavOptions.
4. Compose Navigation:
- Использовать AnimatedVisibility, AnimatedContent или другие transition-компоненты.
Анимации переходов делают интерфейс плавным и улучшают UX.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Да, потеря состояния (state) может быть связана с фрагментами (Fragments) в Android. Это довольно распространённая проблема, особенно при работе с динамическими интерфейсами. Давайте разберёмся, почему она возникает, как её предотвратить и какие решения существуют.
Фрагменты имеют сложный жизненный цикл, который тесно связан с активностью. Основные этапы:
onCreate() — создаётся фрагмент, инициализируются объекты.onViewCreated() — создаётся View (UI компоненты).onStart() / onResume() — фрагмент становится видимым и активным.onPause() / onStop() — фрагмент приостанавливается.onDestroyView() — уничтожается только View (UI), но сам фрагмент всё ещё существует.onDestroy() — полностью уничтожается фрагмент.Фрагменты могут пересоздаваться системой, например, при смене ориентации экрана или нехватке памяти. Если разработчик неправильно сохраняет состояние фрагмента, оно может быть утеряно.
Когда фрагмент переходит в состояние
onDestroyView(), его View уничтожается, но сам объект фрагмента сохраняется. Если пользователь вернётся к этому фрагменту, View будет пересоздана, и вы потеряете все изменения, сделанные ранее, если они не сохранены явно.Использование
FragmentManager или FragmentTransaction с неправильными методами, такими как replace() или add(), без должного управления стэком (back stack), может привести к пересозданию или дублированию фрагментов, что вызывает потерю состояния.Фрагменты, как и активности, используют механизм сохранения состояния через
Bundle (в onSaveInstanceState). Если состояние не сохранено правильно, данные могут быть потеряны.При уничтожении фрагмента система вызывает метод
onSaveInstanceState(). Здесь вы можете сохранить важные данные в Bundle.override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("key_text", editText.text.toString())
}При пересоздании фрагмента данные можно восстановить в
onViewCreated()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val restoredText = savedInstanceState?.getString("key_text")
editText.setText(restoredText)
}Для хранения состояния, которое переживает уничтожение и пересоздание фрагмента, лучше использовать архитектурный компонент
ViewModel.ViewModelclass MyViewModel : ViewModel() {
val textData = MutableLiveData<String>()
}class MyFragment : Fragment() {
private lateinit var viewModel: MyViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// Восстановление данных
viewModel.textData.observe(viewLifecycleOwner) { text ->
editText.setText(text)
}
// Сохранение данных при изменении
editText.addTextChangedListener {
viewModel.textData.value = it.toString()
}
}
}При работе с фрагментами всегда добавляйте их в back stack, если вы хотите сохранить состояние.
val fragment = MyFragment()
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Android оценивает приоритет процесса:
1. Foreground (не трогается),
2. Visible,
3. Service,
4. Cached (в фоне),
5. Empty (не содержит компонентов).
Убиваются сначала самые низкоприоритетные, при нехватке памяти.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Методы
get и replace в Kotlin относятся к работе с коллекциями, карты (Map) или к строкам. В зависимости от контекста, они вызываются через разные классы. Давайте разберем каждый случай отдельно.В контексте работы с картами (
Map), методы get и replace относятся к получению значений по ключу и замене существующих значений.Метод
getМетод
get используется для извлечения значения из карты по указанному ключу.val map = mapOf("key1" to "value1", "key2" to "value2")
println(map.get("key1")) // value1
println(map["key2"]) // value2 (альтернатива `get`)Метод
replace используется для обновления значения, связанного с определённым ключом, если он существует. Этот метод доступен только для изменяемых карт (MutableMap).val mutableMap = mutableMapOf("key1" to "value1", "key2" to "value2")
mutableMap.replace("key1", "newValue1")
println(mutableMap) // {key1=newValue1, key2=value2}В контексте строк, методы
get и replace работают с символами и подстроками.Метод
get используется для доступа к символу строки по индексу. Это альтернатива квадратным скобкам.val text = "Kotlin"
println(text.get(0)) // K
println(text[1]) // o (альтернатива `get`)
Метод
replace заменяет символы или подстроки в строке на заданные.val text = "Kotlin is awesome"
val newText = text.replace("awesome", "powerful")
println(newText) // Kotlin is powerful
Если вы пишете свои классы, вы можете переопределить оператор
get и метод replace, чтобы использовать их для своих нужд.class CustomList<T>(private val items: List<T>) {
operator fun get(index: Int): T {
return items[index]
}
}
fun main() {
val customList = CustomList(listOf(1, 2, 3))
println(customList[0]) // 1
}Пример с
replaceclass CustomMap<K, V>(private val map: MutableMap<K, V>) {
fun replace(key: K, value: V) {
if (map.containsKey(key)) {
map[key] = value
}
}
}
fun main() {
val customMap = CustomMap(mutableMapOf("key1" to "value1"))
customMap.replace("key1", "newValue1")
println(customMap) // {key1=newValue1}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
ArrayList реализован на основе массива. Амортизированное O(1). Если массив не заполнен, элемент добавляется в конец списка. В случае, если массив заполнен, требуется выделение нового массива и копирование элементов, что занимает O(n) времени.
val arrayList = ArrayList<Int>()
arrayList.add(1) // Быстрая вставка в конец
O(n). Необходимо сдвинуть все элементы, начиная с позиции вставки, чтобы освободить место для нового элемента. Это требует времени, пропорционального количеству элементов после позиции вставки.
arrayList.add(0, 2) // Медленная вставка в начало
arrayList.add(1, 3) // Медленная вставка в середину
LinkedList реализован на основе узлов, где каждый узел содержит ссылку на следующий и/или предыдущий узел (в случае двусвязного списка).val linkedList = LinkedList<Int>()
linkedList.addFirst(1) // Быстрая вставка в начало
O(1) (если есть ссылка на последний узел) или O(n) (если необходимо пройти весь список). Если список двусвязный и хранится ссылка на последний элемент, вставка в конец осуществляется за O(1). В противном случае, требуется пройти весь список до конца.
linkedList.addLast(2) // Быстрая вставка в конец, если есть ссылка на последний узел
O(n). Необходимо пройти список до нужной позиции и изменить ссылки узлов.
linkedList.add(1, 3) // Медленная вставка в середину
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Файл classes.dex содержит весь скомпилированный Kotlin/Java код.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
Модификаторы доступа (Access Modifiers) — это ключевые слова, которые определяют, кто может видеть и использовать класс, переменную или метод.
Они помогают инкапсулировать данные и защищать код от неправильного использования.
Пример Java
public class Example {
private int a = 10; // ❌ Только внутри класса
int b = 20; // ✅ Видно внутри пакета (package-private)
protected int c = 30; // ✅ Видно в пакете и наследниках
public int d = 40; // ✅ Доступно везде
}В Kotlin есть почти такие же модификаторы, но
package-private заменён на internal.Пример Kotlin
class Example {
private val a = 10 // ❌ Только в этом классе
internal val b = 20 // ✅ Видно в модуле
protected val c = 30 // ✅ Видно в наследниках
public val d = 40 // ✅ Видно везде (по умолчанию)
}Для полей (переменных класса)
public class User {
private String name; // ❌ Скрыто от других классов
public User(String name) {
this.name = name;
}
public String getName() { // ✅ Доступ через метод
return name;
}
}Для методов
class Animal {
protected void makeSound() { // ✅ Доступен только в наследниках
System.out.println("Животное издаёт звук");
}
}
class Dog extends Animal {
public void bark() {
makeSound(); // ✅ Разрешено, потому что `protected`
System.out.println("Гав-гав!");
}
}Для классов
public class Car { } // ✅ Доступен везде
class Engine { } // ❌ Только в этом пакетеВ Kotlin можно делать
private class, но только внутри другого класса. class Car {
private class Engine // ❌ Только в этом классе
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Executor — это абстракция для управления потоками. Он позволяет отправлять задачи на выполнение, не заботясь напрямую о создании и управлении потоками.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Если профайлер показывает, что рендеринг какого-либо фрейма занял 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