
Auch wenn Sie diese Fehler nicht machen, sollten Sie sich an sie erinnern, um in Zukunft keine Probleme mehr zu haben.
1. Verlust von LiveData-Beobachtern in Fragmenten
Fragmente haben einen komplexen Lebenszyklus, und wenn ein Fragment eine Aktivität trennt und wieder verbindet, wird es nicht immer zerstört. Beispielsweise werden gespeicherte Fragmente bei Konfigurationsänderungen nicht zerstört. In diesem Fall bleibt die Fragmentinstanz erhalten und nur ihre Ansicht wird zerstört, sodass onDestroy()
nicht aufgerufen wird und der Status DESTROYED nicht erreicht wird.
Dies bedeutet, dass, wenn wir LiveData in onCreateView()
oder höher (normalerweise in onActivityCreated()
) beobachten und das Fragment als LifecycleOwner übergeben:
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) })
Dann senden wir jedes Mal eine neue identische Instanz von Observer, wenn ein Fragment erneut verbunden wird. LiveData löscht jedoch keine vorherigen Beobachter, da LifecycleOwner (Fragment) den Status DESTROYED nicht erreicht hat. Dies führt letztendlich zu einer wachsenden Anzahl identischer und gleichzeitig aktiver Beobachter, und derselbe Code von onChanged()
wird mehrmals ausgeführt.
Das Problem wurde ursprünglich hier gemeldet. Eine ausführlichere Erklärung finden Sie hier .
Die empfohlene Lösung besteht darin, getViewLifecycleOwner () oder getViewLifecycleOwnerLiveData () aus dem Fragmentlebenszyklus zu verwenden, die der Unterstützungsbibliothek 28.0.0 und AndroidX 1.0.0 hinzugefügt wurden, damit LiveData Beobachter jedes Mal löscht, wenn das View-Fragment zerstört wird:
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. Starten Sie die Daten nach jeder Bildschirmdrehung neu
Wir sind es gewohnt, die Initialisierungs- und Konfigurationslogik in onCreate()
in Activity (und analog in onCreateView()
oder später in Fragmenten) onCreateView()
ist es an dieser Stelle möglicherweise verlockend, das Laden einiger Daten in ViewModels zu initiieren. Abhängig von Ihrer Logik kann dies jedoch nach jeder Drehung des Bildschirms zu einem erneuten Laden der Daten führen (selbst wenn das ViewModel verwendet wurde), was in den meisten Fällen einfach sinnlos ist.
Ein Beispiel:
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()
Die Entscheidung hängt auch von Ihrer Logik ab. Wenn das Repository Daten zwischenspeichert, funktioniert der obige Code wahrscheinlich ordnungsgemäß. Dieses Problem kann auch auf andere Weise gelöst werden:
- Verwenden Sie etwas Ähnliches wie AbsentLiveData und starten Sie den Download nur, wenn keine Daten empfangen wurden.
- Laden Sie Daten herunter, wenn dies wirklich erforderlich ist. Zum Beispiel in OnClickListener;
- Und wahrscheinlich das einfachste: Fügen Sie Ladeaufrufe in den ViewModel-Konstruktor ein und verwenden Sie einfache Getter:
class ProductViewModel( private val repository: ProductRepository ) : ViewModel() { private val productDetails = MutableLiveData<Resource<ProductDetails>>() private val specialOffers = MutableLiveData<Resource<SpecialOffers>>() init { loadProductsDetails()
3. Leak ViewModels
Architekturentwickler haben klargestellt, dass wir keine View-Links an das ViewModel übergeben sollten.

Wir müssen jedoch auch vorsichtig sein, wenn wir Verweise auf ViewModels an andere Klassen übergeben. Nach Abschluss der Aktivität (oder des Fragments) kann das ViewModel nicht auf ein Objekt verwiesen werden, das die Aktivität überleben kann, sodass das ViewModel vom Garbage Collector zerstört werden kann.
In diesem Leckbeispiel wird der im Singleton-Stil geschriebene Repository-Listener an das ViewModel übergeben. Anschließend wird der Hörer nicht zerstört:
@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 } } }
Die Lösung hier könnte darin bestehen, den Listener in der onCleared()
-Methode zu entfernen, ihn als WeakReference
im Repository zu speichern oder LiveData für die Kommunikation zwischen dem Repository und ViewModel zu verwenden:
@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. Erlauben Sie View, LiveData zu ändern
Dies ist kein Fehler, widerspricht jedoch der Interessentrennung. View - Fragmente und Aktivität - sollte LiveData und damit seinen eigenen Status nicht aktualisieren können, da es in der Verantwortung von ViewModels liegt. View sollte nur LiveData sehen können.
Daher müssen wir den Zugriff auf MutableLiveData kapseln:
class CatalogueViewModel : ViewModel() {
5. Erstellen von ViewModel-Abhängigkeiten nach jeder Konfigurationsänderung
ViewModels halten Konfigurationsänderungen stand, z. B. der Bildschirmdrehung. Wenn Sie sie also jedes Mal erstellen, wenn eine Änderung auftritt, kann dies manchmal zu unvorhersehbarem Verhalten führen, insbesondere wenn im Designer eine Logik integriert ist.
Obwohl dies ziemlich offensichtlich erscheint, ist es bei Verwendung der ViewModelFactory, die normalerweise dieselben Abhängigkeiten wie das von ihr erstellte ViewModel aufweist, leicht zu übersehen.
ViewModelProvider speichert eine Instanz von ViewModel, jedoch keine Instanz von ViewModelFactory. Wenn wir also diesen Code haben:
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) } ... }
Jedes Mal, wenn eine Konfigurationsänderung auftritt, erstellen wir eine neue Instanz von ViewModelFactory und müssen daher ohne besondere Notwendigkeit neue Instanzen aller Abhängigkeiten erstellen (vorausgesetzt, sie sind in keiner Weise eingeschränkt).
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) } ... }