Kotlin | Вопросы собесов
2.57K subscribers
28 photos
966 links
Download Telegram
🤔 Откуда происходит повторный вызов composable функции?

Повторный вызов происходит из механизма "recomposition". Compose автоматически вызывает функцию снова, если состояние, связанное с этой функцией, изменилось.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
🤔 Как по объекту понять, что он не используется?

Сильная ссылка - это ссылка, которая напрямую указывает на объект и предотвращает его сборку сборщиком мусора.

🚩Основные подходы

🟠WeakReference
Использование слабых ссылок (WeakReference) позволяет определить, был ли объект освобожден сборщиком мусора.
🟠ObjectWatcher (в LeakCanary)
LeakCanary использует ObjectWatcher для отслеживания объектов. Если объект не освобожден, ObjectWatcher уведомляет об утечке.

import java.lang.ref.WeakReference;

public class MemoryLeakExample {

public static void main(String[] args) {
// Создание объекта
MyObject myObject = new MyObject();

// Создание слабой ссылки на объект
WeakReference<MyObject> weakRef = new WeakReference<>(myObject);

// Удаление сильной ссылки
myObject = null;

// Вызов сборщика мусора
System.gc();

// Проверка, была ли слабая ссылка освобождена
if (weakRef.get() == null) {
System.out.println("Object has been garbage collected");
} else {
System.out.println("Object is still alive");
}
}

static class MyObject {
// Некоторая логика класса
}
}


🚩Понимание, что объект не используется

🟠Нет сильных ссылок
Убедитесь, что на объект нет сильных ссылок. Только слабые, мягкие или фантомные ссылки не предотвращают сборку объекта.
🟠Сборка мусора
Если объект становится недоступным через сильные ссылки, сборщик мусора может его освободить.
🟠Проверка с помощью WeakReference
Слабые ссылки могут быть использованы для проверки того, был ли объект освобожден.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Как изменить количество колонок в RecyclerView в зависимости от ориентации?

Ты можешь использовать GridLayoutManager и задать количество колонок динамически

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2💊1
🤔 Какие способы навигации знаешь?

В Android есть несколько способов навигации между экранами. Давай разберём основные.

🟠Activity-based навигация
Каждый экран – это отдельная Activity, переход осуществляется с помощью Intent.
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)


🟠Fragment-based навигация
Один Activity содержит несколько Fragment, навигация через FragmentManager.
supportFragmentManager.beginTransaction()
.replace(R.id.container, SecondFragment())
.addToBackStack(null)
.commit()


🟠Navigation Component (Jetpack)
Google рекомендует Navigation Component для удобной работы с Fragment.
findNavController().navigate(R.id.action_firstFragment_to_secondFragment)


🟠Bottom Navigation / Tab Navigation
Используется BottomNavigationView или TabLayout для переключения между экранами.
bottomNavigationView.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.home -> replaceFragment(HomeFragment())
R.id.profile -> replaceFragment(ProfileFragment())
}
true
}


🟠Drawer Navigation (Navigation Drawer)
Боковое меню (DrawerLayout) с пунктами навигации.
drawerLayout.openDrawer(GravityCompat.START)


🟠Deep Links и App Links
Навигация через ссылки (например, myapp://profile/123).
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="myapp" android:host="profile"/>
</intent-filter>


🟠Navigation в Jetpack Compose
Используется NavHost и NavController.
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile") { ProfileScreen(navController) }
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4💊1
🤔 Будет ли zip гарантировать параллельность выполнения 2 запросов в сеть, запущенных для Coroutine?

Нет, zip сам по себе не гарантирует параллельность.
Если ты просто напишешь zip(firstCall(), secondCall()), оба вызова начнутся последовательно, и zip просто объединит их результаты.
Чтобы получить параллельность:
- Запусти оба вызова через async.
- Затем передай их await() в zip.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
🤔 Что делает библиотека LifeCycle ?

Библиотека Lifecycle в Android Jetpack помогает управлять и контролировать жизненный цикл компонентов Android, таких как Activities и Fragments. Она упрощает создание компонентов, которые осведомлены о своем жизненном цикле и могут корректно реагировать на изменения в нем. Это позволяет избегать утечек памяти и некорректной работы компонентов при изменении конфигураций или переходах между состояниями.

🚩Основные компоненты библиотеки Lifecycle

🟠LifecycleOwner
Это интерфейс, который определяет класс как владеющий жизненным циклом. Activity и Fragment уже реализуют этот интерфейс.
class MyActivity : AppCompatActivity(), LifecycleOwner {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}


🟠LifecycleObserver
Это интерфейс, который позволяет классу наблюдать за изменениями в жизненном цикле компонента. Методы, аннотированные @OnLifecycleEvent, будут вызываться при соответствующих событиях жизненного цикла.
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() {
// Код, который выполняется при старте жизненного цикла
Log.d("MyObserver", "onStart called")
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {
// Код, который выполняется при остановке жизненного цикла
Log.d("MyObserver", "onStop called")
}
}


🟠Lifecycle
Это класс, который содержит информацию о текущем состоянии и позволяет другим объектам наблюдать за изменениями в жизненном цикле.
class MyActivity : AppCompatActivity() {
private lateinit var myObserver: MyObserver

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

myObserver = MyObserver()
lifecycle.addObserver(myObserver)
}
}


