декабря 21, 2025
16 мин. чтения

Вопросы для собеседования на позицию старшего разработчика мобильных приложений (Android): Полное руководство

interview
career-advice
job-search
Вопросы для собеседования на позицию старшего разработчика мобильных приложений (Android): Полное руководство
MB

Milad Bonakdar

Автор

Освойте продвинутую разработку под Android с помощью основных вопросов для собеседования, охватывающих архитектурные шаблоны, оптимизацию производительности, внедрение зависимостей, тестирование, безопасность и проектирование систем для старших разработчиков.


Введение

От старших Android-разработчиков ожидается проектирование масштабируемых и поддерживаемых приложений, обеспечивающих высокую производительность и качество кода. Эта роль требует глубоких знаний Android-фреймворков, архитектурных паттернов, внедрения зависимостей, стратегий тестирования и умения принимать обоснованные технические решения.

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


Продвинутый Kotlin и языковые особенности (5 вопросов)

1. Объясните, что такое Kotlin Coroutines и каковы их преимущества перед потоками.

Ответ: Корутины - это легковесные примитивы параллелизма, позволяющие писать асинхронный код последовательным образом.

  • Преимущества перед потоками:
    • Легковесность: Можно создавать тысячи корутин без проблем с производительностью.
    • Структурированный параллелизм: Отношения родитель-потомок обеспечивают правильную очистку.
    • Поддержка отмены: Встроенное распространение отмены.
    • Обработка исключений: Структурированная обработка исключений.
  • Ключевые компоненты:
    • CoroutineScope: Определяет жизненный цикл.
    • Dispatchers: Контролируют контекст выполнения (Main, IO, Default).
    • suspend функции: Могут быть приостановлены и возобновлены.
class UserRepository(private val api: ApiService) {
    suspend fun getUser(id: Int): Result<User> = withContext(Dispatchers.IO) {
        try {
            val user = api.fetchUser(id)
            Result.success(user)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user
    
    fun loadUser(id: Int) {
        viewModelScope.launch {
            when (val result = repository.getUser(id)) {
                is Result.Success -> _user.value = result.data
                is Result.Error -> handleError(result.exception)
            }
        }
    }
}

Редкость: Очень часто Сложность: Сложно


2. Что такое Sealed Classes и когда их следует использовать?

Ответ: Sealed classes (запечатанные классы) представляют собой иерархии классов с ограничениями, где все подклассы известны во время компиляции.

  • Преимущества:
    • Исчерпывающие when выражения.
    • Безопасное с точки зрения типов управление состоянием.
    • Лучше, чем enum для сложных данных.
  • Случаи использования: Представление состояний, результатов, событий навигации.
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()
    
    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.getUser()
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

// In UI
when (val state = uiState.value) {
    is UiState.Loading -> showLoading()
    is UiState.Success -> showData(state.data)
    is UiState.Error -> showError(state.message)
    // Compiler ensures all cases are handled
}

Редкость: Часто Сложность: Средне


3. Объясните, что такое Kotlin Flow и чем он отличается от LiveData.

Ответ: Flow - это холодный асинхронный поток Kotlin, который последовательно выдает значения.

  • Flow vs LiveData:
    • Flow: Холодный поток, поддерживает операторы, не зависит от жизненного цикла, более гибкий.
    • LiveData: Горячий поток, зависит от жизненного цикла, специфичен для Android, проще для UI.
  • Типы Flow:
    • Flow: Холодный поток (запускается при сборе).
    • StateFlow: Горячий поток с текущим состоянием.
    • SharedFlow: Горячий поток для событий.
class UserRepository {
    // Cold Flow - starts when collected
    fun getUsers(): Flow<List<User>> = flow {
        val users = api.fetchUsers()
        emit(users)
    }.flowOn(Dispatchers.IO)
    
    // StateFlow - hot, holds state
    private val _userState = MutableStateFlow<List<User>>(emptyList())
    val userState: StateFlow<List<User>> = _userState.asStateFlow()
    
    // SharedFlow - hot, for events
    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events.asSharedFlow()
}

class UserViewModel : ViewModel() {
    val users = repository.getUsers()
        .map { it.filter { user -> user.isActive } }
        .catch { emit(emptyList()) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )
}

// Collect in Activity/Fragment
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.users.collect { users ->
            updateUI(users)
        }
    }
}

