本文翻译自【5 common mistakes when using Architecture Components】,介绍了在使用 Android Architecture Components 的五个常见误区。水平有限,欢迎指正讨论。 轻微的疏忽或多或少会带来严重的后果 —— 即便你没有犯这些错误,也应该了解并记住它们,以免未来遇到一些问题。本文将介绍如下五个误区:html
原文:Leaking LiveData observers in Fragmentsjava
Fragment 具备复杂的生命周期,当一个 Fragment 与其宿主 Activity 取消关联(执行 Fragment#onDetach()
),而后从新关联(执行 Fragment#onAttach()
)时,实际上这个 Fragment 并不老是会被销毁(执行 Fragment#onDestroy()
)。例如在配置变化时,被保留(Fragment#setRetainInstance(true)
)的 Fragment 不会被销毁。这时,只有 Fragment 的视图会被销毁(Fragment#onDestroyView()
),而 Fragment 实例没有被销毁,所以不会调用 Fragment#onDestroy()
方法,也就是说 Fragment 做为 LifecycleOwner 没有到达已销毁状态 (Lifecycle.State#DESTROYED)。 这意味着,若是咱们在 Fragment#onCreateView()
及之后的方法(一般是 Fragment#onActivityCreated()
)中观察 LiveData,并将 Fragment 做为 LifecycleOwner 传入就会出现问题。 例如:android
class BooksFragment: Fragment() {
private lateinit var viewModel: BooksViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_books, container)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java)
viewModel.liveData.observe(this, Observer { updateViews(it) }) // Risky: Passing Fragment as LifecycleOwner
}
//...
}
复制代码
每次当 Activity 从新关联 Fragment 时,咱们都会传递一个新的相同的观察者实例,可是 LiveData 不会删除之前的观察者,由于 LifecycleOwner(即 Fragment)没有达到已销毁状态。这最终会致使愈来愈多的相同观察者同时处于活动状态,从而致使 Observer#onChanged() 方法也会被重复执行屡次。git
推荐的解决方案是:经过 Fragment#getViewLifecycleOwner() 或 Fragment#getViewLifecycleOwnerLiveData() 方法获取 Fragment 的视图(View)生命周期,而不是 Fragment 实例的生命周期,这两个方法是在 Support-28.0.0 和 AndroidX-1.0.0 中添加的,这样,LiveData 就会在每次 Fragment 的视图销毁时移除观察者。数据库
class BooksFragment : Fragment() {
//...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java)
viewModel.liveData.observe(viewLifecycleOwner, Observer { updateViews(it) }) // Usually what we want: Passing Fragment's view as LifecycleOwner
}
//...
}
复制代码
原文:Reloading data after every rotation后端
一般,咱们在 Activity#onCreate()
,或 Fragment#onCreateView()
及之后的生命周期方法中初始化代码逻辑,用来触发 ViewModel 获取数据。若是代码不规范,在每次屏幕旋转后,即便 ViewModel 实例不会从新建立,也可能致使从新加载数据,而在大多数状况下,数据并无变化,因此这种从新加载没有意义。 例如:api
class ProductViewModel(
private val repository: ProductRepository
) : ViewModel() {
private val productDetails = MutableLiveData<Resource<ProductDetails>>()
private val specialOffers = MutableLiveData<Resource<SpecialOffers>>()
fun getProductsDetails(): LiveData<Resource<ProductDetails>> {
repository.getProductDetails() // Loading ProductDetails from network/database
//... // Getting ProductDetails from repository and updating productDetails LiveData
return productDetails
}
fun loadSpecialOffers() {
repository.getSpecialOffers() // Loading SpecialOffers from network/database
//... // Getting SpecialOffers from repository and updating specialOffers LiveData
}
}
class ProductActivity : AppCompatActivity() {
lateinit var productViewModelFactory: ProductViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java)
viewModel.getProductsDetails().observe(this, Observer { /*...*/ }) // (probable) Reloading product details after every rotation
viewModel.loadSpecialOffers() // (probable) Reloading special offers after every rotation
}
}
复制代码
这个问题的解决方案取决于咱们的代码逻辑。若是 Repository 缓存了数据,上面的例子就没有问题,由于 Repository 的缓存有效就不会请求网络或读写数据库。但也有一些其余解决办法:缓存
class ProductViewModel(
private val repository: ProductRepository
) : ViewModel() {
private val productDetails = MutableLiveData<Resource<ProductDetails>>()
private val specialOffers = MutableLiveData<Resource<SpecialOffers>>()
init {
loadProductsDetails() // ViewModel is created only once during Activity/Fragment lifetime
}
private fun loadProductsDetails() { // private, just utility method to be invoked in constructor
repository.getProductDetails() // Loading ProductDetails from network/database
... // Getting ProductDetails from repository and updating productDetails LiveData
}
fun loadSpecialOffers() { // public, intended to be invoked by other classes when needed
repository.getSpecialOffers() // Loading SpecialOffers from network/database
... // Getting SpecialOffers from repository and updating _specialOffers LiveData
}
fun getProductDetails(): LiveData<Resource<ProductDetails>> { // Simple getter
return productDetails
}
fun getSpecialOffers(): LiveData<Resource<SpecialOffers>> { // Simple getter
return specialOffers
}
}
class ProductActivity : AppCompatActivity() {
lateinit var productViewModelFactory: ProductViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java)
viewModel.getProductDetails().observe(this, Observer { /*...*/ }) // Just setting observer
viewModel.getSpecialOffers().observe(this, Observer { /*...*/ }) // Just setting observer
button_offers.setOnClickListener { viewModel.loadSpecialOffers() }
}
}
复制代码
原文:Leaking ViewModels网络
Google 官方已经明确提示,ViewModel 不该持有 View、Lifecycle、或其余可能持有 Activity 的 Context 的类的引用。
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
可是,咱们也应注意 其余类不该持有 ViewModel 的引用。在 Activity 或 Fragment 销毁后,其它任何比 Activity 或 Fragment 生命周期长的类都不该再持有 ViewModel 的引用,不然会影响 ViewModel 被 GC 回收,从而泄露 ViewModel。 以下面的例子,Repository 是一个单例,它持有了 ViewModel 的监听器引用,但并无释放:
@Singleton
class LocationRepository() {
private var listener: ((Location) -> Unit)? = null
fun setOnLocationChangedListener(listener: (Location) -> Unit) {
this.listener = listener
}
private fun onLocationUpdated(location: Location) {
listener?.invoke(location)
}
}
class MapViewModel: AutoClearViewModel() {
private val liveData = MutableLiveData<LocationRepository.Location>()
private val repository = LocationRepository()
init {
repository.setOnLocationChangedListener { // Risky: Passing listener (which holds reference to the MapViewModel)
liveData.value = it // to singleton scoped LocationRepository
}
}
}
复制代码
有以下几种方案能够避免泄露 ViewModel:
@Singleton
class LocationRepository() {
private var listener: ((Location) -> Unit)? = null
fun setOnLocationChangedListener(listener: (Location) -> Unit) {
this.listener = listener
}
fun removeOnLocationChangedListener() {
this.listener = null
}
private fun onLocationUpdated(location: Location) {
listener?.invoke(location)
}
}
class MapViewModel: AutoClearViewModel() {
private val liveData = MutableLiveData<LocationRepository.Location>()
private val repository = LocationRepository()
init {
repository.setOnLocationChangedListener { // Risky: Passing listener (which holds reference to the MapViewModel)
liveData.value = it // to singleton scoped LocationRepository
}
}
override onCleared() { // GOOD: Listener instance from above and MapViewModel
repository.removeOnLocationChangedListener() // can now be garbage collected
}
}
复制代码
原文:Exposing LiveData as mutable to Views
这个误区不是 Bug,可是它违背了关注点分离原则。如下引自 维基百科
关注点分离(Separation of concerns,SOC)是对只与“特定概念、目标”(关注点)相关联的软件组成部分进行“标识、封装和操纵”的能力,即标识、封装和操纵关注点的能力。是处理复杂性的一个原则。因为关注点混杂在一块儿会致使复杂性大大增长,因此可以把不一样的关注点分离开来,分别处理就是处理复杂性的一个原则,一种方法。关注点分离在计算机科学中,是将计算机程序分隔为不一样部分的设计原则,是面向对象的程序设计的核心概念。每一部分会有各自的关注焦点。关注焦点是影响计算机程式代码的一组资讯。关注焦点能够像是将代码优化过的硬件细节通常,或者像实例化类别的名称同样具体。展示关注点分离设计的程序被称为模组化程序。模组化程度,也就是区分关注焦点,经过将资讯封装在具备明确界面的程序代码段落中。封装是一种资讯隐藏手段。资讯系统中的分层设计是关注点分离的另外一个实施例(例如,表示层,业务逻辑层,数据访问层,维持齐一层)。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中再也不含有针对特定领域问题代码的调用(将针对特定领域问题代码抽象化成较少的程式码,例如将代码封装成function或是class),业务逻辑同特定领域问题的关系经过侧面来封装、维护,这样本来分散在整个应用程序中的变更就能够很好的管理起来。关注点分离的价值在于简化计算机程序的开发和维护。当关注点分开时,各部分能够重复使用,以及独立开发和更新。具备特殊价值的是可以稍后改进或修改一段代码,而无需知道其余部分的细节必须对这些部分进行相应的更改。
View,即 Activity 和 Fragment 不该该主动更新 LiveData 数据来刷新 UI 状态,由于这是 ViewModel 的职责。View 只应该是 LiveData 的观察者。 所以咱们应该封装 MutableLiveData 的使用,例如暴露 getter 方法或使用 Kotlin 的 后端属性。
class CatalogueViewModel : ViewModel() {
// BAD: Exposing mutable LiveData
val products = MutableLiveData<Products>()
// GOOD: Encapsulate access to mutable LiveData through getter
private val promotions = MutableLiveData<Promotions>()
fun getPromotions(): LiveData<Promotions> = promotions
// GOOD: Encapsulate access to mutable LiveData using backing property
private val _offers = MutableLiveData<Offers>()
val offers: LiveData<Offers> = _offers
fun loadData(){
products.value = loadProducts() // Other classes can also set products value
promotions.value = loadPromotions() // Only CatalogueViewModel can set promotions value
_offers.value = loadOffers() // Only CatalogueViewModel can set offers value
}
}
复制代码
原文:Creating ViewModel’s dependencies after every configuration change
当屏幕旋转引发配置变化时,ViewModel 不会从新建立(详见Lifecycle),所以每次配置变化后建立 ViewModel 的依赖项是无心义的,有时可能致使意想不到的后果,尤为当依赖的构造方法中存在业务逻辑时。 虽然这听起来很明显,但在使用 ViewModelFactory 时很容易忽略这一点,由于 ViewModeFactory 一般与它建立的 ViewModel 具备相同的依赖关系。 ViewModelProvider 会保留 ViewModel 实例,但不保留 ViewModelFactory 实例,例如:
class MoviesViewModel(
private val repository: MoviesRepository,
private val stringProvider: StringProvider,
private val authorisationService: AuthorisationService
) : ViewModel() {
//...
}
class MoviesViewModelFactory( // We need to create instances of below dependencies to create instance of MoviesViewModelFactory
private val repository: MoviesRepository,
private val stringProvider: StringProvider,
private val authorisationService: AuthorisationService
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { // but this method is called by ViewModelProvider only if ViewModel wasn't already created
return MoviesViewModel(repository, stringProvider, authorisationService) as T
}
}
class MoviesActivity : AppCompatActivity() {
@Inject
lateinit var viewModelFactory: MoviesViewModelFactory
private lateinit var viewModel: MoviesViewModel
override fun onCreate(savedInstanceState: Bundle?) { // Called each time Activity is recreated
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movies)
injectDependencies() // Creating new instance of MoviesViewModelFactory
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java)
}
//...
}
复制代码
每次发生配置变化时,咱们都会建立一个新的 ViewModelFactory 实例,从而没必要要地会建立全部依赖项的新实例(假如这些依赖项没有肯定的做用域)。咱们可使用 Provider 的懒加载来避免这个问题:
class MoviesViewModel(
private val repository: MoviesRepository,
private val stringProvider: StringProvider,
private val authorisationService: AuthorisationService
) : ViewModel() {
//...
}
class MoviesViewModelFactory(
private val repository: Provider<MoviesRepository>, // Passing Providers here
private val stringProvider: Provider<StringProvider>, // instead of passing directly dependencies
private val authorisationService: Provider<AuthorisationService>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { // This method is called by ViewModelProvider only if ViewModel wasn't already created
return MoviesViewModel(repository.get(),
stringProvider.get(), // Deferred creating dependencies only if new insance of ViewModel is needed
authorisationService.get()
) as T
}
}
class MoviesActivity : AppCompatActivity() {
@Inject
lateinit var viewModelFactory: MoviesViewModelFactory
private lateinit var viewModel: MoviesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movies)
injectDependencies() // Creating new instance of MoviesViewModelFactory
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java)
}
//...
}
复制代码
我是 xiaobailong24,您能够经过如下平台找到我: