深入理解 Android DataBinding 与 ViewModel

一、Android DataBinding 概述

1.1 DataBinding 简介

Android DataBinding 是 Android 官方提供的一个支持库,允许开发者将布局文件(XML)与应用程序逻辑(通常是 ViewModel)直接绑定。这种绑定机制能够减少传统的 findViewById() 调用,使代码更加简洁、可维护。

DataBinding 的核心优势包括:

  • 减少样板代码,提高开发效率
  • 实现视图与数据的解耦,遵循 MVVM 架构模式
  • 提高代码的可读性和可维护性
  • 减少因空指针异常导致的崩溃

1.2 DataBinding 基本用法

使用 DataBinding 首先需要在项目的 build.gradle 文件中启用该功能:

android {...dataBinding {enabled = true}
}

然后在布局文件中使用 <layout> 标签包裹根元素:

<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="user"type="com.example.User" /></data><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.name}" />
</layout>

在 Activity 或 Fragment 中,可以通过生成的 Binding 类来设置数据:

// 加载布局并获取 Binding 实例
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
// 设置变量值
binding.setUser(user);
// 设置生命周期所有者,使 LiveData 能够自动更新
binding.setLifecycleOwner(this);
// 设置ContentView
setContentView(binding.getRoot());

1.3 DataBinding 与 MVVM 架构

DataBinding 是实现 MVVM(Model-View-ViewModel)架构的关键组件之一。MVVM 架构模式将视图(View)与业务逻辑(ViewModel)分离,通过 DataBinding 实现两者之间的数据流动。

  • Model:负责数据的获取和存储,通常包括网络请求、数据库操作等
  • View:对应于 Activity、Fragment 或自定义 View,负责展示 UI
  • ViewModel:作为 View 和 Model 之间的桥梁,处理业务逻辑并提供数据给 View

DataBinding 使得 View 可以直接绑定到 ViewModel 中的数据,而不需要在 Activity 或 Fragment 中编写大量的胶水代码。这种分离使得代码更加模块化,易于测试和维护。

二、ViewModel 基础

2.1 ViewModel 简介

ViewModel 是 Android Architecture Components 中的一个组件,旨在存储和管理与 UI 相关的数据,并确保数据在配置更改(如屏幕旋转)后仍然存在。ViewModel 的设计遵循以下原则:

  • 分离 UI 逻辑与业务逻辑
  • 保持数据在配置更改期间的持久性
  • 简化单元测试
  • 促进代码复用

2.2 ViewModel 基本用法

使用 ViewModel 通常需要继承 ViewModel 类,并在其中定义与 UI 相关的数据:

import androidx.lifecycle.ViewModel;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.LiveData;public class MyViewModel extends ViewModel {// 使用 MutableLiveData 存储可修改的数据private MutableLiveData<String> mUserName = new MutableLiveData<>();// 提供只读的 LiveData 给外部观察public LiveData<String> getUserName() {return mUserName;}// 更新用户名的方法public void setUserName(String userName) {mUserName.setValue(userName);}
}

在 Activity 或 Fragment 中获取 ViewModel 实例:

import androidx.lifecycle.ViewModelProvider;public class MainActivity extends AppCompatActivity {private MyViewModel mViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 通过 ViewModelProvider 获取 ViewModel 实例mViewModel = new ViewModelProvider(this).get(MyViewModel.class);// 观察 LiveData 的变化mViewModel.getUserName().observe(this, userName -> {// 更新 UI});}
}

2.3 ViewModel 的生命周期

ViewModel 的生命周期与 Activity 或 Fragment 的生命周期不同。ViewModel 的生命周期从它被创建开始,直到与之关联的 Activity 被完全销毁(非配置更改导致的销毁)或 Fragment 被移除。

下面是 ViewModel 生命周期的关键特点:

  • ViewModel 在 Activity 因配置更改(如旋转)而重新创建时保持不变
  • ViewModel 在 Activity 真正销毁时才会被清除
  • ViewModel 的生命周期比 Activity 或 Fragment 的生命周期更长

这种生命周期特性使得 ViewModel 非常适合存储和管理与 UI 相关的数据,特别是在处理异步操作和配置更改时。

三、DataBinding 与 ViewModel 的集成

3.1 集成方式

DataBinding 与 ViewModel 的集成主要通过以下步骤实现:

  1. 在布局文件中声明 ViewModel 变量
  2. 在 Activity 或 Fragment 中设置 ViewModel 实例
  3. 在布局文件中使用表达式绑定 ViewModel 中的数据和方法

下面是一个完整的示例:

<!-- activity_main.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="viewModel"type="com.example.MyViewModel" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{viewModel.userName}" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="更新名称"android:onClick="@{() -> viewModel.updateUserName()}" /></LinearLayout>
</layout>
// MyViewModel.java
public class MyViewModel extends ViewModel {// 使用 LiveData 存储用户名private MutableLiveData<String> userName = new MutableLiveData<>("初始名称");// 获取用户名的 LiveDatapublic LiveData<String> getUserName() {return userName;}// 更新用户名的方法public void updateUserName() {userName.setValue("新名称");}
}
// MainActivity.java
public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;private MyViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 初始化 DataBindingbinding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// 获取 ViewModel 实例viewModel = new ViewModelProvider(this).get(MyViewModel.class);// 设置 ViewModel 到 DataBindingbinding.setViewModel(viewModel);// 设置生命周期所有者,使 LiveData 能够自动更新binding.setLifecycleOwner(this);}
}

3.2 数据绑定机制

DataBinding 与 ViewModel 的集成基于以下核心机制:

  1. 表达式语言:DataBinding 使用一种简单的表达式语言来绑定数据和方法。例如:android:text="@{viewModel.userName}"
  2. LiveData 支持:DataBinding 能够自动观察 LiveData 的变化,并在数据更新时自动更新 UI。
  3. 双向绑定:除了单向绑定,DataBinding 还支持双向绑定,例如在 EditText 中:android:text="@={viewModel.inputText}"
  4. 事件绑定:可以直接在布局文件中绑定 ViewModel 中的方法作为事件处理程序,例如:android:onClick="@{() -> viewModel.doSomething()}"

3.3 优势分析

将 DataBinding 与 ViewModel 集成带来了以下优势:

  1. 代码简洁:减少了 Activity 和 Fragment 中的样板代码,使代码更加清晰。
  2. 视图与逻辑分离:遵循 MVVM 架构模式,使视图和业务逻辑完全分离。
  3. 自动数据更新:通过 LiveData 和 DataBinding 的结合,实现了数据到 UI 的自动更新。
  4. 配置更改处理:ViewModel 在配置更改时保持数据,避免了数据丢失和重复加载。
  5. 易于测试:ViewModel 可以独立测试,不依赖于 Activity 或 Fragment。

四、ViewModel 源码分析

4.1 ViewModel 类结构

ViewModel 是一个抽象类,定义在 androidx.lifecycle 包中:

package androidx.lifecycle;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;/*** ViewModel 类是一个抽象类,用于存储和管理与 UI 相关的数据* 设计目的是在配置更改(如屏幕旋转)期间保留数据*/
public abstract class ViewModel {// 标记 ViewModel 是否已经被清理private boolean mCleared = false;/*** 当 ViewModel 不再被使用且将被销毁时调用* 子类可以重写此方法来清理资源,如取消网络请求或数据库操作*/@SuppressWarnings("WeakerAccess")protected void onCleared() {}/*** 由 ViewModelStore 调用,用于清理 ViewModel* 此方法会设置 mCleared 标志并调用 onCleared() 方法*/final void clear() {mCleared = true;// 调用 onCleared() 方法,由子类实现具体的清理逻辑onCleared();}/*** 检查 ViewModel 是否已被清理* @return 如果 ViewModel 已被清理返回 true,否则返回 false*/public final boolean isCleared() {return mCleared;}
}