Редкость: Очень часто Сложность: Сложно


4. Что такое Inline Functions и когда их следует использовать?

Ответ: Inline functions (встроенные функции) копируют тело функции в место вызова, избегая накладных расходов на вызов функции.

  • Преимущества:
    • Устраняет накладные расходы на выделение лямбда-выражений.
    • Позволяет нелокальные возвраты из лямбда-выражений.
    • Лучшая производительность для функций высшего порядка.
  • Случаи использования: Функции высшего порядка с лямбда-параметрами.
  • Компромисс: Увеличивает размер кода.
// Without inline - creates lambda object
fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
}

// With inline - no lambda object created
inline fun measureTimeInline(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
}

// noinline - prevents specific parameter from being inlined
inline fun performOperation(
    inline operation: () -> Unit,
    noinline logger: () -> Unit
) {
    operation()
    saveLogger(logger)  // Can store noinline lambda
}

// crossinline - allows inline but prevents non-local returns
inline fun runAsync(crossinline block: () -> Unit) {
    thread {
        block()  // Can't return from outer function
    }
}

Редкость: Средне Сложность: Сложно


5. Объясните Delegation в Kotlin.

Ответ: Delegation (делегирование) позволяет объекту делегировать часть своих обязанностей другому объекту.

  • Class Delegation: Ключевое слово by.
  • Property Delegation: Lazy, observable, delegates.
  • Преимущества: Повторное использование кода, композиция вместо наследования.
// Class delegation
interface Repository {
    fun getData(): String
}

class RemoteRepository : Repository {
    override fun getData() = "Remote data"
}

class CachedRepository(
    private val remote: Repository
) : Repository by remote {
    private var cache: String? = null
    
    override fun getData(): String {
        return cache ?: remote.getData().also { cache = it }
    }
}

// Property delegation
class User {
    // Lazy initialization
    val database by lazy {
        Room.databaseBuilder(context, AppDatabase::class.java, "db").build()
    }
    
    // Observable property
    var name: String by Delegates.observable("Initial") { prop, old, new ->
        println("$old -> $new")
    }
    
    // Custom delegate
    var token: String by PreferenceDelegate("auth_token", "")
}

class PreferenceDelegate(
    private val key: String,
    private val defaultValue: String
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return sharedPreferences.getString(key, defaultValue) ?: defaultValue
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        sharedPreferences.edit().putString(key, value).apply()
    }
}

Редкость: Средне Сложность: Средне


Архитектурные паттерны (6 вопросов)

6. Объясните архитектуру MVVM и ее преимущества.

Ответ: MVVM (Model-View-ViewModel) разделяет логику пользовательского интерфейса от бизнес-логики.

Loading diagram...
  • Model: Слой данных (репозитории, источники данных).
  • View: Слой пользовательского интерфейса (Activities, Fragments, Composables).
  • ViewModel: Логика представления, переживает изменения конфигурации.
  • Преимущества: Тестируемость, разделение ответственности, зависимость от жизненного цикла.
// Model
data class User(val id: Int, val name: String, val email: String)

// Repository
class UserRepository(
    private val api: ApiService,
    private val dao: UserDao
) {
    suspend fun getUser(id: Int): User {
        return dao.getUser(id) ?: api.fetchUser(id).also {
            dao.insertUser(it)
        }
    }
}

// ViewModel
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()
    
    fun loadUser(id: Int) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.getUser(id)
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Error")
            }
        }
    }
}

