十二月 21, 2025
54 分钟阅读

高级 Android 开发者面试题:Kotlin、Compose 与架构

interview
career-advice
job-search
高级 Android 开发者面试题:Kotlin、Compose 与架构
Milad Bonakdar

Milad Bonakdar

作者

围绕 Kotlin 协程、Jetpack Compose 状态、应用架构、性能、测试、离线优先数据和安全,准备高级 Android 开发者面试。


引言

高级 Android 开发者面试通常会考察架构取舍、生命周期安全的并发、Compose 状态归属、离线优先的数据流、性能诊断、测试策略和安全。好的回答不只是说会用哪个 API,还要说明它为什么符合产品约束,以及在生产环境中如何排查问题。

你可以把这份指南当作实战清单。优先准备 Kotlin 协程与 Flow、ViewModel 与状态管理、基于 Room 的 repository、Compose 与旧 UI 的取舍、启动和内存性能,以及你能从自己项目中讲清楚的例子。

如何使用这些问题

  • 把每个回答练成一个简短决策:背景、取舍、实现、失败模式。
  • 将技术选择连接到用户影响,例如更快启动、可靠离线读取、更安全的 token 存储或更少生命周期 bug。
  • 为架构、性能、测试和安全各准备一个真实项目故事。

高级 Kotlin & 语言特性 (5 题)

1. 解释 Kotlin 协程及其相对于线程的优势。

答案: 协程是轻量级的并发原语,允许以顺序方式编写异步代码。

  • 相对于线程的优势:
    • 轻量级: 可以创建数千个协程而不会出现性能问题
    • 结构化并发: 父子关系确保正确的清理
    • 取消支持: 内置取消传播
    • 异常处理: 结构化异常处理
  • 关键组件:
    • 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. 什么是密封类?应该在何时使用它们?

答案: 密封类表示受限的类层次结构,其中所有子类在编译时都是已知的。

  • 优点:
    • 详尽的 when 表达式
    • 类型安全的状态管理
    • 比枚举更适合复杂数据
  • 用例: 表示状态、结果、导航事件
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")
            }
        }
    }
}

// 在 UI 中
when (val state = uiState.value) {
    is UiState.Loading -> showLoading()
    is UiState.Success -> showData(state.data)
    is UiState.Error -> showError(state.message)
    // 编译器确保所有情况都被处理
}

稀有度: 常见 难度: 中等


3. 解释 Kotlin Flow 及其与 LiveData 的区别。

答案: Flow 是 Kotlin 的冷异步流,它按顺序发出值。

  • Flow vs LiveData:
    • Flow: 冷流,支持操作符,不感知生命周期,更灵活
    • LiveData: 热流,感知生命周期,Android 特有,对于 UI 更简单
  • Flow 类型:
    • Flow: 冷流(在收集时启动)
    • StateFlow: 热流,持有当前状态
    • SharedFlow: 热流,用于事件
class UserRepository {
    // 冷 Flow - 在收集时启动
    fun getUsers(): Flow<List<User>> = flow {
        val users = api.fetchUsers()
        emit(users)
    }.flowOn(Dispatchers.IO)
    
    // StateFlow - 热,持有状态
    private val _userState = MutableStateFlow<List<User>>(emptyList())
    val userState: StateFlow<List<User>> = _userState.asStateFlow()
    
    // SharedFlow - 热,用于事件
    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()
        )
}

// 在 Activity/Fragment 中收集
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.users.collect { users ->
            updateUI(users)
        }
    }
}

稀有度: 非常常见 难度: 困难


4. 什么是内联函数?应该在何时使用它们?

答案: 内联函数将函数体复制到调用点,避免了函数调用开销。

  • 优点:
    • 消除 lambda 分配开销
    • 允许从 lambda 进行非局部返回
    • 对于高阶函数有更好的性能
  • 用例: 带有 lambda 参数的高阶函数
  • 权衡: 增加代码大小
// 没有内联 - 创建 lambda 对象
fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
}

// 有内联 - 没有创建 lambda 对象
inline fun measureTimeInline(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
}

// noinline - 阻止特定参数被内联
inline fun performOperation(
    inline operation: () -> Unit,
    noinline logger: () -> Unit
) {
    operation()
    saveLogger(logger)  // 可以存储 noinline lambda
}

// crossinline - 允许内联,但阻止非局部返回
inline fun runAsync(crossinline block: () -> Unit) {
    thread {
        block()  // 不能从外部函数返回
    }
}

稀有度: 中等 难度: 困难


5. 解释 Kotlin 中的委托。

答案: 委托允许对象将其某些职责委托给另一个对象。

  • 类委托: by 关键字
  • 属性委托: Lazy, observable, delegates
  • 优点: 代码重用,组合优于继承
// 类委托
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 }
    }
}