🚩Применение библиотеки Lifecycle

1⃣ Добавьте зависимости в ваш build.gradle
dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}


2⃣Создайте класс, реализующий LifecycleObserver
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Log.d("MyObserver", "Activity resumed")
}

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
Log.d("MyObserver", "Activity paused")
}
}


3⃣Добавьте LifecycleObserver в LifecycleOwner (например, Activity)
class MyActivity : AppCompatActivity() {
private lateinit var myObserver: MyObserver

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

myObserver = MyObserver()
lifecycle.addObserver(myObserver)
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Какой объём памяти у стека и кучи?

- Стек (Stack) — хранит локальные переменные, параметры, вызовы функций. Объём ограничен (обычно от 512 КБ до 2 МБ на поток).
- Куча (Heap) — используется для динамического размещения объектов (new). Размер зависит от платформы и может быть от нескольких МБ до нескольких ГБ.
Стек быстрее, но меньше. Куча медленнее (и управляется GC), но объёмнее.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
🤔 Почему могут пропадать пользовательские данные при повороте экрана?

Пропадание пользовательских данных при повороте экрана в Android-приложениях является распространенной проблемой, связанной с тем, как Android управляет жизненным циклом активности. Когда устройство поворачивается, система уничтожает текущую активность и создает её заново, что приводит к потере данных, если они не были сохранены должным образом.

🚩Причины пропадания данных при повороте экрана

🟠Пересоздание активности
🟠Неправильное управление состоянием

🚩Способы предотвращения потери данных

Сохранение состояния с помощью onSaveInstanceState и onRestoreInstanceState
class MainActivity : AppCompatActivity() {

private var userData: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

if (savedInstanceState != null) {
userData = savedInstanceState.getString("USER_DATA_KEY")
// Восстановите данные в пользовательском интерфейсе
}
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("USER_DATA_KEY", userData)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
userData = savedInstanceState.getString("USER_DATA_KEY")
// Восстановите данные в пользовательском интерфейсе
}
}


Использование Retain Fragment. Этот метод сохраняет данные, используя фрагмент, который сохраняет своё состояние при пересоздании активности.
class RetainFragment : Fragment() {
var userData: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
}

class MainActivity : AppCompatActivity() {

private lateinit var retainFragment: RetainFragment

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val fragmentManager = supportFragmentManager
retainFragment = fragmentManager.findFragmentByTag("RETAIN_FRAGMENT") as RetainFragment?
?: RetainFragment().also {
fragmentManager.beginTransaction().add(it, "RETAIN_FRAGMENT").commit()
}

// Используйте данные из RetainFragment
val userData = retainFragment.userData
}
}


Использование SavedStateHandle в ViewModel
class UserViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
var userData: String?
get() = savedStateHandle.get("USER_DATA_KEY")
set(value) = savedStateHandle.set("USER_DATA_KEY", value)
}