// View (Fragment)
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.uiState.collect { state ->
                when (state) {
                    is UiState.Loading -> showLoading()
                    is UiState.Success -> showUser(state.data)
                    is UiState.Error -> showError(state.message)
                }
            }
        }
    }
}

Редкость: Очень часто Сложность: Средне


7. Что такое Clean Architecture и как ее реализовать в Android?

Ответ: Clean Architecture (чистая архитектура) разделяет код на слои с четкими зависимостями.

Loading diagram...
  • Presentation: UI, ViewModels.
  • Domain: Use Cases, Business Logic, Entities.
  • Data: Repositories, Data Sources (API, Database).
  • Dependency Rule: Внутренние слои не знают о внешних слоях.
// Domain Layer - Entities
data class User(val id: Int, val name: String, val email: String)

// Domain Layer - Repository Interface
interface UserRepository {
    suspend fun getUser(id: Int): Result<User>
}

// Domain Layer - Use Case
class GetUserUseCase(private val repository: UserRepository) {
    suspend operator fun invoke(id: Int): Result<User> {
        return repository.getUser(id)
    }
}

// Data Layer - Repository Implementation
class UserRepositoryImpl(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource
) : UserRepository {
    override suspend fun getUser(id: Int): Result<User> {
        return try {
            val localUser = localDataSource.getUser(id)
            if (localUser != null) {
                Result.success(localUser)
            } else {
                val remoteUser = remoteDataSource.fetchUser(id)
                localDataSource.saveUser(remoteUser)
                Result.success(remoteUser)
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// Presentation Layer - ViewModel
class UserViewModel(
    private val getUserUseCase: GetUserUseCase
) : ViewModel() {
    fun loadUser(id: Int) {
        viewModelScope.launch {
            when (val result = getUserUseCase(id)) {
                is Result.Success -> handleSuccess(result.data)
                is Result.Failure -> handleError(result.exception)
            }
        }
    }
}

Редкость: Часто Сложность: Сложно


8. Объясните Dependency Injection и Dagger/Hilt.

Ответ: Dependency Injection (внедрение зависимостей) предоставляет зависимости классам вместо того, чтобы создавать их внутри.

  • Преимущества: Тестируемость, слабая связанность, повторное использование.
  • Dagger: Фреймворк DI времени компиляции.
  • Hilt: Упрощенный Dagger для Android.
// Define dependencies with Hilt
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(context, AppDatabase::class.java, "app_db")
            .build()
    }
    
    @Provides
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }
}

// Repository with injected dependencies
@Singleton
class UserRepository @Inject constructor(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    suspend fun getUser(id: Int): User {
        return userDao.getUser(id) ?: apiService.fetchUser(id).also {
            userDao.insertUser(it)
        }
    }
}

// ViewModel with injected repository
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // ViewModel logic
}

// Activity/Fragment
@AndroidEntryPoint
class UserActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()
}

Редкость: Очень часто Сложность: Сложно


9. Что такое Repository pattern и почему его используют?

Ответ: Repository pattern (шаблон репозитория) абстрагирует источники данных, предоставляя чистый API для доступа к данным.

  • Преимущества:
    • Единственный источник истины.
    • Централизованная логика данных.
    • Легко переключать источники данных.
    • Тестируемость.
  • Реализация: Координирует работу между несколькими источниками данных.
interface UserRepository {
    suspend fun getUsers(): Flow<List<User>>
    suspend fun getUser(id: Int): User?
    suspend fun saveUser(user: User)
    suspend fun deleteUser(id: Int)
}