// 属性委托
class User {
    // 延迟初始化
    val database by lazy {
        Room.databaseBuilder(context, AppDatabase::class.java, "db").build()
    }
    
    // 可观察属性
    var name: String by Delegates.observable("Initial") { prop, old, new ->
        println("$old -> $new")
    }
    
    // 自定义委托
    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) 将 UI 逻辑与业务逻辑分离。

Loading diagram...
  • Model: 数据层(repositories, data sources)
  • View: UI 层(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. 什么是整洁架构?如何在 Android 中实现它?

答案: 整洁架构将代码分离成具有清晰依赖关系的不同层。

Loading diagram...
  • Presentation: UI, ViewModels
  • Domain: Use Cases, Business Logic, Entities
  • Data: Repositories, Data Sources (API, Database)
  • 依赖规则: 内部层不知道外部层
// 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. 解释依赖注入和 Dagger/Hilt。

答案: 依赖注入为类提供依赖项,而不是在内部创建它们。

  • 优点: 可测试性,松耦合,可重用性
  • Dagger: 编译时 DI 框架
  • Hilt: 简化了 Android 的 Dagger
// 使用 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
@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)
        }
    }
}

// 带有注入 repository 的 ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // ViewModel 逻辑
}

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

稀有度: 非常常见 难度: 困难


9. 什么是 Repository 模式?为什么要使用它?

答案: Repository 模式抽象了数据源,为数据访问提供了一个干净的 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 {
        // 首先发出缓存数据
        val cachedUsers = cacheDataSource.getUsers()
        if (cachedUsers.isNotEmpty()) {
            emit(cachedUsers)
        }
        
        // 从本地数据库获取
        val localUsers = localDataSource.getUsers()
        if (localUsers.isNotEmpty()) {
            emit(localUsers)
            cacheDataSource.saveUsers(localUsers)
        }
        
        // 从远程获取
        try {
            val remoteUsers = remoteDataSource.fetchUsers()
            localDataSource.saveUsers(remoteUsers)
            cacheDataSource.saveUsers(remoteUsers)
            emit(remoteUsers)
        } catch (e: Exception) {
            // 如果远程失败,我们已经发出了缓存/本地数据
        }
    }
    
    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. 解释单 Activity 架构。

答案: 单 Activity 架构使用一个 Activity 搭配多个 Fragment,由 Navigation Component 管理。

  • 优点:
    • 简化导航
    • Fragment 之间共享 ViewModel
    • 更好的动画
    • 更容易的深度链接
  • Navigation Component: 处理 fragment 事务、返回栈、参数
// 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 - 单个 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 - 导航到 detail
class HomeFragment : Fragment() {
    fun navigateToDetail(userId: Int) {
        val action = HomeFragmentDirections.actionHomeToDetail(userId)
        findNavController().navigate(action)
    }
}

// DetailFragment - 接收参数
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) 架构?

答案: MVI 是一种受 Redux 启发的单向数据流架构。

  • 组件:
    • Model: 表示 UI 状态
    • View: 渲染状态,发出 intents
    • Intent: 用户操作/事件
  • 优点: 可预测状态,更容易调试,时间旅行调试
// State
data class UserScreenState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// Intent (用户操作)
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)
        
        // 发送 intent
        viewModel.processIntent(UserIntent.LoadUser(123))
        
        // 观察状态
        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 模式: 重用视图(内置)
  2. DiffUtil: 高效的列表更新
  3. Stable IDs: 覆盖 getItemId() 并设置 setHasStableIds(true)
  4. 预取: 增加预取距离
  5. 图片加载: 使用 Glide/Coil 等库,并进行适当的大小调整
  6. 避免繁重操作: 不要在 onBindViewHolder 中执行昂贵的计算
  7. 嵌套 RecyclerView: 设置 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
    }
}

// 在 Fragment/Activity 中
recyclerView.apply {
    setHasFixedSize(true)
    adapter = userAdapter
    // 增加预取
    (layoutManager as? LinearLayoutManager)?.initialPrefetchItemCount = 4
}

// 高效地更新列表
userAdapter.submitList(newUsers)

稀有度: 非常常见 难度: 中等


13. 如何检测和修复 Android 中的内存泄漏?

答案: 当对象在内存中保留的时间超过需要的时间时,就会发生内存泄漏。

  • 常见原因:
    • Context 泄漏(Activity/Fragment 引用)
    • 静态引用
    • 匿名内部类
    • 监听器未注销
    • 协程未取消
  • 检测工具:
    • LeakCanary 库
    • Android Studio Memory Profiler
    • Heap dumps
// 错误 - 内存泄漏
class MyActivity : AppCompatActivity() {
    companion object {
        var instance: MyActivity? = null  // 静态引用泄漏
    }
    
    private val handler = Handler()  // 隐式引用 Activity
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        instance = this
        
