MST

星途 面试题库

面试题:Kotlin Android ViewModel架构下的单元测试与性能优化

在使用Kotlin进行Android开发且基于ViewModel架构的项目中,如何针对ViewModel编写高效的单元测试?当应用中存在大量ViewModel时,怎样进行性能优化以避免卡顿,如资源管理、数据加载策略等方面,给出详细的思路和方案。
33.5万 热度难度
编程语言Kotlin

知识考点

AI 面试

面试题答案

一键面试

针对ViewModel编写高效单元测试

  1. 使用Mockito库
    • 引入Mockito依赖:在build.gradle文件中添加testImplementation 'org.mockito:mockito - core:4.11.0'(版本号可根据实际情况调整)。
    • 示例:假设ViewModel中有一个依赖的仓库接口UserRepository,可以这样模拟:
    import org.junit.Test
    import org.mockito.Mockito
    import org.mockito.Mockito.mock
    import org.mockito.Mockito.verify
    import androidx.arch.core.executor.testing.InstantTaskExecutorRule
    import androidx.lifecycle.SavedStateHandle
    import com.example.app.viewmodel.UserViewModel
    import com.example.app.repository.UserRepository
    import kotlinx.coroutines.ExperimentalCoroutinesApi
    import kotlinx.coroutines.test.runTest
    
    @ExperimentalCoroutinesApi
    class UserViewModelTest {
        @get:Rule
        val instantExecutorRule = InstantTaskExecutorRule()
    
        @Test
        fun `test fetchUser`() = runTest {
            val mockRepository = mock(UserRepository::class.java)
            val savedStateHandle = SavedStateHandle()
            val viewModel = UserViewModel(mockRepository, savedStateHandle)
            viewModel.fetchUser()
            verify(mockRepository).fetchUser()
        }
    }
    
  2. 使用JUnit 5
    • 引入JUnit 5依赖:testImplementation 'org.junit.jupiter:junit - jupiter:5.9.2'
    • 使用@Test注解标记测试方法,如上述示例中的test fetchUser方法。
  3. 测试LiveData
    • 使用getValue()方法获取LiveData的值。例如,假设ViewModel中有一个LiveData对象userLiveData
    @Test
    fun `test userLiveData`() = runTest {
        val mockRepository = mock(UserRepository::class.java)
        val savedStateHandle = SavedStateHandle()
        val viewModel = UserViewModel(mockRepository, savedStateHandle)
        viewModel.fetchUser()
        val user = viewModel.userLiveData.getValue()
        assert(user!= null)
    }
    

大量ViewModel时的性能优化

资源管理

  1. 复用资源
    • 数据库连接:如果ViewModel需要访问数据库,使用单例模式创建数据库连接对象。例如,使用object关键字创建一个单例的DatabaseHelper对象:
    object DatabaseHelper {
        private lateinit var database: SQLiteDatabase
        fun getDatabase(context: Context): SQLiteDatabase {
            if (!::database.isInitialized) {
                val dbHelper = MyOpenHelper(context)
                database = dbHelper.writableDatabase
            }
            return database
        }
    }
    
    • 网络请求实例:对于网络请求,如使用OkHttp,可以创建一个单例的OkHttpClient实例。
    object NetworkClient {
        val client: OkHttpClient by lazy {
            OkHttpClient.Builder()
               .build()
        }
    }
    
  2. 及时释放资源
    • 关闭数据库连接:在ViewModel的onCleared方法中关闭数据库连接。例如:
    class MyViewModel : ViewModel() {
        private var database: SQLiteDatabase? = null
        override fun onCleared() {
            super.onCleared()
            database?.close()
        }
    }
    
    • 取消网络请求:在ViewModel的onCleared方法中取消正在进行的网络请求。如果使用RetrofitOkHttp,可以在Call对象上调用cancel()方法。
    class MyViewModel : ViewModel() {
        private var call: Call<MyResponse>? = null
        override fun onCleared() {
            super.onCleared()
            call?.cancel()
        }
    }
    

数据加载策略

  1. 分页加载
    • 在ViewModel中实现分页逻辑。例如,假设使用RecyclerView展示数据,在ViewModel中定义当前页码和每页数量:
    class MyViewModel : ViewModel() {
        private var currentPage = 1
        private val pageSize = 20
        val dataList = MutableLiveData<List<MyData>>()
    
        fun loadData() {
            viewModelScope.launch {
                val response = myRepository.fetchData(currentPage, pageSize)
                if (response.isSuccess) {
                    dataList.value = response.data
                    currentPage++
                }
            }
        }
    }
    
  2. 缓存数据
    • 内存缓存:使用LruCache在内存中缓存数据。例如,创建一个ViewModelCache类:
    class ViewModelCache {
        private val cache = LruCache<String, Any>(1024 * 1024 * 10) // 10MB cache
        fun put(key: String, value: Any) {
            cache.put(key, value)
        }
        fun get(key: String): Any? {
            return cache.get(key)
        }
    }
    
    • 磁盘缓存:使用DiskLruCache在磁盘上缓存数据。例如,在ViewModel中初始化磁盘缓存:
    class MyViewModel : ViewModel() {
        private lateinit var diskCache: DiskLruCache
        init {
            val cacheDir = context.cacheDir
            diskCache = DiskLruCache.open(cacheDir, 1, 1, 1024 * 1024 * 10) // 10MB cache
        }
        // 使用磁盘缓存获取和保存数据的方法
    }
    
  3. 懒加载
    • 对于一些非必要立即加载的数据,使用懒加载策略。例如,在ViewModel中定义一个懒加载的LiveData:
    class MyViewModel : ViewModel() {
        private val _lazyData = lazy {
            viewModelScope.launch {
                val data = myRepository.fetchLazyData()
                lazyDataLiveData.postValue(data)
            }
        }
        val lazyDataLiveData = MutableLiveData<MyLazyData>()
        fun loadLazyData() {
            _lazyData.value
        }
    }