dezembro 21, 2025
18 min de leitura

Perguntas de Entrevista para Desenvolvedor Mobile Sênior (Android): Guia Completo

interview
career-advice
job-search
Perguntas de Entrevista para Desenvolvedor Mobile Sênior (Android): Guia Completo
Milad Bonakdar

Milad Bonakdar

Autor

Domine o desenvolvimento Android avançado com perguntas de entrevista essenciais, abrangendo padrões de arquitetura, otimização de desempenho, injeção de dependência, testes, segurança e design de sistema para desenvolvedores seniores.


Introdução

Espera-se que os desenvolvedores Android seniores arquitetem aplicativos escaláveis e de fácil manutenção, garantindo alto desempenho e qualidade de código. Essa função exige profundo conhecimento das estruturas do Android, padrões arquiteturais, injeção de dependência, estratégias de teste e a capacidade de tomar decisões técnicas informadas.

Este guia abrangente aborda as principais perguntas de entrevista para desenvolvedores Android seniores, abrangendo conceitos avançados de Kotlin, padrões arquiteturais, otimização de desempenho, injeção de dependência, testes e design de sistema. Cada pergunta inclui respostas detalhadas, avaliação de raridade e níveis de dificuldade.


Kotlin Avançado e Recursos da Linguagem (5 Perguntas)

1. Explique as Coroutines do Kotlin e suas vantagens sobre as threads.

Resposta: Coroutines são primitivas de concorrência leves que permitem escrever código assíncrono de maneira sequencial.

  • Vantagens sobre Threads:
    • Leves: É possível criar milhares de coroutines sem problemas de desempenho
    • Concorrência Estruturada: O relacionamento pai-filho garante uma limpeza adequada
    • Suporte ao Cancelamento: Propagação de cancelamento integrada
    • Tratamento de Exceções: Tratamento de exceções estruturado
  • Componentes Principais:
    • CoroutineScope: Define o ciclo de vida
    • Dispatchers: Controla o contexto de execução (Main, IO, Default)
    • suspend functions: Podem ser pausadas e retomadas
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)
            }
        }
    }
}

Raridade: Muito Comum Dificuldade: Difícil


2. O que são Sealed Classes e quando você deve usá-las?

Resposta: Sealed classes representam hierarquias de classes restritas onde todas as subclasses são conhecidas em tempo de compilação.

  • Benefícios:
    • Expressões when exaustivas
    • Gerenciamento de estado com segurança de tipo
    • Melhor que enums para dados complexos
  • Casos de Uso: Representação de estados, resultados, eventos de navegação
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
}

Raridade: Comum Dificuldade: Média


3. Explique o Kotlin Flow e como ele difere do LiveData.

Resposta: Flow é o stream assíncrono frio do Kotlin que emite valores sequencialmente.

  • Flow vs LiveData:
    • Flow: Stream frio, suporta operadores, não tem reconhecimento do ciclo de vida, mais flexível
    • LiveData: Stream quente, tem reconhecimento do ciclo de vida, específico do Android, mais simples para a UI
  • Tipos de Flow:
    • Flow: Stream frio (inicia na coleta)
    • StateFlow: Stream quente com estado atual
    • SharedFlow: Stream quente para eventos
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)
        }
    }
}

Raridade: Muito Comum Dificuldade: Difícil


4. O que são Inline Functions e quando você deve usá-las?

Resposta: Inline functions copiam o corpo da função para o local da chamada, evitando a sobrecarga da chamada da função.

  • Benefícios:
    • Elimina a sobrecarga de alocação de lambda
    • Permite retornos não locais de lambdas
    • Melhor desempenho para funções de ordem superior
  • Casos de Uso: Funções de ordem superior com parâmetros lambda
  • Compromisso: Aumenta o tamanho do código
// 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
    }
}

Raridade: Média Dificuldade: Difícil


5. Explique a Delegação no Kotlin.

