深入理解 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 的集成主要通过以下步骤实现:
- 在布局文件中声明 ViewModel 变量
- 在 Activity 或 Fragment 中设置 ViewModel 实例
- 在布局文件中使用表达式绑定 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 的集成基于以下核心机制:
- 表达式语言:DataBinding 使用一种简单的表达式语言来绑定数据和方法。例如:
android:text="@{viewModel.userName}"
- LiveData 支持:DataBinding 能够自动观察 LiveData 的变化,并在数据更新时自动更新 UI。
- 双向绑定:除了单向绑定,DataBinding 还支持双向绑定,例如在 EditText 中:
android:text="@={viewModel.inputText}"
- 事件绑定:可以直接在布局文件中绑定 ViewModel 中的方法作为事件处理程序,例如:
android:onClick="@{() -> viewModel.doSomething()}"
3.3 优势分析
将 DataBinding 与 ViewModel 集成带来了以下优势:
- 代码简洁:减少了 Activity 和 Fragment 中的样板代码,使代码更加清晰。
- 视图与逻辑分离:遵循 MVVM 架构模式,使视图和业务逻辑完全分离。
- 自动数据更新:通过 LiveData 和 DataBinding 的结合,实现了数据到 UI 的自动更新。
- 配置更改处理:ViewModel 在配置更改时保持数据,避免了数据丢失和重复加载。
- 易于测试: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 提供了几种默认的工厂实现:
- 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);}}
}
- 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}"
双向绑定的实现涉及两个方向的数据流动:
- 从 ViewModel 到 View:与单向绑定相同
- 从 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 的引用,可能会导致内存泄漏。
解决方案:
- 不要在 ViewModel 中持有 Activity、Fragment 或 View 的引用
- 如果需要上下文,可以继承 AndroidViewModel 并使用 Application 上下文
- 确保在 ViewModel 销毁时取消所有异步操作
8.2 LiveData 数据倒灌问题
问题描述:当 Activity 或 Fragment 因配置更改重建时,LiveData 可能会发送之前的值,导致数据倒灌。
解决方案:
- 使用 SingleLiveEvent 包装 LiveData,确保每个值只被消费一次
- 使用 EventWrapper 类包装数据,添加消费状态标志
- 使用 LiveData 的扩展方法,如 Transformations.map() 过滤旧数据
8.3 双向绑定导致的无限循环问题
问题描述:在某些情况下,双向绑定可能会导致无限循环更新。
解决方案:
- 在 ViewModel 中添加标志位,避免不必要的更新
- 使用 LiveData 的 distinctUntilChanged() 方法过滤重复数据
- 在数据处理逻辑中添加条件判断,避免循环更新
8.4 ViewModel 与协程结合的问题
问题描述:在 ViewModel 中使用协程时,如果不正确管理协程作用域,可能会导致内存泄漏或崩溃。
解决方案:
- 使用 viewModelScope 管理协程,确保在 ViewModel 销毁时取消协程
- 在协程中捕获异常,避免因未处理的异常导致崩溃
- 使用 flow 替代 LiveData,利用 flow 的背压机制处理数据流
8.5 DataBinding 表达式复杂问题
问题描述:布局文件中的 DataBinding 表达式过于复杂,导致代码难以维护。
解决方案:
- 将复杂的逻辑移到 ViewModel 或 BindingAdapter 中
- 使用计算属性或方法简化表达式
- 避免在布局文件中编写过多的业务逻辑
8.6 数据更新不及时问题
问题描述:DataBinding 有时不能及时更新 UI。
解决方案:
- 确保在主线程更新 LiveData 或 ObservableField 的值
- 使用 LiveData 的 postValue() 方法在后台线程更新值
- 检查是否正确设置了 LifecycleOwner
- 确保在布局文件中正确绑定了数据
8.7 多模块项目中的 DataBinding 问题
问题描述:在多模块项目中使用 DataBinding 时,可能会遇到找不到生成类或绑定失败的问题。
解决方案:
- 确保每个模块都启用了 DataBinding
- 检查布局文件的命名和位置是否正确
- 清理并重新构建项目
- 检查模块之间的依赖关系是否正确
8.8 ViewModel 作用域混淆问题
问题描述:在 Fragment 中使用 ViewModel 时,可能会混淆 Activity 级别和 Fragment 级别 ViewModel 的作用域。
解决方案:
- 明确需要的 ViewModel 作用域
- 使用 ViewModelProvider(this) 获取 Fragment 级别的 ViewModel
- 使用 ViewModelProvider(requireActivity()) 获取 Activity 级别的 ViewModel
- 避免在不同作用域的 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 可以简化代码,但过度使用可能会导致性能问题和代码难以维护。以下是一些建议:
- 避免复杂表达式:布局文件中的表达式应该尽量简单,复杂的逻辑应该移到 ViewModel 或 BindingAdapter 中。
- 限制双向绑定的使用:双向绑定虽然方便,但可能会导致性能问题和数据流向混乱,应谨慎使用。
- 避免在循环中使用复杂绑定:在 RecyclerView 的 item 布局中,避免使用复杂的绑定表达式,以免影响滚动性能。
10.2 优化 LiveData 更新频率
LiveData 的更新可能会触发 UI 重绘,过于频繁的更新会影响性能。以下是一些优化建议:
- 合并频繁更新:如果有多个相关的数据需要更新,可以考虑合并这些更新,减少 UI 重绘次数。
- 使用 distinctUntilChanged():对于 Flow 或 LiveData,可以使用 distinctUntilChanged() 过滤重复数据,避免不必要的 UI 更新。
- 避免在循环中更新 LiveData:在循环中更新 LiveData 会导致频繁的 UI 更新,应尽量避免。
10.3 合理管理 ViewModel 生命周期
ViewModel 的生命周期应该与对应的 UI 组件相匹配,避免不必要的内存占用。以下是一些建议:
- 及时清理资源:在 ViewModel 的 onCleared() 方法中清理资源,如取消网络请求、关闭数据库连接等。
- 避免持有大对象:ViewModel 中应避免持有大对象,如 Activity、Context 或大型数据集合,以免造成内存泄漏。
- 使用适当的作用域:根据需要选择合适的 ViewModel 作用域,如 Activity 级别或 Fragment 级别。
10.4 优化 BindingAdapter
BindingAdapter 可以提高代码的复用性,但不当使用可能会影响性能。以下是一些优化建议:
- 避免重复创建对象:在 BindingAdapter 中避免重复创建对象,特别是在循环中使用的 BindingAdapter。
- 使用 @JvmStatic 注解:对于 Java 中的 BindingAdapter 方法,使用 @JvmStatic 注解可以避免创建额外的对象。
- 检查参数变化:在 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 的性能。以下是一些优化建议:
- 减少布局嵌套:过深的布局嵌套会增加视图渲染的时间,应尽量扁平化布局结构。