class UserRepositoryImpl @Inject constructor(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource,
    private val cacheDataSource: UserCacheDataSource
) : UserRepository {
    
    override suspend fun getUsers(): Flow<List<User>> = flow {
        // Emit cached data first
        val cachedUsers = cacheDataSource.getUsers()
        if (cachedUsers.isNotEmpty()) {
            emit(cachedUsers)
        }
        
        // Fetch from local database
        val localUsers = localDataSource.getUsers()
        if (localUsers.isNotEmpty()) {
            emit(localUsers)
            cacheDataSource.saveUsers(localUsers)
        }
        
        // Fetch from remote
        try {
            val remoteUsers = remoteDataSource.fetchUsers()
            localDataSource.saveUsers(remoteUsers)
            cacheDataSource.saveUsers(remoteUsers)
            emit(remoteUsers)
        } catch (e: Exception) {
            // If remote fails, we already emitted cached/local data
        }
    }
    
    override suspend fun getUser(id: Int): User? {
        return cacheDataSource.getUser(id)
            ?: localDataSource.getUser(id)
            ?: remoteDataSource.fetchUser(id)?.also {
                localDataSource.saveUser(it)
                cacheDataSource.saveUser(it)
            }
    }
}

Редкость: Очень часто Сложность: Средне


10. Объясните Single Activity architecture.

Ответ: Single Activity architecture (архитектура одного Activity) использует один Activity с несколькими Fragments, управляемыми Navigation Component.

  • Преимущества:
    • Упрощенная навигация.
    • Общие ViewModels между фрагментами.
    • Лучшие анимации.
    • Более легкая глубокая линковка.
  • Navigation Component: Обрабатывает транзакции фрагментов, стек возврата, аргументы.
// Navigation graph (nav_graph.xml)
<navigation>
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.app.HomeFragment">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>
    
    <fragment
        android:id="@+id/detailFragment"
        android:name="com.app.DetailFragment">
        <argument
            android:name="userId"
            app:argType="integer" />
    </fragment>
</navigation>

// MainActivity - single activity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val navController = findNavController(R.id.nav_host_fragment)
        setupActionBarWithNavController(navController)
    }
}

// HomeFragment - navigate to detail
class HomeFragment : Fragment() {
    fun navigateToDetail(userId: Int) {
        val action = HomeFragmentDirections.actionHomeToDetail(userId)
        findNavController().navigate(action)
    }
}

// DetailFragment - receive arguments
class DetailFragment : Fragment() {
    private val args: DetailFragmentArgs by navArgs()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val userId = args.userId
        viewModel.loadUser(userId)
    }
}

Редкость: Часто Сложность: Средне


11. Что такое MVI (Model-View-Intent) architecture?

Ответ: MVI - это архитектура с однонаправленным потоком данных, вдохновленная Redux.

  • Компоненты:
    • Model: Представляет состояние UI.
    • View: Отображает состояние, выдает intents.
    • Intent: Действия/события пользователя.
  • Преимущества: Предсказуемое состояние, более легкая отладка, отладка с перемещением во времени.
// State
data class UserScreenState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// Intent (user actions)
sealed class UserIntent {
    data class LoadUser(val id: Int) : UserIntent()
    object RetryLoading : UserIntent()
}

// ViewModel
class UserViewModel : ViewModel() {
    private val _state = MutableStateFlow(UserScreenState())
    val state: StateFlow<UserScreenState> = _state.asStateFlow()
    
    fun processIntent(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.id)
            is UserIntent.RetryLoading -> retryLoading()
        }
    }
    
    private fun loadUser(id: Int) {
        viewModelScope.launch {
            _state.value = _state.value.copy(isLoading = true, error = null)
            try {
                val user = repository.getUser(id)
                _state.value = _state.value.copy(
                    isLoading = false,
                    user = user
                )
            } catch (e: Exception) {
                _state.value = _state.value.copy(
                    isLoading = false,
                    error = e.message
                )
            }
        }
    }
}

// View
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // Send intent
        viewModel.processIntent(UserIntent.LoadUser(123))
        
        // Observe state
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.state.collect { state ->
                render(state)
            }
        }
    }
    
    private fun render(state: UserScreenState) {
        when {
            state.isLoading -> showLoading()
            state.error != null -> showError(state.error)
            state.user != null -> showUser(state.user)
        }
    }
}

Редкость: Средне Сложность: Сложно


Производительность и оптимизация (5 вопросов)

12. Как оптимизировать производительность RecyclerView?

