Google постоянно ограничивает работу сервисов в Android, чтобы:
Уменьшить расход батареи
Оптимизировать использование памяти
Защитить пользователя от фоновых процессов, "убивающих" производительность
Сервис нельзя запустить из фона, если приложение не активно.
startService(Intent) выдаст ошибку, если приложение не на переднем плане. 1. Использовать Foreground Service (с уведомлением).
2. Использовать JobIntentService / WorkManager.
class MyForegroundService : Service() {
override fun onCreate() {
super.onCreate()
val notification = NotificationCompat.Builder(this, "channelId")
.setContentTitle("Сервис работает")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build()
startForeground(1, notification) // Запускаем сервис в Foreground
}
override fun onBind(intent: Intent?): IBinder? = null
}Если сервис запущен из фона (например, через
BroadcastReceiver), он не запустится. Исключение – если сервис работает в Foreground или с AlarmManager.
1. Использовать WorkManager (лучший вариант).
2. Использовать Foreground Service с уведомлением.
3. Использовать AlarmManager для периодических задач.
В спящем режиме (
Doze Mode) система отключает сервисы. Приложения не могут выполнять фоновые задачи.
Использовать Foreground Service.
Использовать JobScheduler / WorkManager.
Использовать Firebase Cloud Messaging (FCM) для пробуждения приложения.
Если пользователь смахнул приложение из списка недавних, фоновые сервисы будут убиты.
Решение → Foreground Service +
startForeground() или JobScheduler.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
RxJava может вызывать утечки памяти, если подписки (
Disposable) не очищаются правильно. Утечки памяти происходят, когда RxJava-события продолжают выполняться после уничтожения
Activity/Fragment, удерживая ссылки на View или Context. CompositeDisposable собирает все подписки и очищает их при onDestroy(). class MainActivity : AppCompatActivity() {
private val disposables = CompositeDisposable() // Собираем подписки
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val disposable = Observable.interval(1, TimeUnit.SECONDS)
.subscribe { println("Tick: $it") }
disposables.add(disposable) // Добавляем подписку
}
override fun onDestroy() {
super.onDestroy()
disposables.clear() // ✅ Очищаем подписки, предотвращая утечку памяти
}
}Если используем Jetpack ViewModel или
LifecycleOwner, можно автоматически отвязывать подписки. class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Observable.interval(1, TimeUnit.SECONDS)
.autoDispose(viewLifecycleOwner) // ✅ Автоочистка при `onDestroyView()`
.subscribe { println("Tick: $it") }
}
}Если подписка не в
CompositeDisposable, её можно очистить вручную. private var disposable: Disposable? = null
fun startObserving() {
disposable = Observable.interval(1, TimeUnit.SECONDS)
.subscribe { println("Tick: $it") }
}
fun stopObserving() {
disposable?.dispose() // ✅ Очищаем подписку
}
RxJava может удерживать
Activity/Fragment, вызывая утечки памяти. Лучше использовать
ViewModel + LiveData! class MyViewModel : ViewModel() {
private val _timer = MutableLiveData<Long>()
val timer: LiveData<Long> = _timer
init {
Observable.interval(1, TimeUnit.SECONDS)
.subscribe { _timer.postValue(it) }
}
}Подписка в
Activity (без утечек!) viewModel.timer.observe(this) { println("Tick: $it") }Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Порядок:
1. Статический блок суперкласса;
2. Статический блок текущего класса;
3. Конструктор суперкласса;
4. Конструктор текущего класса.
(Статические блоки вызываются один раз при загрузке класса.)
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
Модификации могут означать разные вещи в зависимости от контекста. В Android-разработке чаще всего речь идёт о следующих типах модификаций:
Модификация данных (изменение состояния)
Модификация UI (Compose Modifiers)
Модификация кода (рефакторинг, оптимизация)
Модификация системы (кастомные прошивки, рут-изменения)
Это изменение переменных, объектов и структур данных во время работы приложения.
class ViewModel : ViewModel() {
private val _state = MutableStateFlow("Привет, мир!")
val state: StateFlow<String> = _state
fun updateText(newText: String) {
_state.value = newText // Модификация состояния
}
}В Jetpack Compose модификации (
Modifier) используются для изменения внешнего вида и поведения UI-элементов. Text(
text = "Привет!",
modifier = Modifier
.padding(16.dp) // Добавляем отступ
.background(Color.Blue) // Меняем фон
.clickable { println("Нажали!") } // Добавляем клик
)
В программировании модификация кода означает его улучшение без изменения основной логики.
fun sum(a: Int, b: Int): Int {
return a + b
}После рефакторинга
fun sum(a: Int, b: Int) = a + b
Это изменения в самой операционной системе Android, например:
- Рут-доступ (
root), позволяющий изменять системные файлы. - Кастомные прошивки (например, LineageOS).
- Изменение build.prop для скрытия рут-доступа.
ro.build.version.sdk=33
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊7👍1
Аннотация
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍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
👍4
Наследование — это когда один класс расширяет другой, наследуя его поведение.
Композиция — это включение одного объекта в другой, при этом зависимость явно передаётся. Композиция считается более гибкой и менее связанной альтернативой наследованию.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В Kotlin оператор
== используется для структурного сравнения объектов. Оператор
== вызывает метод equals(), чтобы проверить содержимое объектов, а не их ссылки. data class User(val name: String)
fun main() {
val user1 = User("Alice")
val user2 = User("Alice")
println(user1 == user2) // true (структурное сравнение)
println(user1 === user2) // false (сравнение ссылок)
}
== проверяет, равны ли данные объектов (equals()). === проверяет, ссылаются ли объекты на один и тот же участок памяти. val str1 = "Hello"
val str2 = "Hello"
println(str1 == str2) // true (содержимое одинаковое)
println(str1 === str2) // true (Kotlin кеширует строки)
val obj1 = String("Hello".toCharArray())
val obj2 = String("Hello".toCharArray())
println(obj1 == obj2) // true (содержимое одинаковое)
println(obj1 === obj2) // false (разные объекты в памяти)
Если
equals() не переопределён, то сравниваются ссылки (как ===). class Person(val name: String)
fun main() {
val p1 = Person("Bob")
val p2 = Person("Bob")
println(p1 == p2) // false (equals() не переопределён, сравниваются ссылки)
}
Чтобы
== работал по содержимому, нужно переопределить equals()class Person(val name: String) {
override fun equals(other: Any?): Boolean {
return other is Person && other.name == this.name
}
}
fun main() {
val p1 = Person("Bob")
val p2 = Person("Bob")
println(p1 == p2) // true (equals() сравнивает содержимое)
}Kotlin предотвращает
NullPointerException при сравнении с null:val a: String? = null
val b: String? = "Hello"
println(a == b) // false (без NPE)
println(a == null) // true
Когда
a == b, на самом деле выполняется:a?.equals(b) ?: (b === null)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Нет, LayoutInflater — не синглтон, но может быть получен как скоуп-зависимый объект (getSystemService) в Context. Однако его можно переиспользовать, так как он не хранит состояния между вызовами.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊4🔥2
Разделение UI и бизнес-логики делает код понятнее, тестируемее и проще в поддержке.
Это ключевой принцип чистой архитектуры (Clean Architecture) и паттернов MVP, MVVM, MVI.
Плохой код (UI + логика в
Activity) class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val result = fetchData() // ❌ Логика в UI
button.text = result
}
}
fun fetchData(): String {
return "Данные с сервера" // ❌ Тут должна быть ViewModel
}
}Разделим код на
Activity + ViewModel MainActivity (только UI) class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
viewModel.text.observe(this) { text ->
button.text = text // ✅ UI обновляется из ViewModel
}
button.setOnClickListener {
viewModel.loadData() // ✅ Вызываем бизнес-логику
}
}
}MainViewModel (бизнес-логика) class MainViewModel : ViewModel() {
private val _text = MutableLiveData<String>()
val text: LiveData<String> = _text
fun loadData() {
_text.value = "Данные с сервера" // ✅ UI не знает, откуда данные
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
- В manifest:
- В коде:
Также можно использовать taskAffinity и фильтрацию в onNewIntent().
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
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
👍4🔥1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
Data Class и Sealed Class решают разные задачи и обеспечивают улучшения в организации кода, управлении состоянием и безопасности типов. Они вносят значительные упрощения и повышают читаемость кода в Kotlin-проектах.
Data Class предназначены для хранения данных и автоматически предоставляют ряд полезных методов, что упрощает разработку и уменьшает объем шаблонного кода. Основные причины использования Data Class:
Автоматически генерирует методы
equals(), hashCode(), и toString(), а также copy() и компонентные функции для объектов данных. Это избавляет от необходимости ручной реализации этих методов, что уменьшает количество кода и возможность ошибок.Идеально подходят для передачи данных между различными частями приложения, например, между слоями в архитектуре MVVM или при передаче данных между активностями и фрагментами.
С помощью них легко создавать неизменяемые объекты, что способствует безопасной работе с данными, особенно в многопоточной среде.
Sealed Class используются для определения закрытых иерархий классов, где все потомки известны и ограничены. Они полезны по следующим причинам:
Гарантируют, что все возможные подтипы обработаны в выражениях
when, что предотвращает ошибки во время выполнения из-за пропущенных случаев. Это упрощает управление состояниями и делает код более безопасным и предсказуемым.Ограничивают возможность создания подклассов за пределами файла, в котором они объявлены. Это предотвращает неожиданное наследование и сохраняет иерархию классов контролируемой и понятной.
Поскольку все расширения таких классов должны быть объявлены в том же файле, это способствует лучшей инкапсуляции и организации кода.
data class User(val name: String, val age: Int)
Пример Sealed Class
sealed class Result {
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Success with data: ${result.data}")
is Result.Failure -> println("Failure with error: ${result.error.message}")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Композиция создаётся внутри функции setContent, которая задает точку входа для composable функций в Activity или Fragment. Она инициирует рендеринг интерфейса и управление состоянием.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Чтобы
Retrofit мог возвращать Observable, Single, Maybe или Flowable из RxJava, нужно добавить RxJava Adapter. В
build.gradle.kts (Kotlin DSL)dependencies {
implementation("com.squareup.retrofit2:adapter-rxjava3:2.9.0") // Адаптер для RxJava 3
implementation("io.reactivex.rxjava3:rxjava:3.1.8") // RxJava 3
}Добавляем адаптер в
Retrofit.Builderval retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create()) // Преобразование JSON
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // Поддержка RxJava
.build()
Теперь можно возвращать RxJava-объекты вместо
Call<>. interface ApiService {
@GET("users/{id}")
fun getUser(@Path("id") userId: Int): Single<User>
}Пример с
Observable<> (несколько данных или обновления) interface ApiService {
@GET("users")
fun getUsers(): Observable<List<User>>
}Пример с
Flowable<> (если нужен Backpressure) interface ApiService {
@GET("posts")
fun getPosts(): Flowable<List<Post>>
}Пример подписки в
ViewModel (RxJava 3 + LiveData) class UserViewModel(private val apiService: ApiService) : ViewModel() {
private val _userLiveData = MutableLiveData<User>()
val userLiveData: LiveData<User> = _userLiveData
fun fetchUser(userId: Int) {
apiService.getUser(userId)
.subscribeOn(Schedulers.io()) // Запрос в фоновом потоке
.observeOn(AndroidSchedulers.mainThread()) // Обновление UI в главном потоке
.subscribe({ user ->
_userLiveData.value = user
}, { error ->
Log.e("UserViewModel", "Ошибка загрузки", error)
})
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
- App link: использует HTTP/HTTPS ссылки и требует подтверждения владения доменом через файл assetlinks.json.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Запрос данных из двух таблиц обычно выполняется с помощью операции объединения (JOIN) в SQL. Алгоритм и его сложность зависят от типа объединения, структуры данных и используемой базы данных.
Предположим, у нас есть две таблицы:
employees и departments. Мы хотим получить список сотрудников вместе с их отделами.SELECT employees.name, departments.name
FROM employees
INNER JOIN departments ON employees.department_id = departments.id;
Для каждой строки из первой таблицы выполняется поиск соответствующих строк во второй таблице.
for each row in employees:
for each row in departments:
if row.employees.department_id == row.departments.id:
yield (row.employees.name, row.departments.name)
Создается хеш-таблица для одной из таблиц на основе ключа соединения. Для каждой строки из другой таблицы проверяется наличие соответствующего ключа в хеш-таблице.
hash_table = {}
for each row in departments:
hash_table[row.id] = row.name
for each row in employees:
if row.department_id in hash_table:
yield (row.name, hash_table[row.department_id])Обе таблицы сортируются по ключу соединения, затем выполняется слияние отсортированных списков.
sorted_employees = sort(employees, key=lambda x: x.department_id)
sorted_departments = sort(departments, key=lambda x: x.id)
i, j = 0, 0
while i < len(sorted_employees) and j < len(sorted_departments):
if sorted_employees[i].department_id == sorted_departments[j].id:
yield (sorted_employees[i].name, sorted_departments[j].name)
i += 1
j += 1
elif sorted_employees[i].department_id < sorted_departments[j].id:
i += 1
else:
j += 1
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊3👍1