class MainActivity : AppCompatActivity() {

private lateinit var viewModel: UserViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(UserViewModel::class.java)

// Используйте данные из ViewModel
val userData = viewModel.userData
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Какой основной поток выполнения приложения?

Основной поток приложения — это главный поток (UI-поток), который отвечает за обработку пользовательского интерфейса и взаимодействие с пользователем.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 Из каких основных элементов состоит Dagger?

Dagger – это фреймворк для внедрения зависимостей (DI), который помогает управлять созданием и передачей объектов в приложении. В основе Dagger лежат граф зависимостей, который состоит из следующих ключевых элементов:

🚩`@Module` и `@Provides` – создание зависимостей

@Module – это класс, который предоставляет зависимости.
@Provides – аннотация, указывающая, что метод создаёт объект для графа Dagger.
@Module
class NetworkModule {

@Provides
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}


🟠`@Inject` – внедрение зависимостей
@Inject в конструкторе – позволяет Dagger автоматически создавать объект без @Module.
class ApiService @Inject constructor(private val retrofit: Retrofit) {
fun fetchData() { /*...*/ }
}


🟠`@Component` – связывание модулей и мест внедрения
@Component – это интерфейс, который соединяет модули и классы, где нужны зависимости.
@Component(modules = [NetworkModule::class])
interface AppComponent {
fun inject(activity: MainActivity) // Указываем, куда внедрять зависимости
}


🟠`@Singleton` – область жизни объекта
Используется для создания одного экземпляра объекта на всё приложение.
@Singleton
@Component(modules = [NetworkModule::class])
interface AppComponent


🟠`@Binds` – привязка интерфейса к реализации
Если у нас есть интерфейс и его реализация, лучше использовать @Binds вместо @Provides.
interface Repository {
fun getData(): String
}

class RepositoryImpl @Inject constructor() : Repository {
override fun getData() = "Data from repository"
}

@Module
abstract class RepositoryModule {
@Binds
abstract fun bindRepository(repo: RepositoryImpl): Repository
}


🟠`Subcomponent` – вложенные компоненты
Если нужно создать зависимости с разными областями жизни (например, Activity, Fragment), используют @Subcomponent.
@Subcomponent
interface ActivityComponent {
fun inject(activity: MainActivity)
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 Работает ли switch() с double/float?

Нет, в Java switch не работает с float и
ли double, так как они подвержены проблемам сравнения с плавающей точкой. switch работает с int, byte, short, char, enum, String, а также с их обёрточными типами.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1💊1
🤔 Какое событие вызывается при нажатии юзера по экрану?

В Android при нажатии пользователя на экран вызывается событие ACTION_DOWN.

🚩Разбор работы событий касания

Android использует систему обработки касаний через MotionEvent. Когда пользователь касается экрана, система генерирует разные типы событий:
ACTION_DOWN – вызывается в момент первого касания.
ACTION_MOVE – вызывается, когда пользователь двигает палец по экрану.
ACTION_UP – вызывается, когда пользователь убирает палец с экрана.
ACTION_CANCEL – вызывается, если система прерывает касание (например, из-за входящего звонка).

🚩Как обрабатывать нажатие

Для обработки событий касания нужно переопределить метод onTouchEvent() в View или использовать setOnTouchListener().
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d("TouchEvent", "Палец коснулся экрана")
return true
}
MotionEvent.ACTION_UP -> {
Log.d("TouchEvent", "Палец отпущен")
}
}
return super.onTouchEvent(event)
}


Можно также назначить слушатель
view.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
Log.d("TouchEvent", "Нажатие зафиксировано")
true
} else {
false
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Конструкторы нужны для инициализации свойств. В data class основной конструктор обязателен, так как он используется для equals, copy, toString и hashCode. Обойтись совсем без конструктора нельзя — хотя можно использовать значения по умолчанию.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
👍2🔥2💊1
🤔 В чём отличие АПК с подписью и без подписи?

APK (Android Package) — это архив с кодом приложения, ресурсами и манифестом.
Приложение должно быть подписано, чтобы его можно было установить на устройство.

🚩Неподписанный APK (`unsigned APK`)

- Это черновая версия APK, которая не имеет цифровой подписи.
- Такой APK можно запустить только в эмуляторе или при отладке (debug build).
- Google Play не принимает неподписанные APK.
При сборке debug-версии в Android Studio:
gradlew assembleDebug


Попытка установить неподписанный APK
adb install app-unsigned.apk


Ошибка
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]


🚩Подписанный APK (`signed APK`)

- Подписанный APK содержит цифровую подпись, которая гарантирует, что код не был изменён.
- Android проверяет ключ подписи перед установкой.
Google Play требует подписанный APK или AAB.
Как подписать APK вручную?
apksigner sign --ks my-release-key.jks --out app-signed.apk app-unsigned.apk


🚩Зачем нужна подпись?

Подпись APK гарантирует*
Целостность → код не был изменён после сборки.
Подлинность → приложение подписано разработчиком, а не злоумышленником.
Обновления → только приложения с тем же ключом могут обновлять старую версию.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Какие модификаторы доступа могут быть в типах в Kotlin?

- public — доступен везде (по умолчанию).
- internal — доступен в пределах модуля.
- protected — доступен внутри класса и подклассов.
- private — доступен внутри файла или класса.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🤔 Отличие sealed и enum классов

sealed class – это ограниченная иерархия классов, где можно создавать разные подклассы с разными свойствами.
enum class – это фиксированный набор однотипных объектов, которые не имеют разной структуры.

🚩`enum class` – для фиксированного набора значений

Когда значения не изменятся (например, дни недели, цвета, статусы).
Когда у всех значений одинаковая структура.
enum class Status {
LOADING, SUCCESS, ERROR
}