Ответ: Несколько стратегий улучшают производительность прокрутки RecyclerView:

  1. ViewHolder Pattern: Повторное использование представлений (встроено).
  2. DiffUtil: Эффективное обновление списка.
  3. Stable IDs: Переопределите getItemId() и setHasStableIds(true).
  4. Prefetching: Увеличьте расстояние предварительной выборки.
  5. Image Loading: Используйте библиотеки, такие как Glide/Coil, с правильным размером.
  6. Avoid Heavy Operations: Не выполняйте дорогостоящие вычисления в onBindViewHolder.
  7. Nested RecyclerViews: Установите setRecycledViewPool() и setHasFixedSize(true).
class UserAdapter : ListAdapter<User, UserViewHolder>(UserDiffCallback()) {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = ItemUserBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return UserViewHolder(binding)
    }
    
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
    
    override fun getItemId(position: Int): Long {
        return getItem(position).id.toLong()
    }
}

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }
    
    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }
}

// In Fragment/Activity
recyclerView.apply {
    setHasFixedSize(true)
    adapter = userAdapter
    // Increase prefetch
    (layoutManager as? LinearLayoutManager)?.initialPrefetchItemCount = 4
}

// Update list efficiently
userAdapter.submitList(newUsers)

Редкость: Очень часто Сложность: Средне


13. Как обнаружить и исправить утечки памяти в Android?

Ответ: Утечки памяти возникают, когда объекты удерживаются в памяти дольше, чем необходимо.

  • Общие причины:
    • Утечки контекста (ссылки на Activity/Fragment).
    • Статические ссылки.
    • Анонимные внутренние классы.
    • Неотмененные слушатели.
    • Неотмененные корутины.
  • Инструменты обнаружения:
    • Библиотека LeakCanary.
    • Android Studio Memory Profiler.
    • Heap dumps.
// BAD - Memory leak
class MyActivity : AppCompatActivity() {
    companion object {
        var instance: MyActivity? = null  // Static reference leaks
    }
    
    private val handler = Handler()  // Implicit reference to Activity
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        instance = this
        
        // Anonymous class holds Activity reference
        handler.postDelayed({
            // Activity might be destroyed
        }, 10000)
    }
}

// GOOD - No memory leak
class MyActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Use weak reference if needed
        val weakRef = WeakReference(this)
        handler.postDelayed({
            weakRef.get()?.let { activity ->
                // Safe to use activity
            }
        }, 10000)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)  // Clean up
    }
}

// ViewModel - survives configuration changes
class MyViewModel : ViewModel() {
    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.Main + job)
    
    override fun onCleared() {
        super.onCleared()
        job.cancel()  // Cancel coroutines
    }
}

Редкость: Очень часто Сложность: Средне


14. Как оптимизировать время запуска приложения?

Ответ: Более быстрый запуск улучшает пользовательский опыт:

  1. Lazy Initialization: Инициализируйте объекты только при необходимости.
  2. Avoid Heavy Work in Application.onCreate():
    • Переместите в фоновый поток.
    • Отложите некритическую инициализацию.
  3. Content Providers: Минимизируйте или загружайте лениво.
  4. Reduce Dependencies: Меньше библиотек = более быстрый запуск.
  5. App Startup Library: Структурированная инициализация.
  6. Baseline Profiles: Подсказки для предварительной компиляции.
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Critical initialization only
        initCrashReporting()
        
        // Defer non-critical work
        lifecycleScope.launch {
            delay(100)
            initAnalytics()
            initImageLoader()
        }
        
        // Background initialization
        CoroutineScope(Dispatchers.IO).launch {
            preloadData()
        }
    }
}

// App Startup library
class AnalyticsInitializer : Initializer<Analytics> {
    override fun create(context: Context): Analytics {
        return Analytics.getInstance(context).apply {
            initialize()
        }
    }
    
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

// Lazy initialization
class MyActivity : AppCompatActivity() {
    private val database by lazy {
        Room.databaseBuilder(this, AppDatabase::class.java, "db").build()
    }
    
