深入理解 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>
<variable
name="user"
type="com.example.User" />
</data>
<TextView
android: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;
@Override
protected 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>
<variable
name="viewModel"
type="com.example.MyViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.userName}" />
<Button
android: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<>("初始名称");
// 获取用户名的 LiveData
public LiveData<String> getUserName() {
return userName;
}
// 更新用户名的方法
public void updateUserName() {
userName.setValue("新名称");
}
}
// MainActivity.java
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化 DataBinding
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 获取 ViewModel 实例
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
// 设置 ViewModel 到 DataBinding
binding.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 实例的 ViewModelStore
private 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
@MainThread
public <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
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 从 ViewModelStore 中获取 ViewModel
ViewModel 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.
}
}
// 使用工厂创建新的 ViewModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
// 将新创建的 ViewModel 存入 ViewModelStore
mViewModelStore.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
@Override
public <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 实例
*/
@NonNull
public 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
@Override
public <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 {
@NonNull
public final TextView textView;
@Bindable
protected 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 对象的方法
@Nullable
public 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>() {
@Override
public 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() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
// 当文本变化时,更新 ViewModel 中的值
if (!s.toString().equals(text.get())) {
text.set(s.toString());
}
}
});
// 添加对 ObservableField 的观察
text.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
// 当 ViewModel 中的值变化时,更新 EditText
String 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 实例
*/
@NonNull
ViewModelStore getViewModelStore();
}
Activity 和 Fragment 都实现了这个接口,因此它们可以提供 ViewModelStore。
6.2.2 ComponentActivity 中的实现
ComponentActivity 是 AndroidX 中所有 Activity 的基类,它实现了 ViewModelStoreOwner 接口:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
// ViewModelStore 实例
private ViewModelStore mViewModelStore;
// 其他代码...
@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) {
// 如果存在非配置实例,从其中获取 ViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
// 如果没有非配置实例或其中没有 ViewModelStore,则创建新的
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
@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;
}
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
@Override
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
return mViewModelStore;
}
// 其他代码...
void performDestroy() {
mCalled = true;
Lifecycle lifecycle = getLifecycle();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
// 如果 Fragment 不是被保留,清理 ViewModelStore
if (!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) {
// 从保留的实例中获取 ViewModelStore
mViewModelStore = 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 不是因为配置更改而销毁,清理 ViewModelStore
mViewModelStore.clear();
}
// 其他清理操作...
}
对于 Fragment:
// Fragment.java
void performDestroy() {
mCalled = true;
Lifecycle lifecycle = getLifecycle();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
// 如果 Fragment 不是被保留,清理 ViewModelStore
if (!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 -->
<TextView
android: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();
// 执行登录逻辑
}
}
<!-- 布局文件中使用双向绑定 -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.userName}" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.password}"
android:inputType="textPassword" />
<Button
android: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 -->
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:imageUrl="@{viewModel.imageUrl}" />
<NestedScrollView
android: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);
}
@Override
protected 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;
@Before
public void setup() {
mViewModel = new MyViewModel(mRepository);
}
@Test
public 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
@Override
public <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;
@Override
public 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;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
}
@Override
public 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 {
@PrimaryKey
private int id;
private String name;
private String email;
// getter 和 setter 方法
}
// 定义 DAO
@Dao
public interface UserDao {
@Query("SELECT * FROM users")
LiveData<List<User>> getAllUsers();
@Insert
void insert(User user);
@Update
void update(User user);
@Delete
void 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;
}
@Override
protected 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;
@Override
protected 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 -> {
// 更新 UI
updateUserList(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
@Override
public 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;
@Override
protected 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 {
@Inject
public 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;
@Inject
public MyViewModel(DataRepository repository) {
this.repository = repository;
data = new MutableLiveData<>();
loadData();
}
private void loadData() {
// 从 Repository 获取数据
repository.fetchData().observeForever(new Observer<String>() {
@Override
public 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;
@Override
protected 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>() {
@Override
public 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.launch
class 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.lifecycleScope
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override 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 - 1
val 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.Flow
class 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.launch
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: ItemViewModel
private lateinit var adapter: ItemAdapter
@OptIn(ExperimentalPagingApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化 RecyclerView 和 Adapter
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = ItemAdapter()
recyclerView.adapter = adapter.withLoadStateFooter(
footer = ItemLoadStateAdapter { adapter.retry() }
)
// 获取 ViewModel
viewModel = 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.ItemLayoutBinding
class 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.name
tvDescription.text = item.description
executePendingBindings()
}
}
}
}
// 加载状态适配器
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.Loading
btnRetry.isVisible = loadState is LoadState.Error
tvError.isVisible = loadState is LoadState.Error
if (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();
}
@Override
protected 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;
@Override
protected 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.module
val 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.startKoin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 启动 Koin
startKoin {
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.launch
class MyViewModel(private val repository: DataRepository) : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
init {
// 初始化时加载数据
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.viewModel
class MainActivity : AppCompatActivity() {
// 使用 Koin 获取 ViewModel
private val viewModel: MyViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 观察数据变化
viewModel.data.observe(this) { data ->
// 更新 UI
findViewById<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.launch
class ComposeViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState
data 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.Bundle
class 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取 UserViewModel
mUserViewModel = new ViewModelProvider(this).get(UserViewModel.class);
// 获取 OrderViewModel
mOrderViewModel = new ViewModelProvider(this).get(OrderViewModel.class);
// 观察用户数据
mUserViewModel.getUser().observe(this, user -> {
// 更新用户信息 UI
updateUserInfo(user);
// 用户数据加载完成后,加载订单数据
mOrderViewModel.loadOrders(user.getId());
});
// 观察订单数据
mOrderViewModel.getOrders().observe(this, orders -> {
// 更新订单列表 UI
updateOrderList(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>
<variable
name="userViewModel"
type="com.example.UserViewModel" />
<variable
name="orderViewModel"
type="com.example.OrderViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 用户信息部分 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userViewModel.user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userViewModel.user.email}" />
<!-- 订单列表部分 -->
<RecyclerView
android: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 的性能。以下是一些优化建议:
-
减少布局嵌套:过深的布局嵌套会增加视图渲染的时间,应尽量扁平化布局结构。