Resposta: A delegação permite que um objeto delegue algumas de suas responsabilidades a outro objeto.

  • Delegação de Classe: palavra-chave by
  • Delegação de Propriedade: Lazy, observable, delegates
  • Benefícios: Reutilização de código, composição em vez de herança
// 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()
    }
}

Raridade: Média Dificuldade: Média


Padrões de Arquitetura (6 Perguntas)

6. Explique a arquitetura MVVM e seus benefícios.

Resposta: MVVM (Model-View-ViewModel) separa a lógica da UI da lógica de negócios.

Loading diagram...
  • Model: Camada de dados (repositórios, fontes de dados)
  • View: Camada de UI (Activities, Fragments, Composables)
  • ViewModel: Lógica de apresentação, sobrevive a mudanças de configuração
  • Benefícios: Testável, separação de preocupações, reconhecimento do ciclo de vida
// 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)
                }
            }
        }
    }
}

Raridade: Muito Comum Dificuldade: Média


7. O que é Clean Architecture e como você a implementa no Android?

Resposta: Clean Architecture separa o código em camadas com dependências claras.

Loading diagram...
  • Presentation: UI, ViewModels
  • Domain: Use Cases, Business Logic, Entities
  • Data: Repositories, Data Sources (API, Database)
  • Dependency Rule: Camadas internas não conhecem as camadas externas
// 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)
            }
        }
    }
}

Raridade: Comum Dificuldade: Difícil


8. Explique a Injeção de Dependência e Dagger/Hilt.

Resposta: A Injeção de Dependência fornece dependências para as classes em vez de criá-las internamente.

  • Benefícios: Testabilidade, baixo acoplamento, reutilização
  • Dagger: Framework de DI em tempo de compilação
  • Hilt: Dagger simplificado para 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()
}

Raridade: Muito Comum Dificuldade: Difícil


9. O que é o padrão Repository e por que usá-lo?

Resposta: O padrão Repository abstrai as fontes de dados, fornecendo uma API limpa para acesso aos dados.

  • Benefícios:
    • Única fonte de verdade
    • Lógica de dados centralizada
    • Fácil de trocar as fontes de dados
    • Testável
  • Implementação: Coordena entre várias fontes de dados
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)
            }
    }
}

Raridade: Muito Comum Dificuldade: Média


10. Explique a arquitetura Single Activity.

Resposta: A arquitetura Single Activity usa uma Activity com vários Fragments, gerenciados pelo Navigation Component.

  • Benefícios:
    • Navegação simplificada
    • ViewModels compartilhados entre fragments
    • Melhores animações
    • Deep linking mais fácil
  • Navigation Component: Lida com transações de fragment, pilha de retorno, argumentos
// 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)
    }
}

Raridade: Comum Dificuldade: Média


11. O que é a arquitetura MVI (Model-View-Intent)?

Resposta: MVI é uma arquitetura de fluxo de dados unidirecional inspirada no Redux.

  • Componentes:
    • Model: Representa o estado da UI
    • View: Renderiza o estado, emite intents
    • Intent: Ações/eventos do usuário
  • Benefícios: Estado previsível, depuração mais fácil, depuração com viagem no tempo
// 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)
        }
    }
}

Raridade: Média Dificuldade: Difícil


Desempenho e Otimização (5 Perguntas)

12. Como você otimiza o desempenho do RecyclerView?

Resposta: Várias estratégias melhoram o desempenho de rolagem do RecyclerView:

  1. ViewHolder Pattern: Reutiliza views (integrado)
  2. DiffUtil: Atualizações de lista eficientes
  3. Stable IDs: Substitua getItemId() e setHasStableIds(true)
  4. Prefetching: Aumente a distância de prefetch
  5. Image Loading: Use bibliotecas como Glide/Coil com dimensionamento adequado
  6. Avoid Heavy Operations: Não execute cálculos dispendiosos em onBindViewHolder
  7. Nested RecyclerViews: Defina setRecycledViewPool() e 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)