ViewModel 类的核心功能非常简单,主要提供了一个生命周期回调方法 onCleared(),用于在 ViewModel 不再被使用时进行资源清理。

4.2 ViewModelProvider 源码分析

ViewModelProvider 是用于获取 ViewModel 实例的核心类,它负责创建和管理 ViewModel 实例。

package androidx.lifecycle;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.arch.core.util.Function;import java.util.HashMap;
import java.util.Map;/*** ViewModelProvider 用于创建和管理 ViewModel 实例*/
public class ViewModelProvider {// 存储 ViewModel 实例的 ViewModelStoreprivate final ViewModelStore mViewModelStore;// 创建 ViewModel 的工厂private final Factory mFactory;/*** 使用给定的工厂和 ViewModelStore 创建 ViewModelProvider* @param store 存储 ViewModel 的 ViewModelStore* @param factory 创建 ViewModel 的工厂*/public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {mFactory = factory;mViewModelStore = store;}/*** 获取 ViewModel 实例的通用方法* @param modelClass ViewModel 的类* @param <T> ViewModel 的类型* @return ViewModel 实例*/@NonNull@MainThreadpublic <T extends ViewModel> T get(@NonNull Class<T> modelClass) {String canonicalName = modelClass.getCanonicalName();if (canonicalName == null) {throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");}return get(DEFAULT_KEY + ":" + canonicalName, modelClass);}/*** 使用指定的键获取 ViewModel 实例* @param key 用于标识 ViewModel 的键* @param modelClass ViewModel 的类* @param <T> ViewModel 的类型* @return ViewModel 实例*/@NonNull@MainThreadpublic <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {// 从 ViewModelStore 中获取 ViewModelViewModel viewModel = mViewModelStore.get(key);if (modelClass.isInstance(viewModel)) {// 如果已经存在且类型匹配,直接返回if (mFactory instanceof OnRequeryFactory) {((OnRequeryFactory) mFactory).onRequery(viewModel);}return (T) viewModel;} else {// 类型不匹配时,如果存在旧的 ViewModel 则清除if (viewModel != null) {// TODO: log a warning.}}// 使用工厂创建新的 ViewModelif (mFactory instanceof KeyedFactory) {viewModel = ((KeyedFactory) mFactory).create(key, modelClass);} else {viewModel = mFactory.create(modelClass);}// 将新创建的 ViewModel 存入 ViewModelStoremViewModelStore.put(key, viewModel);return (T) viewModel;}// 其他内部类和方法...
}

ViewModelProvider 的核心功能是通过工厂模式创建 ViewModel 实例,并将其存储在 ViewModelStore 中。这样可以确保在配置更改时,同一个 ViewModel 实例会被重复使用。

4.3 ViewModelStore 源码分析

ViewModelStore 是一个简单的存储类,用于存储 ViewModel 实例:

package androidx.lifecycle;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;import java.util.HashMap;
import java.util.Map;/*** ViewModelStore 用于存储 ViewModel 实例* 通常与 Activity 或 Fragment 关联*/
public class ViewModelStore {// 使用 HashMap 存储 ViewModel 实例private final HashMap<String, ViewModel> mMap = new HashMap<>();/*** 向存储中添加 ViewModel* @param key 用于标识 ViewModel 的键* @param viewModel ViewModel 实例*/final void put(String key, ViewModel viewModel) {// 检查是否已存在相同键的 ViewModel,如果存在则清除ViewModel oldViewModel = mMap.put(key, viewModel);if (oldViewModel != null) {oldViewModel.clear();}}/*** 从存储中获取 ViewModel* @param key 用于标识 ViewModel 的键* @return ViewModel 实例,如果不存在则返回 null*/final ViewModel get(String key) {return mMap.get(key);}/*** 清理存储中的所有 ViewModel* 调用每个 ViewModel 的 clear() 方法进行资源清理*/public final void clear() {for (ViewModel vm : mMap.values()) {vm.clear();}mMap.clear();}
}

ViewModelStore 的实现非常简单,主要使用一个 HashMap 来存储 ViewModel 实例,并提供了添加、获取和清理 ViewModel 的方法。

4.4 ViewModelProvider.Factory 源码分析

ViewModelProvider.Factory 是一个接口,定义了创建 ViewModel 的工厂方法:

package androidx.lifecycle;import androidx.annotation.NonNull;/*** 用于创建 ViewModel 的工厂接口*/
public interface Factory {/*** 创建给定类的 ViewModel 实例* @param modelClass ViewModel 的类* @param <T> ViewModel 的类型* @return ViewModel 实例*/@NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

ViewModelProvider 提供了几种默认的工厂实现:

  1. NewInstanceFactory:使用反射通过无参构造函数创建 ViewModel
/*** 默认的工厂实现,使用反射通过无参构造函数创建 ViewModel*/
public static class NewInstanceFactory implements Factory {@NonNull@Overridepublic <T extends ViewModel> T create(@NonNull Class<T> modelClass) {// 检查 ViewModel 类是否是 ViewModel 的子类try {return modelClass.newInstance();} catch (InstantiationException e) {throw new RuntimeException("Cannot create an instance of " + modelClass, e);} catch (IllegalAccessException e) {throw new RuntimeException("Cannot create an instance of " + modelClass, e);}}
}
  1. AndroidViewModelFactory:专门用于创建 AndroidViewModel 子类的工厂
/*** 用于创建 AndroidViewModel 子类的工厂* AndroidViewModel 需要 Application 作为参数*/
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {// 单例实例private static AndroidViewModelFactory sInstance;// 应用程序实例private final Application mApplication;/*** 获取 AndroidViewModelFactory 的单例实例* @param application 应用程序实例* @return AndroidViewModelFactory 实例*/@NonNullpublic static AndroidViewModelFactory getInstance(@NonNull Application application) {if (sInstance == null) {sInstance = new AndroidViewModelFactory(application);}return sInstance;}/*** 构造函数* @param application 应用程序实例*/public AndroidViewModelFactory(@NonNull Application application) {mApplication = application;}@NonNull@Overridepublic <T extends ViewModel> T create(@NonNull Class<T> modelClass) {// 检查 ViewModel 是否是 AndroidViewModel 的子类if (AndroidViewModel.class.isAssignableFrom(modelClass)) {// 尝试使用 Application 参数创建实例try {return modelClass.getConstructor(Application.class).newInstance(mApplication);} catch (NoSuchMethodException e) {throw new RuntimeException("Cannot create an instance of " + modelClass, e);} catch (IllegalAccessException e) {throw new RuntimeException("Cannot create an instance of " + modelClass, e);} catch (InstantiationException e) {throw new RuntimeException("Cannot create an instance of " + modelClass, e);} catch (InvocationTargetException e) {throw new RuntimeException("Cannot create an instance of " + modelClass, e);}}// 如果不是 AndroidViewModel 的子类,使用默认的工厂方法return super.create(modelClass);}
}

五、DataBinding 源码分析

5.1 DataBinding 生成的代码

当启用 DataBinding 后,Android 构建系统会为每个布局文件生成对应的 Binding 类。例如,对于布局文件 activity_main.xml,会生成 ActivityMainBinding 类。

下面是一个简化的生成代码示例:

// ActivityMainBinding.java (简化版)
public abstract class ActivityMainBinding extends ViewDataBinding {@NonNullpublic final TextView textView;@Bindableprotected User mUser;protected ActivityMainBinding(Object _bindingComponent, View _root,int _localFieldCount,TextView textView) {super(_bindingComponent, _root, _localFieldCount);this.textView = textView;}// 设置 User 对象的方法public abstract void setUser(@Nullable User user);// 获取 User 对象的方法@Nullablepublic abstract User getUser();
}

实际生成的代码会更加复杂,包含了数据绑定的具体实现逻辑。

5.2 DataBinding 初始化过程

DataBinding 的初始化过程主要发生在 Activity 或 Fragment 中:

// 在 Activity 中使用 DataBinding
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

ActivityMainBinding.inflate() 方法最终会调用生成的 Binding 类的静态方法:

// ActivityMainBinding.java (简化版)
public static ActivityMainBinding inflate(LayoutInflater inflater, ViewGroup root, boolean attachToRoot) {// 解析布局文件View rootView = inflater.inflate(R.layout.activity_main, root, false);if (attachToRoot) {root.addView(rootView);}return bind(rootView);
}public static ActivityMainBinding bind(View rootView) {// 获取布局中的各个视图TextView textView = rootView.findViewById(R.id.textView);// 创建并返回 Binding 实例return new ActivityMainBindingImpl(rootView, textView);
}

5.3 数据绑定表达式处理

DataBinding 使用表达式语言来处理布局文件中的绑定表达式。例如:

android:text="@{user.name}"

这种表达式会在生成的代码中被转换为具体的实现:

// ActivityMainBindingImpl.java (简化版)
private void executeBindings() {synchronized(this) {// 获取变量值User user = mUser;String userContentDescription = null;String userText = null;// 计算表达式的值if (user != null) {userContentDescription = user.getContentDescription();userText = user.getText();}// 更新视图updateRegistration(0, user);textView.setText(userText);textView.setContentDescription(userContentDescription);}
}

5.4 LiveData 绑定处理

当绑定 LiveData 对象时,DataBinding 会自动注册一个观察者:

// 简化版的 LiveData 绑定实现
private void registerLiveDataBindings(int localFieldId) {if (localFieldId == 0) {// 注册对 LiveData 的观察if (mUserLiveData != null) {if (mUserLiveDataObserver == null) {mUserLiveDataObserver = new Observer<User>() {@Overridepublic void onChanged(@Nullable User user) {mUser = user;// 数据变化时重新执行绑定requestRebind();}};}// 观察 LiveData 的变化mUserLiveData.observeForever(mUserLiveDataObserver);}}
}

当 LiveData 的值发生变化时,观察者会被触发,然后调用 requestRebind() 方法重新执行数据绑定。

5.5 双向绑定实现

双向绑定使用特殊的语法 @={} 实现,例如:

android:text="@={viewModel.inputText}"

双向绑定的实现涉及两个方向的数据流动:

  1. 从 ViewModel 到 View:与单向绑定相同
  2. 从 View 到 ViewModel:通过设置 View 的监听器实现

下面是一个简化的 EditText 双向绑定实现:

// 简化版的双向绑定实现
private void setupEditTextBinding(final EditText editText, final ObservableField<String> text) {// 设置初始值editText.setText(text.get());// 添加文本变化监听器editText.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {}@Overridepublic void afterTextChanged(Editable s) {// 当文本变化时,更新 ViewModel 中的值if (!s.toString().equals(text.get())) {text.set(s.toString());}}});// 添加对 ObservableField 的观察text.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {@Overridepublic void onPropertyChanged(Observable sender, int propertyId) {// 当 ViewModel 中的值变化时,更新 EditTextString newValue = text.get();if (!newValue.equals(editText.getText().toString())) {editText.setText(newValue);}}});
}

六、ViewModel 生命周期管理

6.1 ViewModel 与 Activity/Fragment 生命周期的关系

ViewModel 的生命周期与 Activity 或 Fragment 的生命周期密切相关,但又有所不同。下面是它们之间的关系图:

Activity/Fragment 生命周期              ViewModel 生命周期onCreate()                             创建 ViewModel|                                     ||                                     |
onStart()                              存在|                                     ||                                     |
onResume()                             存在|                                     ||                                     |
onPause()                              存在|                                     ||                                     |
onStop()                               存在|                                     ||                                     |
onDestroy() (非配置更改)               销毁 ViewModel

当 Activity 或 Fragment 因配置更改(如屏幕旋转)而重新创建时,ViewModel 不会被销毁,而是继续存在并被新的 Activity 或 Fragment 实例复用。

6.2 ViewModel 生命周期管理源码分析

ViewModel 的生命周期管理主要由 ViewModelStoreOwner 和 ViewModelStore 完成。

6.2.1 ViewModelStoreOwner 接口

ViewModelStoreOwner 是一个接口,定义了获取 ViewModelStore 的方法:

package androidx.lifecycle;import androidx.annotation.NonNull;/*** 实现此接口的类可以提供 ViewModelStore*/
public interface ViewModelStoreOwner {/*** 返回存储 ViewModel 的 ViewModelStore* @return ViewModelStore 实例*/@NonNullViewModelStore getViewModelStore();
}

Activity 和 Fragment 都实现了这个接口,因此它们可以提供 ViewModelStore。

6.2.2 ComponentActivity 中的实现

ComponentActivity 是 AndroidX 中所有 Activity 的基类,它实现了 ViewModelStoreOwner 接口:

public class ComponentActivity extends androidx.core.app.ComponentActivity implementsLifecycleOwner,ViewModelStoreOwner,SavedStateRegistryOwner,OnBackPressedDispatcherOwner {// ViewModelStore 实例private ViewModelStore mViewModelStore;// 其他代码...@NonNull@Overridepublic ViewModelStore getViewModelStore() {if (getApplication() == null) {throw new IllegalStateException("Your activity is not yet attached to the "+ "Application instance. You can't request ViewModel before onCreate call.");}if (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// 如果存在非配置实例,从其中获取 ViewModelStoremViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {// 如果没有非配置实例或其中没有 ViewModelStore,则创建新的mViewModelStore = new ViewModelStore();}}return mViewModelStore;}@Overridepublic final Object onRetainNonConfigurationInstance() {// 保留非配置实例Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;if (viewModelStore == null) {// 检查是否有之前保留的非配置实例NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}if (viewModelStore == null && custom == null) {return null;}NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore;return nci;}static final class NonConfigurationInstances {Object custom;ViewModelStore viewModelStore;}// 其他代码...
}
6.2.3 Fragment 中的实现

Fragment 也实现了 ViewModelStoreOwner 接口:

public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener,LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {// ViewModelStore 实例private ViewModelStore mViewModelStore;// 其他代码...@NonNull@Overridepublic ViewModelStore getViewModelStore() {if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}return mViewModelStore;}// 其他代码...void performDestroy() {mCalled = true;Lifecycle lifecycle = getLifecycle();lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);// 如果 Fragment 不是被保留,清理 ViewModelStoreif (!mRetaining) {if (mViewModelStore != null && !getActivity().isChangingConfigurations()) {mViewModelStore.clear();}// 其他清理操作...}}
}

6.3 配置更改时的 ViewModel 保留机制

当 Activity 因配置更改而重新创建时,ViewModel 的保留机制主要依赖于 Activity 的 onRetainNonConfigurationInstance() 方法。

// ComponentActivity.java
@Override
public final Object onRetainNonConfigurationInstance() {Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;if (viewModelStore == null) {// 检查是否有之前保留的非配置实例NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}if (viewModelStore == null && custom == null) {return null;}NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore;return nci;
}

当 Activity 重新创建时,会调用 getLastNonConfigurationInstance() 方法获取之前保留的实例:

// ComponentActivity.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {if (getApplication() == null) {throw new IllegalStateException("Your activity is not yet attached to the "+ "Application instance. You can't request ViewModel before onCreate call.");}if (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// 从保留的实例中获取 ViewModelStoremViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}return mViewModelStore;
}

这种机制确保了在配置更改时,ViewModel 实例不会被销毁,而是被保留并复用。

6.4 ViewModel 的销毁时机

ViewModel 的销毁发生在与之关联的 Activity 或 Fragment 真正销毁时(非配置更改导致的销毁)。

对于 Activity:

// ComponentActivity.java
@Override
protected void onDestroy() {super.onDestroy();if (mViewModelStore != null && !isChangingConfigurations()) {// 如果 Activity 不是因为配置更改而销毁,清理 ViewModelStoremViewModelStore.clear();}// 其他清理操作...
}

对于 Fragment:

// Fragment.java
void performDestroy() {mCalled = true;Lifecycle lifecycle = getLifecycle();lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);// 如果 Fragment 不是被保留,清理 ViewModelStoreif (!mRetaining) {if (mViewModelStore != null && !getActivity().isChangingConfigurations()) {mViewModelStore.clear();}// 其他清理操作...}
}

当 ViewModelStore 的 clear() 方法被调用时,会遍历所有存储的 ViewModel 并调用它们的 clear() 方法:

// ViewModelStore.java
public final void clear() {for (ViewModel vm : mMap.values()) {vm.clear();}mMap.clear();
}

ViewModel 的 clear() 方法会触发 onCleared() 回调,允许子类进行资源清理:

// ViewModel.java
final void clear() {mCleared = true;// 调用 onCleared() 方法,由子类实现具体的清理逻辑onCleared();
}

七、DataBinding 与 ViewModel 集成的最佳实践

7.1 使用 LiveData 与 DataBinding 结合

LiveData 与 DataBinding 的结合是实现数据驱动 UI 的最佳方式。LiveData 能够感知生命周期,并在数据变化时自动更新 UI。

// ViewModel 中使用 LiveData
public class MyViewModel extends ViewModel {private MutableLiveData<String> mUserName = new MutableLiveData<>();public LiveData<String> getUserName() {return mUserName;}public void fetchUserName() {// 从网络或数据库获取用户名// ...mUserName.setValue("新用户名");}
}
<!-- 布局文件中直接绑定 LiveData -->
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{viewModel.userName}" />

7.2 双向绑定的正确使用

双向绑定可以简化表单输入处理,但应谨慎使用,避免造成数据流向混乱。

// ViewModel 中使用 ObservableField 支持双向绑定
public class MyViewModel extends ViewModel {public final ObservableField<String> userName = new ObservableField<>("");public final ObservableField<String> password = new ObservableField<>("");public void login() {String username = userName.get();String pwd = password.get();// 执行登录逻辑}
}
<!-- 布局文件中使用双向绑定 -->
<EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@={viewModel.userName}" /><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@={viewModel.password}"android:inputType="textPassword" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="登录"android:onClick="@{() -> viewModel.login()}" />

7.3 避免在 ViewModel 中持有 Activity 或 View 的引用

ViewModel 的设计初衷是独立于 Activity 和 Fragment 的生命周期,因此不应该在 ViewModel 中持有 Activity 或 View 的引用,以免造成内存泄漏。

如果需要 Application 上下文,可以继承 AndroidViewModel:

public class MyViewModel extends AndroidViewModel {public MyViewModel(@NonNull Application application) {super(application);// 使用 application 上下文}// 其他代码...
}

7.4 使用自定义 BindingAdapter

自定义 BindingAdapter 可以扩展 DataBinding 的功能,实现更复杂的绑定逻辑。

// 自定义 BindingAdapter 示例
public class BindingAdapters {@BindingAdapter("imageUrl")public static void loadImage(ImageView view, String url) {Glide.with(view.getContext()).load(url).into(view);}@BindingAdapter("android:onScrollChanged")public static void setOnScrollChangedListener(NestedScrollView view, NestedScrollView.OnScrollChangeListener listener) {view.setOnScrollChangeListener(listener);}
}
<!-- 在布局文件中使用自定义 BindingAdapter -->
<ImageViewandroid:layout_width="100dp"android:layout_height="100dp"app:imageUrl="@{viewModel.imageUrl}" /><NestedScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"app:onScrollChanged="@{(v, scrollX, scrollY, oldScrollX, oldScrollY) -> viewModel.onScrollChanged(scrollY)}"><!-- 内容 -->
</NestedScrollView>

7.5 合理处理 ViewModel 中的异步操作

ViewModel 中经常需要执行异步操作,如网络请求或数据库查询。应该使用适当的方式处理这些异步操作,确保在 ViewModel 销毁时能够取消未完成的操作。

public class MyViewModel extends ViewModel {private CompositeDisposable mDisposables = new CompositeDisposable();public void fetchData() {Disposable disposable = apiService.getData().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(data -> mData.setValue(data),error -> mError.setValue(error.getMessage()));mDisposables.add(disposable);}@Overrideprotected void onCleared() {super.onCleared();// 清理未完成的异步操作mDisposables.clear();}
}

7.6 使用 ViewModel 作用域管理

ViewModel 可以有不同的作用域,如 Activity 级别或 Fragment 级别。根据需求选择合适的作用域可以避免数据共享问题。

// Activity 级别 ViewModel
ViewModelProvider(this).get(MyViewModel.class);// Fragment 级别 ViewModel
ViewModelProvider(requireActivity()).get(MyViewModel.class);

7.7 单元测试 ViewModel

ViewModel 应该是易于测试的,因为它不依赖于 Activity 或 Fragment。可以使用 JUnit 和 Mockito 等工具编写单元测试。

@RunWith(MockitoJUnitRunner.class)
public class MyViewModelTest {private MyViewModel mViewModel;@Mock private DataRepository mRepository;@Beforepublic void setup() {mViewModel = new MyViewModel(mRepository);}@Testpublic void testFetchData() {// 设置模拟数据Data mockData = new Data("测试数据");when(mRepository.fetchData()).thenReturn(Observable.just(mockData));// 执行测试mViewModel.fetchData();// 验证结果verify(mRepository).fetchData();assertEquals(mockData, mViewModel.getData().getValue());}
}

7.8 使用 ViewModel 工厂提供参数

当 ViewModel 需要参数时,可以使用 ViewModel 工厂来提供这些参数。

public class MyViewModelFactory implements ViewModelProvider.Factory {private final DataRepository mRepository;private final String mUserId;public MyViewModelFactory(DataRepository repository, String userId) {mRepository = repository;mUserId = userId;}@NonNull@Overridepublic <T extends ViewModel> T create(@NonNull Class<T> modelClass) {if (modelClass.isAssignableFrom(MyViewModel.class)) {return (T) new MyViewModel(mRepository, mUserId);}throw new IllegalArgumentException("Unknown ViewModel class");}
}
// 在 Activity 中使用工厂创建 ViewModel
MyViewModelFactory factory = new MyViewModelFactory(repository, userId);
MyViewModel viewModel = new ViewModelProvider(this, factory).get(MyViewModel.class);

八、常见问题与解决方案

8.1 内存泄漏问题

问题描述:如果在 ViewModel 中持有 Activity、Fragment 或 View 的引用,可能会导致内存泄漏。

解决方案

  1. 不要在 ViewModel 中持有 Activity、Fragment 或 View 的引用
  2. 如果需要上下文,可以继承 AndroidViewModel 并使用 Application 上下文
  3. 确保在 ViewModel 销毁时取消所有异步操作

8.2 LiveData 数据倒灌问题

问题描述:当 Activity 或 Fragment 因配置更改重建时,LiveData 可能会发送之前的值,导致数据倒灌。

解决方案

  1. 使用 SingleLiveEvent 包装 LiveData,确保每个值只被消费一次
  2. 使用 EventWrapper 类包装数据,添加消费状态标志
  3. 使用 LiveData 的扩展方法,如 Transformations.map() 过滤旧数据

8.3 双向绑定导致的无限循环问题

问题描述:在某些情况下,双向绑定可能会导致无限循环更新。

解决方案

  1. 在 ViewModel 中添加标志位,避免不必要的更新
  2. 使用 LiveData 的 distinctUntilChanged() 方法过滤重复数据
  3. 在数据处理逻辑中添加条件判断,避免循环更新

8.4 ViewModel 与协程结合的问题

问题描述:在 ViewModel 中使用协程时,如果不正确管理协程作用域,可能会导致内存泄漏或崩溃。

解决方案

  1. 使用 viewModelScope 管理协程,确保在 ViewModel 销毁时取消协程
  2. 在协程中捕获异常,避免因未处理的异常导致崩溃
  3. 使用 flow 替代 LiveData,利用 flow 的背压机制处理数据流

8.5 DataBinding 表达式复杂问题

问题描述:布局文件中的 DataBinding 表达式过于复杂,导致代码难以维护。

解决方案

  1. 将复杂的逻辑移到 ViewModel 或 BindingAdapter 中
  2. 使用计算属性或方法简化表达式
  3. 避免在布局文件中编写过多的业务逻辑

8.6 数据更新不及时问题

问题描述:DataBinding 有时不能及时更新 UI。

解决方案

  1. 确保在主线程更新 LiveData 或 ObservableField 的值
  2. 使用 LiveData 的 postValue() 方法在后台线程更新值
  3. 检查是否正确设置了 LifecycleOwner
  4. 确保在布局文件中正确绑定了数据

8.7 多模块项目中的 DataBinding 问题

问题描述:在多模块项目中使用 DataBinding 时,可能会遇到找不到生成类或绑定失败的问题。

解决方案

  1. 确保每个模块都启用了 DataBinding
  2. 检查布局文件的命名和位置是否正确
  3. 清理并重新构建项目
  4. 检查模块之间的依赖关系是否正确

8.8 ViewModel 作用域混淆问题

问题描述:在 Fragment 中使用 ViewModel 时,可能会混淆 Activity 级别和 Fragment 级别 ViewModel 的作用域。

解决方案

  1. 明确需要的 ViewModel 作用域
  2. 使用 ViewModelProvider(this) 获取 Fragment 级别的 ViewModel
  3. 使用 ViewModelProvider(requireActivity()) 获取 Activity 级别的 ViewModel
  4. 避免在不同作用域的 ViewModel 中存储相同类型的数据

九、高级应用场景

9.1 与 Navigation 组件结合

ViewModel 可以与 Navigation 组件结合,实现页面间的数据共享和传递。

// 在 SharedViewModel 中定义要共享的数据
public class SharedViewModel extends ViewModel {private final MutableLiveData<String> selectedItem = new MutableLiveData<>();public void selectItem(String item) {selectedItem.setValue(item);}public LiveData<String> getSelectedItem() {return selectedItem;}
}
// 在发送数据的 Fragment 中
public class ListFragment extends Fragment {private SharedViewModel sharedViewModel;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);}private void onItemClick(String item) {sharedViewModel.selectItem(item);Navigation.findNavController(requireView()).navigate(R.id.action_list_to_detail);}
}
// 在接收数据的 Fragment 中
public class DetailFragment extends Fragment {private SharedViewModel sharedViewModel;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View root = inflater.inflate(R.layout.fragment_detail, container, false);sharedViewModel.getSelectedItem().observe(getViewLifecycleOwner(), item -> {// 更新 UI});return root;}
}

9.2 与 Room 数据库结合

ViewModel 可以与 Room 数据库结合,实现数据的持久化存储和自动更新。

// 定义数据实体
@Entity(tableName = "users")
public class User {@PrimaryKeyprivate int id;private String name;private String email;// getter 和 setter 方法
}
// 定义 DAO
@Dao
public interface UserDao {@Query("SELECT * FROM users")LiveData<List<User>> getAllUsers();@Insertvoid insert(User user);@Updatevoid update(User user);@Deletevoid delete(User user);
}
// 定义 Repository
public class UserRepository {private UserDao mUserDao;private LiveData<List<User>> mAllUsers;public UserRepository(Application application) {AppDatabase db = AppDatabase.getDatabase(application);mUserDao = db.userDao();mAllUsers = mUserDao.getAllUsers();}public LiveData<List<User>> getAllUsers() {return mAllUsers;}public void insert(User user) {new insertAsyncTask(mUserDao).execute(user);}private static class insertAsyncTask extends AsyncTask<User, Void, Void> {private UserDao mAsyncTaskDao;insertAsyncTask(UserDao dao) {mAsyncTaskDao = dao;}@Overrideprotected Void doInBackground(final User... params) {mAsyncTaskDao.insert(params[0]);return null;}}
}
// 定义 ViewModel
public class UserViewModel extends AndroidViewModel {private UserRepository mRepository;private LiveData<List<User>> mAllUsers;public UserViewModel(Application application) {super(application);mRepository = new UserRepository(application);mAllUsers = mRepository.getAllUsers();}public LiveData<List<User>> getAllUsers() {return mAllUsers;}public void insert(User user) {mRepository.insert(user);}
}
// 在 Activity 中使用
public class MainActivity extends AppCompatActivity {private UserViewModel mUserViewModel;
// 在 Activity 中使用
public class MainActivity extends AppCompatActivity {private UserViewModel mUserViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取 ViewModel 实例mUserViewModel = new ViewModelProvider(this).get(UserViewModel.class);// 观察用户数据变化mUserViewModel.getAllUsers().observe(this, users -> {// 更新 UIupdateUserList(users);});// 添加用户按钮点击事件findViewById(R.id.add_user_button).setOnClickListener(v -> {User user = new User();user.setName("新用户");user.setEmail("newuser@example.com");mUserViewModel.insert(user);});}private void updateUserList(List<User> users) {// 更新用户列表 UI}
}

9.3 与 WorkManager 结合

ViewModel 可以与 WorkManager 结合,实现后台任务的管理和状态跟踪。

// 定义 Worker 类
public class MyWorker extends Worker {public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {super(context, params);}@NonNull@Overridepublic Result doWork() {// 执行后台任务try {// 模拟耗时操作Thread.sleep(5000);// 返回成功结果return Result.success();} catch (InterruptedException e) {e.printStackTrace();// 返回失败结果return Result.failure();}}
}
// 定义 ViewModel
public class MyViewModel extends ViewModel {private WorkManager mWorkManager;private LiveData<WorkInfo> mWorkInfo;public MyViewModel() {mWorkManager = WorkManager.getInstance(ApplicationProvider.getApplicationContext());}public void startWork() {// 创建一次性工作请求OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class).build();// 启动工作mWorkManager.enqueue(workRequest);// 观察工作状态mWorkInfo = mWorkManager.getWorkInfoByIdLiveData(workRequest.getId());}public LiveData<WorkInfo> getWorkInfo() {return mWorkInfo;}
}
// 在 Activity 中使用
public class MainActivity extends AppCompatActivity {private MyViewModel mViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mViewModel = new ViewModelProvider(this).get(MyViewModel.class);// 观察工作状态mViewModel.getWorkInfo().observe(this, workInfo -> {if (workInfo != null) {WorkInfo.State state = workInfo.getState();// 更新 UI 显示工作状态updateWorkStatus(state);}});// 启动工作按钮点击事件findViewById(R.id.start_work_button).setOnClickListener(v -> {mViewModel.startWork();});}private void updateWorkStatus(WorkInfo.State state) {// 更新工作状态 UI}
}

9.4 与 Hilt 依赖注入结合

ViewModel 可以与 Hilt 依赖注入框架结合,实现依赖的自动注入。

首先,在项目中添加 Hilt 依赖:

// 项目级 build.gradle
buildscript {ext.hilt_version = '2.44'dependencies {classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"}
}// 应用级 build.gradle
plugins {id 'com.google.dagger.hilt.android'
}android {// 其他配置...
}dependencies {implementation "com.google.dagger:hilt-android:$hilt_version"kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}

创建应用类并添加 @HiltAndroidApp 注解:

import dagger.hilt.android.HiltAndroidApp;
import android.app.Application;@HiltAndroidApp
public class MyApplication extends Application {// 应用类代码
}

创建需要注入的 Repository:

import javax.inject.Inject;public class DataRepository {@Injectpublic DataRepository() {// 初始化 Repository}public LiveData<String> fetchData() {// 获取数据的方法MutableLiveData<String> data = new MutableLiveData<>();// 模拟数据加载data.setValue("从 Repository 获取的数据");return data;}
}

创建 ViewModel 并使用 @HiltViewModel 和 @Inject 注解:

import androidx.lifecycle.ViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import dagger.hilt.android.lifecycle.HiltViewModel;
import javax.inject.Inject;@HiltViewModel
public class MyViewModel extends ViewModel {private DataRepository repository;private MutableLiveData<String> data;@Injectpublic MyViewModel(DataRepository repository) {this.repository = repository;data = new MutableLiveData<>();loadData();}private void loadData() {// 从 Repository 获取数据repository.fetchData().observeForever(new Observer<String>() {@Overridepublic void onChanged(String s) {data.setValue(s);}});}public LiveData<String> getData() {return data;}
}

在 Activity 中使用 Hilt 注入 ViewModel:

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.lifecycle.ViewModelProvider;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {private MyViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 使用 Hilt 提供的 ViewModel 工厂viewModel = new ViewModelProvider(this).get(MyViewModel.class);// 观察数据变化viewModel.getData().observe(this, new Observer<String>() {@Overridepublic void onChanged(String s) {// 更新 UI}});}
}

9.5 与 Kotlin Flow 结合

ViewModel 可以与 Kotlin Flow 结合,处理异步数据流。

首先,确保项目中添加了 Kotlin Flow 依赖:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"

创建 ViewModel 并使用 Flow:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launchclass MyViewModel : ViewModel() {// 使用 StateFlow 作为数据流private val _uiState = MutableStateFlow<UiState>(UiState.Loading)val uiState: StateFlow<UiState> = _uiState// 定义 UI 状态的密封类sealed class UiState {object Loading : UiState()data class Success(val data: String) : UiState()data class Error(val message: String) : UiState()}init {// 初始化时加载数据loadData()}// 使用 Flow 模拟数据加载private fun loadData() {viewModelScope.launch {try {// 模拟耗时操作delay(2000)// 获取数据val data = fetchDataFromNetwork()// 更新 UI 状态为成功_uiState.value = UiState.Success(data)} catch (e: Exception) {// 更新 UI 状态为错误_uiState.value = UiState.Error(e.message ?: "未知错误")}}}// 模拟网络请求的挂起函数private suspend fun fetchDataFromNetwork(): String {// 实际应用中这里会进行网络请求return "从网络获取的数据"}// 提供刷新数据的方法fun refreshData() {loadData()}
}

在 Activity 中使用:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScopeclass MainActivity : AppCompatActivity() {private lateinit var viewModel: MyViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 获取 ViewModel 实例viewModel = ViewModelProvider(this).get(MyViewModel::class.java)// 观察 UI 状态变化lifecycleScope.launch {viewModel.uiState.collect { state ->when (state) {is MyViewModel.UiState.Loading -> showLoading()is MyViewModel.UiState.Success -> showData(state.data)is MyViewModel.UiState.Error -> showError(state.message)}}}// 设置刷新按钮点击事件findViewById<Button>(R.id.refresh_button).setOnClickListener {viewModel.refreshData()}}private fun showLoading() {// 显示加载状态}private fun showData(data: String) {// 显示数据}private fun showError(message: String) {// 显示错误信息}
}

9.6 与 Paging 3 库结合

ViewModel 可以与 Paging 3 库结合,实现大数据集的分页加载。

首先,添加 Paging 3 依赖:

implementation "androidx.paging:paging-runtime:3.1.1"
// 如果使用 Kotlin Flow
implementation "androidx.paging:paging-runtime-ktx:3.1.1"

创建数据模型和数据源:

// 数据模型
data class Item(val id: Int, val name: String, val description: String)// 数据源
class ItemDataSource(private val apiService: ApiService) : PagingSource<Int, Item>() {override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {return try {// 获取当前页码,默认为第一页val page = params.key ?: 1// 加载数据val response = apiService.getItems(page, params.loadSize)// 计算前一页和下一页的页码val prevKey = if (page == 1) null else page - 1val nextKey = if (response.isEmpty()) null else page + 1// 返回加载结果LoadResult.Page(data = response,prevKey = prevKey,nextKey = nextKey)} catch (e: Exception) {// 处理错误LoadResult.Error(e)}}// 用于重置 PagingSource 的方法override fun getRefreshKey(state: PagingState<Int, Item>): Int? {return state.anchorPosition?.let { anchorPosition ->val anchorPage = state.closestPageToPosition(anchorPosition)anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)}}
}// 数据仓库
class ItemRepository(private val apiService: ApiService) {fun getItems(): Flow<PagingData<Item>> {return Pager(config = PagingConfig(pageSize = 20,       // 每页加载的数量enablePlaceholders = true,  // 是否启用占位符prefetchDistance = 5  // 距离最后一项多远时预加载下一页),pagingSourceFactory = { ItemDataSource(apiService) }).flow}
}

创建 ViewModel:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flowclass ItemViewModel(private val repository: ItemRepository) : ViewModel() {// 获取分页数据流val items: Flow<PagingData<Item>> = repository.getItems().cachedIn(viewModelScope)  // 缓存数据流,避免配置更改时重新加载
}

在 Activity 中使用:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launchclass MainActivity : AppCompatActivity() {private lateinit var viewModel: ItemViewModelprivate lateinit var adapter: ItemAdapter@OptIn(ExperimentalPagingApi::class)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 初始化 RecyclerView 和 Adapterval recyclerView = findViewById<RecyclerView>(R.id.recyclerView)recyclerView.layoutManager = LinearLayoutManager(this)adapter = ItemAdapter()recyclerView.adapter = adapter.withLoadStateFooter(footer = ItemLoadStateAdapter { adapter.retry() })// 获取 ViewModelviewModel = ViewModelProvider(this).get(ItemViewModel::class.java)// 观察分页数据lifecycleScope.launch {viewModel.items.collectLatest { pagingData ->adapter.submitData(pagingData)}}// 监听加载状态lifecycleScope.launch {adapter.loadStateFlow.collect { loadState ->// 处理加载状态,如显示加载动画、错误信息等when (loadState.refresh) {is LoadState.Loading -> showLoading()is LoadState.NotLoading -> hideLoading()is LoadState.Error -> showError((loadState.refresh as LoadState.Error).error)}}}}private fun showLoading() {// 显示加载状态}private fun hideLoading() {// 隐藏加载状态}private fun showError(error: Throwable) {// 显示错误信息}
}

创建 Adapter:

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.databindingdemo.databinding.ItemLayoutBindingclass ItemAdapter : PagingDataAdapter<Item, ItemAdapter.ItemViewHolder>(ITEM_COMPARATOR) {companion object {private val ITEM_COMPARATOR = object : DiffUtil.ItemCallback<Item>() {override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {return oldItem.id == newItem.id}override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {return oldItem == newItem}}}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {val binding = ItemLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false)return ItemViewHolder(binding)}override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {val item = getItem(position)if (item != null) {holder.bind(item)}}class ItemViewHolder(private val binding: ItemLayoutBinding) :RecyclerView.ViewHolder(binding.root) {fun bind(item: Item) {binding.apply {tvName.text = item.nametvDescription.text = item.descriptionexecutePendingBindings()}}}
}// 加载状态适配器
class ItemLoadStateAdapter(private val retry: () -> Unit) :LoadStateAdapter<ItemLoadStateAdapter.LoadStateViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {val binding = ItemLoadStateBinding.inflate(LayoutInflater.from(parent.context),parent,false)return LoadStateViewHolder(binding)}override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {holder.bind(loadState)}inner class LoadStateViewHolder(private val binding: ItemLoadStateBinding) :RecyclerView.ViewHolder(binding.root) {init {binding.btnRetry.setOnClickListener { retry.invoke() }}fun bind(loadState: LoadState) {binding.apply {progressBar.isVisible = loadState is LoadState.LoadingbtnRetry.isVisible = loadState is LoadState.ErrortvError.isVisible = loadState is LoadState.Errorif (loadState is LoadState.Error) {tvError.text = loadState.error.localizedMessage}}}}
}

9.7 与 RxJava 结合

ViewModel 可以与 RxJava 结合,处理异步数据流。

首先,添加 RxJava 依赖:

implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'

创建 ViewModel:

import androidx.lifecycle.ViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.concurrent.TimeUnit;public class MyViewModel extends ViewModel {private MutableLiveData<String> mData = new MutableLiveData<>();private MutableLiveData<Boolean> mLoading = new MutableLiveData<>(false);private MutableLiveData<String> mError = new MutableLiveData<>();private CompositeDisposable mDisposables = new CompositeDisposable();public LiveData<String> getData() {return mData;}public LiveData<Boolean> getLoading() {return mLoading;}public LiveData<String> getError() {return mError;}public void fetchData() {mLoading.setValue(true);// 创建一个 Observable,模拟网络请求Observable.just("RxJava Data").delay(2, TimeUnit.SECONDS)  // 模拟网络延迟.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {mLoading.setValue(false);mData.setValue(data);},error -> {mLoading.setValue(false);mError.setValue(error.getMessage());}).dispose();}@Overrideprotected void onCleared() {super.onCleared();// 清理所有订阅,防止内存泄漏mDisposables.clear();}
}

在 Activity 中使用:

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.lifecycle.ViewModelProvider;
import android.widget.TextView;
import android.widget.Button;
import android.widget.ProgressBar;public class MainActivity extends AppCompatActivity {private MyViewModel mViewModel;private TextView mTextView;private ProgressBar mProgressBar;private Button mButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTextView = findViewById(R.id.textView);mProgressBar = findViewById(R.id.progressBar);mButton = findViewById(R.id.button);mViewModel = new ViewModelProvider(this).get(MyViewModel.class);// 观察数据变化mViewModel.getData().observe(this, data -> {mTextView.setText(data);});// 观察加载状态mViewModel.getLoading().observe(this, isLoading -> {if (isLoading) {mProgressBar.setVisibility(ProgressBar.VISIBLE);} else {mProgressBar.setVisibility(ProgressBar.GONE);}});// 观察错误信息mViewModel.getError().observe(this, error -> {mTextView.setText(error);});// 设置按钮点击事件mButton.setOnClickListener(v -> {mViewModel.fetchData();});}
}

9.8 与 Koin 依赖注入结合

ViewModel 可以与 Koin 依赖注入框架结合,实现依赖的自动注入。

首先,添加 Koin 依赖:

implementation "io.insert-koin:koin-android:3.3.3"
implementation "io.insert-koin:koin-androidx-viewmodel:3.3.3"

创建 Koin 模块:

import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.moduleval appModule = module {// 提供 Repository 实例single { DataRepository(get()) }// 提供 ViewModel 实例viewModel { MyViewModel(get()) }
}

初始化 Koin:

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoinclass MyApplication : Application() {override fun onCreate() {super.onCreate()// 启动 KoinstartKoin {androidLogger()androidContext(this@MyApplication)modules(appModule)}}
}

创建 Repository:

class DataRepository(private val apiService: ApiService) {suspend fun fetchData(): String {// 模拟网络请求delay(2000)return "从网络获取的数据"}
}

创建 ViewModel:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launchclass MyViewModel(private val repository: DataRepository) : ViewModel() {private val _data = MutableLiveData<String>()val data: LiveData<String> = _datainit {// 初始化时加载数据loadData()}private fun loadData() {viewModelScope.launch {try {// 获取数据val result = repository.fetchData()// 更新数据_data.value = result} catch (e: Exception) {// 处理错误e.printStackTrace()}}}
}

在 Activity 中使用:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import org.koin.androidx.viewmodel.ext.android.viewModelclass MainActivity : AppCompatActivity() {// 使用 Koin 获取 ViewModelprivate val viewModel: MyViewModel by viewModel()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 观察数据变化viewModel.data.observe(this) { data ->// 更新 UIfindViewById<TextView>(R.id.textView).text = data}}
}

9.9 与 Compose 结合

ViewModel 可以与 Jetpack Compose 结合,为 Compose UI 提供数据和状态管理。

首先,添加 Compose 依赖:

implementation "androidx.compose.ui:ui:1.4.3"
implementation "androidx.compose.material:material:1.4.3"
implementation "androidx.compose.ui:ui-tooling-preview:1.4.3"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"

创建 ViewModel:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launchclass ComposeViewModel : ViewModel() {private val _uiState = MutableStateFlow(UiState())val uiState: StateFlow<UiState> = _uiStatedata class UiState(val isLoading: Boolean = false,val data: String = "",val error: String = "")fun fetchData() {viewModelScope.launch {// 更新状态为加载中_uiState.value = uiState.value.copy(isLoading = true)try {// 模拟网络请求delay(2000)// 更新状态为成功_uiState.value = uiState.value.copy(isLoading = false,data = "从网络获取的数据",error = "")} catch (e: Exception) {// 更新状态为错误_uiState.value = uiState.value.copy(isLoading = false,error = e.message ?: "未知错误")}}}
}

创建 Compose UI:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle@Composable
fun MyScreen(viewModel: ComposeViewModel = androidx.lifecycle.viewmodel.compose.viewModel()) {val uiState by viewModel.uiState.collectAsStateWithLifecycle()Scaffold(topBar = {TopAppBar(title = { Text("Compose + ViewModel") })}) { paddingValues ->Column(modifier = Modifier.fillMaxSize().padding(paddingValues).padding(16.dp),horizontalAlignment = Alignment.CenterHorizontally) {if (uiState.isLoading) {CircularProgressIndicator()} else if (uiState.error.isNotEmpty()) {Text(text = uiState.error,color = MaterialTheme.colors.error,textAlign = TextAlign.Center)} else if (uiState.data.isNotEmpty()) {Text(text = uiState.data,style = MaterialTheme.typography.body1,textAlign = TextAlign.Center)}Spacer(modifier = Modifier.height(16.dp))Button(onClick = { viewModel.fetchData() },enabled = !uiState.isLoading) {Text("加载数据")}}}
}@Preview(showBackground = true)
@Composable
fun DefaultPreview() {MyScreen()
}

在 Activity 中设置 Compose 内容:

import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import android.os.Bundleclass ComposeActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {MyScreen()}}
}

9.10 与 MultiViewModel 结合

在某些复杂场景中,可能需要在同一个 Activity 或 Fragment 中使用多个 ViewModel。

创建多个 ViewModel:

// 第一个 ViewModel
public class UserViewModel extends ViewModel {private MutableLiveData<User> mUser = new MutableLiveData<>();public LiveData<User> getUser() {return mUser;}public void loadUser(String userId) {// 加载用户数据// ...mUser.setValue(new User(userId, "用户名", "用户邮箱"));}
}// 第二个 ViewModel
public class OrderViewModel extends ViewModel {private MutableLiveData<List<Order>> mOrders = new MutableLiveData<>();public LiveData<List<Order>> getOrders() {return mOrders;}public void loadOrders(String userId) {// 加载订单数据// ...List<Order> orders = new ArrayList<>();orders.add(new Order("1", userId, "订单1"));orders.add(new Order("2", userId, "订单2"));mOrders.setValue(orders);}
}

在 Activity 中使用多个 ViewModel:

public class MainActivity extends AppCompatActivity {private UserViewModel mUserViewModel;private OrderViewModel mOrderViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取 UserViewModelmUserViewModel = new ViewModelProvider(this).get(UserViewModel.class);// 获取 OrderViewModelmOrderViewModel = new ViewModelProvider(this).get(OrderViewModel.class);// 观察用户数据mUserViewModel.getUser().observe(this, user -> {// 更新用户信息 UIupdateUserInfo(user);// 用户数据加载完成后,加载订单数据mOrderViewModel.loadOrders(user.getId());});// 观察订单数据mOrderViewModel.getOrders().observe(this, orders -> {// 更新订单列表 UIupdateOrderList(orders);});// 加载用户数据mUserViewModel.loadUser("123");}private void updateUserInfo(User user) {// 更新用户信息 UI}private void updateOrderList(List<Order> orders) {// 更新订单列表 UI}
}

在布局文件中可以根据需要绑定不同 ViewModel 的数据:

<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="userViewModel"type="com.example.UserViewModel" /><variablename="orderViewModel"type="com.example.OrderViewModel" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><!-- 用户信息部分 --><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{userViewModel.user.name}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{userViewModel.user.email}" /><!-- 订单列表部分 --><RecyclerViewandroid:layout_width="match_parent"android:layout_height="match_parent"app:items="@{orderViewModel.orders}" /></LinearLayout>
</layout>

十、性能优化与最佳实践

10.1 避免过度使用 DataBinding

虽然 DataBinding 可以简化代码,但过度使用可能会导致性能问题和代码难以维护。以下是一些建议:

  1. 避免复杂表达式:布局文件中的表达式应该尽量简单,复杂的逻辑应该移到 ViewModel 或 BindingAdapter 中。
  2. 限制双向绑定的使用:双向绑定虽然方便,但可能会导致性能问题和数据流向混乱,应谨慎使用。
  3. 避免在循环中使用复杂绑定:在 RecyclerView 的 item 布局中,避免使用复杂的绑定表达式,以免影响滚动性能。

10.2 优化 LiveData 更新频率

LiveData 的更新可能会触发 UI 重绘,过于频繁的更新会影响性能。以下是一些优化建议:

  1. 合并频繁更新:如果有多个相关的数据需要更新,可以考虑合并这些更新,减少 UI 重绘次数。
  2. 使用 distinctUntilChanged():对于 Flow 或 LiveData,可以使用 distinctUntilChanged() 过滤重复数据,避免不必要的 UI 更新。
  3. 避免在循环中更新 LiveData:在循环中更新 LiveData 会导致频繁的 UI 更新,应尽量避免。

10.3 合理管理 ViewModel 生命周期

ViewModel 的生命周期应该与对应的 UI 组件相匹配,避免不必要的内存占用。以下是一些建议:

  1. 及时清理资源:在 ViewModel 的 onCleared() 方法中清理资源,如取消网络请求、关闭数据库连接等。
  2. 避免持有大对象:ViewModel 中应避免持有大对象,如 Activity、Context 或大型数据集合,以免造成内存泄漏。
  3. 使用适当的作用域:根据需要选择合适的 ViewModel 作用域,如 Activity 级别或 Fragment 级别。

10.4 优化 BindingAdapter

BindingAdapter 可以提高代码的复用性,但不当使用可能会影响性能。以下是一些优化建议:

  1. 避免重复创建对象:在 BindingAdapter 中避免重复创建对象,特别是在循环中使用的 BindingAdapter。
  2. 使用 @JvmStatic 注解:对于 Java 中的 BindingAdapter 方法,使用 @JvmStatic 注解可以避免创建额外的对象。
  3. 检查参数变化:在 BindingAdapter 中检查参数是否变化,只有在参数变化时才执行更新操作。
@BindingAdapter("imageUrl")
public static void loadImage(ImageView view, String url) {// 检查 URL 是否变化,避免不必要的加载if (view.getTag(R.id.image_url_tag) == null || !view.getTag(R.id.image_url_tag).equals(url)) {view.setTag(R.id.image_url_tag, url);Glide.with(view.getContext()).load(url).into(view);}
}

10.5 优化布局文件

布局文件的结构和复杂度会影响 DataBinding 的性能。以下是一些优化建议:

  1. 减少布局嵌套:过深的布局嵌套会增加视图渲染的时间,应尽量扁平化布局结构。