    // Only created when accessed
    private val heavyObject by lazy {
        createHeavyObject()
    }
}

Редкость: Часто Сложность: Средне


15. Как эффективно обрабатывать загрузку и кэширование растровых изображений?

Ответ: Эффективная обработка изображений имеет решающее значение для производительности:

  • Libraries: Glide, Coil (handle caching automatically).
  • Manual Optimization:
    • Downsampling (load smaller images).
    • Memory cache (LruCache).
    • Disk cache.
    • Bitmap pooling.
// Using Glide
Glide.with(context)
    .load(imageUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .override(width, height)  // Resize
    .into(imageView)

// Manual implementation with LruCache
class ImageCache {
    private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
    private val cacheSize = maxMemory / 8
    
    private val memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
        override fun sizeOf(key: String, bitmap: Bitmap): Int {
            return bitmap.byteCount / 1024
        }
    }
    
    fun getBitmap(key: String): Bitmap? {
        return memoryCache.get(key)
    }
    
    fun putBitmap(key: String, bitmap: Bitmap) {
        if (getBitmap(key) == null) {
            memoryCache.put(key, bitmap)
        }
    }
}

// Downsample large images
fun decodeSampledBitmap(file: File, reqWidth: Int, reqHeight: Int): Bitmap {
    return BitmapFactory.Options().run {
        inJustDecodeBounds = true
        BitmapFactory.decodeFile(file.path, this)
        
        inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
        inJustDecodeBounds = false
        
        BitmapFactory.decodeFile(file.path, this)
    }
}

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1
    
    if (height > reqHeight || width > reqWidth) {
        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2
        
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    
    return inSampleSize
}

Редкость: Часто Сложность: Сложно


16. Что такое ANR и как его предотвратить?

Ответ: ANR (Application Not Responding) возникает, когда основной поток заблокирован слишком долго.

  • Причины:
    • Тяжелые вычисления в основном потоке.
    • Сетевые вызовы в основном потоке.
    • Операции с базой данных в основном потоке.
    • Deadlocks.
  • Предотвращение:
    • Переместите тяжелую работу в фоновые потоки.
    • Используйте корутины с правильными диспетчерами.
    • Избегайте synchronized блоки на основном потоке.
    • Используйте WorkManager для фоновых задач.
// BAD - Blocks main thread
class BadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ANR risk!
        val data = fetchDataFromNetwork()  // Blocks UI thread
        processLargeFile()  // Blocks UI thread
    }
}

// GOOD - Background processing
class GoodActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        viewModel.loadData()  // Handled in ViewModel with coroutines
    }
}

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // Automatically on Main dispatcher
            showLoading()
            
            // Switch to IO dispatcher for network
            val data = withContext(Dispatchers.IO) {
                fetchDataFromNetwork()
            }
            
            // Switch to Default dispatcher for heavy computation
            val processed = withContext(Dispatchers.Default) {
                processData(data)
            }
            
            // Back to Main for UI update
            updateUI(processed)
        }
    }
}

Редкость: Часто Сложность: Легко


Тестирование (3 вопроса)

17. Как писать unit tests для ViewModels?

Ответ: ViewModels следует тестировать изолированно с помощью mock-зависимостей.

class
Newsletter subscription

Еженедельные советы по карьере, которые действительно работают

Получайте последние идеи прямо на вашу почту

Похожие посты

Decorative doodle

Перестаньте откликаться. Начните получать предложения.

Превратите своё резюме в магнит для собеседований с оптимизацией на базе ИИ, которой доверяют соискатели по всему миру.

Начать бесплатно

Поделиться этим постом

Устройтесь на Работу на 50% Быстрее

Соискатели, использующие профессиональные резюме с улучшением ИИ, находят работу в среднем за 5 недель по сравнению со стандартными 10. Перестаньте ждать и начните проходить собеседования.