【譯】使用 Android Architecture Componen
正文
洩露 Fragment 中的 LiveData 觀察者
原文:Leaking LiveData observers in Fragments
Fragment 具有複雜的生命週期,當一個 Fragment 與其宿主 Activity 取消關聯(執行 Fragment#onDetach()
),然後重新關聯(執行 Fragment#onAttach()
)時,實際上這個 Fragment 並不總是會被銷燬(執行 Fragment#onDestroy()
)。例如在配置變化時,被保留(Fragment#setRetainInstance(true)
)的 Fragment 不會被銷燬。這時,只有 Fragment 的檢視會被銷燬(Fragment#onDestroyView()
),而 Fragment 例項沒有被銷燬,因此不會呼叫 Fragment#onDestroy()
方法,也就是說 Fragment 作為 沒有到達已銷燬狀態 ()。
這意味著,如果我們在 Fragment#onCreateView()
及以後的方法(通常是 Fragment#onActivityCreated()
)中觀察 LiveData,並將 Fragment 作為 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) }) // Risky: Passing Fragment as LifecycleOwner } //...}
每次當 Activity 重新關聯 Fragment 時,我們都會傳遞一個新的相同的觀察者例項,但是 LiveData 不會刪除以前的觀察者,因為 LifecycleOwner(即 Fragment)沒有達到已銷燬狀態。這最終會導致越來越多的相同觀察者同時處於活動狀態,從而導致 方法也會被重複執行多次。
這個問題最初是在提出的,在可以找到更多細節。
推薦的解決方案是:透過 或 方法獲取 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 例項不會重新建立,也可能導致重新載入資料,而在大多數情況下,資料並沒有變化,所以這種重新載入沒有意義。
例如:
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 的快取有效就不會請求網路或讀寫資料庫。但也有一些其他解決辦法:
使用類似於 的類,只有在沒有執行過 或 的情況下,才會載入資料。
在實際需要的地方才開始載入資料,例如點選事件(OnClickListener)
可能最簡單的方案是,將載入資料的邏輯,寫在 ViewModel 的構造方法中,並暴露 getter 方法,例如:
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() } } }
洩露 ViewModel
原文: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:
在 方法中移除監聽器。
Repository 持有 Listener 的 弱引用(WeakReference)。
Repository 和 ViewModel 使用 LiveData 來通訊。
其他保證 ViewModel 能被 GC 正確回收的方案。
@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 } }
將易變的 LiveData 暴露給 View
原文:Exposing LiveData as mutable to Views
這個誤區不是 Bug,但是它違背了關注點分離原則。以下引自
關注點分離(Separation of concerns,SOC)是對只與“特定概念、目標”(關注點)相關聯的軟體組成部分進行“標識、封裝和操縱”的能力,即標識、封裝和操縱關注點的能力。是處理複雜性的一個原則。由於關注點混雜在一起會導致複雜性大大增加,所以能夠把不同的關注點分離開來,分別處理就是處理複雜性的一個原則,一種方法。關注點分離在電腦科學中,是將計算機程式分隔為不同部分的設計原則,是物件導向的程式設計的核心概念。每一部分會有各自的關注焦點。關注焦點是影響計算機程式程式碼的一組資訊。關注焦點可以像是將程式碼最佳化過的硬體細節一般,或者像例項化類別的名稱一樣具體。展現關注點分離設計的程式被稱為模組化程式。模組化程度,也就是區分關注焦點,透過將資訊封裝在具有明確介面的程式程式碼段落中。封裝是一種資訊隱藏手段。資訊系統中的分層設計是關注點分離的另一個實施例(例如,表示層,業務邏輯層,資料訪問層,維持齊一層)。分離關注點使得解決特定領域問題的程式碼從業務邏輯中獨立出來,業務邏輯的程式碼中不再含有針對特定領域問題程式碼的呼叫(將針對特定領域問題程式碼抽象化成較少的程式碼,例如將程式碼封裝成function或是class),業務邏輯同特定領域問題的關係透過側面來封裝、維護,這樣原本分散在整個應用程式中的變動就可以很好的管理起來。關注點分離的價值在於簡化計算機程式的開發和維護。當關注點分開時,各部分可以重複使用,以及獨立開發和更新。具有特殊價值的是能夠稍後改進或修改一段程式碼,而無需知道其他部分的細節必須對這些部分進行相應的更改。
View,即 Activity 和 Fragment 不應該主動更新 LiveData 資料來重新整理 UI 狀態,因為這是 ViewModel 的職責。View 只應該是 LiveData 的觀察者。
因此我們應該封裝 的使用,例如暴露 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 } }
每次配置變化後重新建立 ViewModel 依賴項的例項
原文:Creating ViewModel’s dependencies after every configuration change
當螢幕旋轉引起配置變化時,ViewModel 不會重新建立(詳見),因此每次配置變化後建立 ViewModel 的依賴項是無意義的,有時可能導致意想不到的後果,尤其當依賴的構造方法中存在業務邏輯時。
雖然這聽起來很明顯,但在使用 時很容易忽略這一點,因為 ViewModeFactory 通常與它建立的 ViewModel 具有相同的依賴關係。
會保留 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 例項,從而不必要地會建立所有依賴項的新例項(假如這些依賴項沒有確定的作用域)。我們可以使用 的懶載入來避免這個問題:
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
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3034/viewspace-2821747/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【譯】使用 Android Architecture Components 的五個常見誤區Android
- 【譯】Android Architecture - ViewModel 與 View 的通訊AndroidView
- [譯] Architecture Components 之 Guide to App ArchitectureGUIIDEAPP
- [譯]Spring Security ArchitectureSpring
- Samza文件翻譯 : Architecture
- Android Jetpack 之Navigation Architecture Component使用AndroidJetpackNavigation
- [譯] Architecture Components 之 ViewModelView
- [譯] Architecture Components 之 LiveDataLiveData
- Hadoop官網翻譯之HDFS ArchitectureHadoop
- Android Architecture Components 之 Room 篇AndroidOOM
- Android Jetpack Architecture原理之ViewModelAndroidJetpackView
- Android-Architecture之todo-mvpAndroidMVP
- Android Architecture Blueprints(架構藍圖)Android架構
- [譯] 使用 Architecture Components 開發 MVVM 應用:MVP 開發者的實踐指南MVVMMVP
- Android-Architecture 之 todo-mvp-cleanAndroidMVP
- Android Architecture Components 系列一(初識)Android
- Android Architecture Components Part1:RoomAndroidOOM
- Android Architecture Component 原始碼淺析Android原始碼
- android使用ant編譯(rem)Android編譯REM
- [譯] Architecture Components 之 Handling Lifecycles
- Android Architecture Components Part2:LiveDataAndroidLiveData
- Architecture
- [譯] Architecture Components 之 Adding Components to your ProjectProject
- Android開發編譯curl庫給Android使用Android編譯
- 基於 Android Architecture Components 的 MVVM 淺析AndroidMVVM
- 關於Clean Architecture在android中的應用Android
- FFmpeg編譯Android使用的so庫編譯Android
- Clean Architecture - 清晰簡潔的Android 應用架構Android應用架構
- MVVMArms - MVVM 與 Android Architecture Components 的最佳實戰MVVMAndroid
- HDFS Architecture
- Process Architecture
- Oracle ArchitectureOracle
- website architectureWeb
- The Architecture of NginxNginx
- [譯] ProGuard 在 Android 上的使用姿勢Android
- Android反編譯工具ApkTool的使用Android編譯APK
- Android App 中正確地使用 Splash Screen(譯)AndroidAPP
- 【譯】如何使用Android MediaStore裁剪大圖片AndroidAST