        // 匿名类持有 Activity 引用
        handler.postDelayed({
            // Activity 可能已被销毁
        }, 10000)
    }
}

// 正确 - 没有内存泄漏
class MyActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 如果需要,使用弱引用
        val weakRef = WeakReference(this)
        handler.postDelayed({
            weakRef.get()?.let { activity ->
                // 可以安全地使用 activity
            }
        }, 10000)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)  // 清理
    }
}

// ViewModel - 在配置更改后依然存在
class MyViewModel : ViewModel() {
    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.Main + job)
    
    override fun onCleared() {
        super.onCleared()
        job.cancel()  // 取消协程
    }
}

稀有度: 非常常见 难度: 中等


14. 如何优化应用启动时间?

答案: 更快的启动速度可以改善用户体验:

  1. 延迟初始化: 仅在需要时才初始化对象
  2. 避免在 Application.onCreate() 中进行繁重的工作:
    • 移动到后台线程
    • 延迟非关键初始化
  3. Content Providers: 最小化或延迟加载
  4. 减少依赖项: 库越少 = 启动速度越快
  5. App Startup Library: 结构化初始化
  6. Baseline Profiles: 提前编译提示
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 仅进行关键初始化
        initCrashReporting()
        
        // 延迟非关键工作
        lifecycleScope.launch {
            delay(100)
            initAnalytics()
            initImageLoader()
        }
        
        // 后台初始化
        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()
    }
}

// 延迟初始化
class MyActivity : AppCompatActivity() {
    private val database by lazy {
        Room.databaseBuilder(this, AppDatabase::class.java, "db").build()
    }
    
    // 仅在访问时创建
    private val heavyObject by lazy {
        createHeavyObject()
    }
}

稀有度: 常见 难度: 中等


15. 如何有效地处理位图加载和缓存?

答案: 高效的图像处理对于性能至关重要:

  • 库: Glide, Coil (自动处理缓存)
  • 手动优化:
    • 缩减采样(加载较小的图像)
    • 内存缓存 (LruCache)
    • 磁盘缓存
    • 位图池
// 使用 Glide
Glide.with(context)
    .load(imageUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .override(width, height)  // 调整大小
    .into(imageView)

// 使用 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)
        }
    }
}

// 缩减采样大型图像
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,应用程序无响应)发生在主线程被阻塞太长时间时。

  • 原因:
    • 在主线程上进行繁重的计算
    • 在主线程上进行网络调用
    • 在主线程上进行数据库操作
    • 死锁
  • 预防:
    • 将繁重的工作移动到后台线程
    • 使用带有适当 dispatchers 的协程
    • 避免在主线程上使用 synchronized 块
    • 使用 WorkManager 进行后台任务
// 错误 - 阻塞主线程
class BadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ANR 风险!
        val data = fetchDataFromNetwork()  // 阻塞 UI 线程
        processLargeFile()  // 阻塞 UI 线程
    }
}

// 正确 - 后台处理
class GoodActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        viewModel.loadData()  // 在 ViewModel 中使用协程处理
    }
}

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // 自动在 Main dispatcher 上
            showLoading()
            
            // 切换到 IO dispatcher 进行网络操作
            val data = withContext(Dispatchers.IO) {
                fetchDataFromNetwork()
            }
            
            // 切换到 Default dispatcher 进行繁重的计算
            val processed = withContext(Dispatchers.Default) {
                processData(data)
            }
            
            // 返回到 Main 进行 UI 更新
            updateUI(processed)
        }
    }
}

稀有度: 常见 难度: 简单


测试 (3 题)

17. 如何为 ViewModels 编写单元测试?

答案: 应该使用模拟的依赖项隔离地测试 ViewModels。

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)
    }
    
    @Test
    fun `loadUser failure updates error state`() = runTest {
        // Given
        val exception = Exception("Network error")
        coEvery { repository.getUser(1) } returns Result.failure(exception)
        
        // When
        viewModel.loadUser(1)
        
        // Then
        val state = viewModel.uiState.value
        assertTrue(state is UiState.Error)
        assertEquals("Network error", (state as UiState.Error).message)
    }
}

// MainDispatcherRule 用于测试协程
@ExperimentalCoroutinesApi
class MainDispatcherRule : TestWatcher() {
    private val testDispatcher = StandardTestDispatcher()
    
    override fun starting(description: Description) {
        Dispatchers
Newsletter subscription

真正有效的每周职业建议

将最新见解直接发送到您的收件箱

停止申请,开始被录用

使用全球求职者信赖的AI驱动优化,将您的简历转变为面试磁铁。

免费开始

分享这篇文章

将简历撰写时间减少90%

普通求职者需要花费3小时以上来格式化简历。我们的AI在15分钟内完成,让您以12倍的速度进入申请阶段。