使用Android建筑组件时的5个常见错误

使用Android建筑组件时的5个常见错误


即使您没有犯这些错误,也值得记住它们,以免将来遇到一些问题。


1.片段中的LiveData观察器泄漏


片段具有复杂的生命周期,并且当片段断开连接并重新加入活动时,它并不总是被销毁。 例如,在配置更改期间不会破坏保存的片段。 发生这种情况时,片段实例仍然存在,并且只破坏了它的View,因此不会调用onDestroy()并且不会达到DESTROYED状态。


这意味着如果我们开始在onCreateView()或更高版本(通常在onActivityCreated() )中观察LiveData并将片段作为LifecycleOwner传递:


 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) }) //    LifecycleOwner } ... } 

然后,每当片段重新加入时,我们都将传输一个新的Observer相同实例,但是LiveData不会删除以前的观察者,因为LifecycleOwner(片段)尚未达到DESTROYED状态。 这最终导致越来越多的相同且同时处于活动状态的观察者,并且onChanged()的同一代码执行了多次。


该问题最初是在此处报告的,可以在此处找到更详细的解释。


推荐的解决方案是使用片段生命周期中的getViewLifecycleOwner()getViewLifecycleOwnerLiveData() ,它们已添加到支持库28.0.0和AndroidX 1.0.0中,因此,每次销毁View片段时,LiveData都会删除观察者:


 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) }) } ... } 

2.每次旋转屏幕后重新启动数据


我们习惯于将初始化和配置逻辑放在Activity的onCreate()中(并以类似的方式放在onCreateView()或更高版本的片段中),因此在这一点上可能很想在ViewModels中启动某些数据的加载。 但是,根据您的逻辑,这可能会导致屏幕每次旋转后重新加载数据(即使使用了ViewModel),在大多数情况下这毫无意义。


一个例子:


 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() //  ProductDetails ... //  ProductDetails   productDetails LiveData return productDetails } fun loadSpecialOffers() { repository.getSpecialOffers() //  SpecialOffers ... //  SpecialOffers   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 { /*...*/ }) // ()   productDetails    viewModel.loadSpecialOffers() // ()   specialOffers    } } 

该决定还取决于您的逻辑。 如果存储库将缓存数据,则以上代码可能会正常工作。 另外,可以通过其他方式解决此问题:


  • 请使用与AbsentLiveData类似的方法,仅在未收到数据时才开始下载;
  • 在确实需要时开始下载数据。 例如,在OnClickListener中;
  • 可能是最简单的方法:将load调用放入ViewModel构造函数中,并使用简单的getter:

 class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() init { loadProductsDetails() // ViewModel         Activity   } private fun loadProductsDetails() { repository.getProductDetails() ... } fun loadSpecialOffers() { repository.getSpecialOffers() ... } fun getProductDetails(): LiveData<Resource<ProductDetails>> { return productDetails } fun getSpecialOffers(): LiveData<Resource<SpecialOffers>> { 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 { /*...*/ }) viewModel.getSpecialOffers().observe(this, Observer { /*...*/ }) button_offers.setOnClickListener { viewModel.loadSpecialOffers() } } } 

3.泄漏ViewModels


架构开发人员明确表示,我们不应将View链接传递给ViewModel。


警告


但在将对ViewModels的引用传递给其他类时,我们也必须小心。 活动(或片段)完成后,无法将ViewModel引用到该Activity可以生存的任何对象,因此,垃圾回收器可以破坏ViewModel。


在此泄漏示例中,以Singleton样式编写的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 { liveData.value = it } } } 

此处的解决方案可能是删除onCleared()方法中的侦听器,将其另存为存储库中的WeakReference ,或使用LiveData在存储库和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 { liveData.value = it } } override onCleared() { repository.removeOnLocationChangedListener() } } 

4.允许View修改LiveData


这不是错误,但是与利益分离矛盾。 视图-片段和活动-应该不能更新LiveData,因此不能更新其自身的状态,因为这是ViewModels的责任。 View应该只能观看LiveData。


因此,我们必须封装对MutableLiveData的访问:


 class CatalogueViewModel : ViewModel() { // :   MutableLiveData val products = MutableLiveData<Products>() // :    MutableLiveData private val promotions = MutableLiveData<Promotions>() fun getPromotions(): LiveData<Promotions> = promotions // :    MutableLiveData private val _offers = MutableLiveData<Offers>() val offers: LiveData<Offers> = _offers fun loadData(){ products.value = loadProducts() //     products promotions.value = loadPromotions() //  CatalogueViewModel   promotions _offers.value = loadOffers() // Only CatalogueViewModel   _offers } } 

5.每次配置更改后创建ViewModel依赖项


ViewModel可以承受配置更改,例如屏幕旋转,因此,如果每次更改都创建视图模型,则有时会导致无法预料的行为,尤其是在设计器中内置了某些逻辑的情况下。


尽管这看起来似乎很明显,但是使用ViewModelFactory时,它通常很容易被忽略,它通常与它创建的ViewModel具有相同的依赖关系。


ViewModelProvider保存ViewModel的一个实例,但不保存ViewModelFactory的一个实例,因此如果我们有以下代码:


 class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { 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?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movies) injectDependencies() viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... } 

然后,每次配置更改发生时,我们将创建一个ViewModelFactory的新实例,因此,无需特别创建所有依赖关系的新实例(前提是它们不受任何限制)。


 class MoviesViewModel( private val repository: MoviesRepository, private val stringProvider: StringProvider, private val authorisationService: AuthorisationService ) : ViewModel() { ... } class MoviesViewModelFactory( private val repository: Provider<MoviesRepository>, private val stringProvider: Provider<StringProvider>, private val authorisationService: Provider<AuthorisationService> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return MoviesViewModel(repository.get(), stringProvider.get(), 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() viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java) } ... } 

Source: https://habr.com/ru/post/zh-CN454424/


All Articles