Raridade: Muito Comum Dificuldade: Média


13. Como você detecta e corrige vazamentos de memória no Android?

Resposta: Os vazamentos de memória ocorrem quando os objetos são mantidos na memória por mais tempo do que o necessário.

  • Causas Comuns:
    • Vazamentos de contexto (referências de Activity/Fragment)
    • Referências estáticas
    • Classes internas anônimas
    • Listeners não removidos
    • Coroutines não canceladas
  • Ferramentas de Detecção:
    • Biblioteca 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
    }
}

Raridade: Muito Comum Dificuldade: Média


14. Como você otimiza o tempo de inicialização do aplicativo?

Resposta: Uma inicialização mais rápida melhora a experiência do usuário:

  1. Inicialização Lazy: Inicialize objetos somente quando necessário
  2. Evite Trabalho Pesado em Application.onCreate():
    • Mova para uma thread em segundo plano
    • Adie a inicialização não crítica
  3. Content Providers: Minimize ou carregue de forma lazy
  4. Reduza as Dependências: Menos bibliotecas = inicialização mais rápida
  5. App Startup Library: Inicialização estruturada
  6. Baseline Profiles: Dicas de compilação ahead-of-time
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()
    }
}

Raridade: Comum Dificuldade: Média


15. Como você lida com o carregamento e o cache de bitmaps de forma eficiente?

Resposta: O tratamento eficiente de imagens é crucial para o desempenho:

  • Bibliotecas: Glide, Coil (lidam com o cache automaticamente)
  • Otimização Manual:
    • Downsampling (carregar imagens menores)
    • Cache de memória (LruCache)
    • Cache de disco
    • 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
}

Raridade: Comum Dificuldade: Difícil


16. O que é ANR e como você o impede?

Resposta: ANR (Application Not Responding) ocorre quando a thread principal é bloqueada por muito tempo.

  • Causas:
    • Computação pesada na thread principal
    • Chamadas de rede na thread principal
    • Operações de banco de dados na thread principal
    • Deadlocks
  • Prevenção:
    • Mova o trabalho pesado para threads em segundo plano
    • Use coroutines com dispatchers adequados
    • Evite blocos synchronized na thread principal
    • Use WorkManager para tarefas em segundo plano
// 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)
        }
    }
}

Raridade: Comum Dificuldade: Fácil


Testes (3 Perguntas)

17. Como você escreve testes unitários para ViewModels?

Resposta: Os ViewModels devem ser testados isoladamente com dependências mockadas.

class UserViewModelTest {
    @get:Rule
    val instantExecutorRule = InstantTaskExecutorRule()
    
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()
    
    private lateinit var viewModel: UserViewModel
    private lateinit var repository: UserRepository
    
    @Before
    fun setup() {
        repository = mockk()
        viewModel = UserViewModel(repository)
    }
    
    @Test
    fun `loadUser success updates state`() = runTest {
        // Given
        val user = User(1, "John", "[email protected]")
        coEvery { repository.getUser(1) } returns Result.success(user)
        
        // When
        viewModel.loadUser(1)
        
        // Then
        val state = viewModel.uiState.value
        assertTrue(state is UiState.Success)
        assertEquals(user, (state as UiState.Success).data)
    }
Newsletter subscription

Dicas de carreira semanais que realmente funcionam

Receba as últimas ideias diretamente na sua caixa de entrada

Destaque-se para Recrutadores e Conquiste o Emprego dos Seus Sonhos

Junte-se a milhares que transformaram suas carreiras com currículos impulsionados por IA que passam no ATS e impressionam gerentes de contratação.

Comece a criar agora

Compartilhar esta publicação

Duplique Seus Retornos de Entrevista

Candidatos que adaptam seus currículos à descrição da vaga obtêm 2,5 vezes mais entrevistas. Use nossa IA para personalizar automaticamente seu CV para cada candidatura instantaneamente.