Можно добавлять свойства и методы
enum class Color(val hex: String) {
RED("#FF0000"),
GREEN("#00FF00"),
BLUE("#0000FF");

fun printHex() = println(hex)
}

fun main() {
val color = Color.RED
println(color.hex) // #FF0000
color.printHex() // #FF0000
}


🚩`sealed class` – для сложных состояний с разной структурой

Когда у состояний разные параметры и поведение.
Когда нужен when, который проверяет все возможные подклассы.
sealed class Status {
object Loading : Status()
data class Success(val data: String) : Status()
data class Error(val message: String) : Status()
}


Использование с when (без else)
fun handleStatus(status: Status) {
when (status) {
is Status.Loading -> println("Загрузка...")
is Status.Success -> println("Данные: ${status.data}")
is Status.Error -> println("Ошибка: ${status.message}")
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Назовите все операторы комбинирования потоков в RxJava

Основные операторы:
- merge — объединяет элементы из нескольких источников.
- concat — объединяет источники последовательно.
- zip — комбинирует элементы из разных источников попарно.
- combineLatest — объединяет последние элементы.
- switchMap — переключается на новый поток и отменяет предыдущий.
- amb — выбирает тот источник, который быстрее начнёт эмиссию.
- join, groupJoin — объединяют потоки с логикой по времени жизни элементов.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
🤔 Вставка значения быстрее в Linked list или в Array list?

🚩Вставка в `ArrayList`

ArrayList реализован на основе массива.

🟠Вставка в конец
Амортизированное O(1). Если массив не заполнен, элемент добавляется в конец списка. В случае, если массив заполнен, требуется выделение нового массива и копирование элементов, что занимает O(n) времени.
val arrayList = ArrayList<Int>()
arrayList.add(1) // Быстрая вставка в конец


🟠Вставка в начало или середину
O(n). Необходимо сдвинуть все элементы, начиная с позиции вставки, чтобы освободить место для нового элемента. Это требует времени, пропорционального количеству элементов после позиции вставки.
arrayList.add(0, 2) // Медленная вставка в начало
arrayList.add(1, 3) // Медленная вставка в середину


🚩Вставка в LinkedList

LinkedList реализован на основе узлов, где каждый узел содержит ссылку на следующий и/или предыдущий узел (в случае двусвязного списка).

🟠Вставка в начало: O(1). Для добавления элемента в начало достаточно изменить ссылки первого узла и нового узла.
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
👍4
🤔 В чём разница между FragmentManager и FragmentTransaction?

- FragmentManager управляет фрагментами: добавление, поиск, стек возврата.
- FragmentTransaction используется для выполнения операций с фрагментами (добавление, замена, удаление). Это как "транзакция базы данных", применяемая к UI.
Transaction создаётся через FragmentManager.beginTransaction() и применяется через commit().


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2💊2
🤔 Опиши как лучше всего использовать модификаторы доступа в Java?

В Java есть 4 модификатора доступа, которые контролируют видимость полей, методов и классов:

🚩`private` – самый безопасный (использовать по умолчанию)

Поля почти всегда должны быть private, чтобы инкапсулировать данные. Методы тоже лучше делать private, если они не должны использоваться извне.
public class User {
private String name; // Доступен только внутри класса

public User(String name) {
this.name = name;
}

private void logUserAction() { // Только внутри класса
System.out.println(name + " совершил действие");
}
}


🚩`package-private` (без модификатора) – для классов внутри пакета

Используется, если класс не должен быть доступен за пределами пакета. Полезно для вспомогательных классов.
class FileHelper { //  Доступен только в этом пакете
static void readFile(String path) {
System.out.println("Читаем файл: " + path);
}
}


🚩`protected` – для наследников, но не для всех

protected лучше использовать только в abstract классах и для методов, которые должны быть переопределены. Не используй protected для полей → нарушает инкапсуляцию!
abstract class Animal {
protected void makeSound() { // Наследники могут переопределять
System.out.println("Какой-то звук");
}
}

class Dog extends Animal {
@Override
protected void makeSound() {
System.out.println("Гав-гав!");
}
}


🚩`public` – только если класс/метод нужен везде

Класс можно делать public, только если он должен быть доступен во всех пакетах. Не делай поля public → нарушает инкапсуляцию!
public class UserManager { //  Доступен везде
public void createUser() { // Можно вызывать в любом месте
System.out.println("Создание пользователя...");
}
}


🚩`final` и `static` – дополняем модификаторы

final class → класс нельзя наследовать.
final method → метод нельзя переопределить.
final field → переменная неизменяема (константа).
public final class MathUtils {
public static final double PI = 3.14159;
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2