将ViewModel改为继承BaseAndroidViewModel,通过构造器注入Application,使用addDisposable管理RxJava订阅,LiveData改为私有并提供getter,移除RxLifecycle依赖。
1060 lines
36 KiB
Markdown
1060 lines
36 KiB
Markdown
# AIAGENTS.md — Android MVVM 架构规范
|
||
|
||
> 本文件用于指导 AI Agent 在 Android 项目中正确生成 MVVM 架构代码。
|
||
> 所有新增/修改的 Activity、Fragment、ViewModel 必须遵循以下规范。
|
||
|
||
---
|
||
|
||
## 一、架构总览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ View (UI) │
|
||
│ Activity / Fragment / ViewBinding │
|
||
│ 职责:渲染 UI、接收用户输入、订阅 ViewModel 状态 │
|
||
└────────────────────┬────────────────────────────────┘
|
||
│ 调用方法 / 观察 LiveData
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ ViewModel │
|
||
│ 持有 UI 状态、处理业务逻辑、暴露数据 │
|
||
│ 职责:不持有 View 引用、不直接操作数据层 │
|
||
└────────────────────┬────────────────────────────────┘
|
||
│ 调用
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Repository │
|
||
│ 统一数据源入口(网络 + 本地缓存) │
|
||
└────────────────────┬────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Model (Data) │
|
||
│ Retrofit / Room / SharedPreferences │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 二、核心原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **单向数据流** | View → 调用 ViewModel 方法 → ViewModel 更新 LiveData → View 被动刷新 |
|
||
| **ViewModel 不持有 View 引用** | 禁止传入 Activity / Fragment / View / Context |
|
||
| **View 不直接访问数据层** | 所有数据必须通过 ViewModel → Repository |
|
||
| **生命周期安全** | ViewModel 中的数据操作需在 `viewModelScope` 或 `Lifecycle` 感知下执行 |
|
||
| **可测试性** | ViewModel 不依赖 Android Framework,便于单元测试 |
|
||
|
||
---
|
||
|
||
## 三、基类代码
|
||
|
||
### 3.1 BaseViewModel(无需 Context 时使用)
|
||
|
||
> **适用场景:** ViewModel 不需要 `Context`(如纯内存计算、通过 Repository 访问数据等)。
|
||
> 若需要 `Context` / `Application`,请使用 **3.2 BaseAndroidViewModel**。
|
||
|
||
```java
|
||
import io.reactivex.disposables.CompositeDisposable;
|
||
import io.reactivex.disposables.Disposable;
|
||
|
||
/**
|
||
* MVVM ViewModel 基类(无 Context 版本)
|
||
*
|
||
* 职责:
|
||
* - 持有 UI 状态(LiveData)
|
||
* - 处理业务逻辑
|
||
* - 暴露数据给 View 层
|
||
* - 通过 CompositeDisposable 统一管理 RxJava 异步任务
|
||
*
|
||
* 禁止:
|
||
* - 持有任何 View 引用(Activity / Fragment / View / Context)
|
||
* - 直接进行网络请求或数据库操作(必须通过 Repository)
|
||
* - 在 ViewModel 中弹 Toast / Dialog / Snackbar
|
||
* - 手动创建 Thread / Executor 执行异步任务(必须使用 RxJava + CompositeDisposable)
|
||
* - 忘记将 Disposable add 到 disposables 中
|
||
*/
|
||
public abstract class BaseViewModel extends ViewModel {
|
||
|
||
// ============================================================
|
||
// 通用 UI 状态
|
||
// ============================================================
|
||
|
||
/** 全局加载状态 */
|
||
private final MutableLiveData<Boolean> loadingLiveData = new MutableLiveData<>(false);
|
||
|
||
/** 全局错误提示 */
|
||
private final MutableLiveData<String> errorLiveData = new MutableLiveData<>();
|
||
|
||
/** 全局成功提示 */
|
||
private final MutableLiveData<String> successLiveData = new MutableLiveData<>();
|
||
|
||
// ============================================================
|
||
// RxJava 异步管理(核心)
|
||
// ============================================================
|
||
|
||
/**
|
||
* CompositeDisposable 用于统一管理所有 RxJava 订阅。
|
||
* 在 onCleared() 中自动 dispose,防止内存泄漏。
|
||
*
|
||
* 使用方式:
|
||
* disposables.add(
|
||
* repository.fetchUser(id)
|
||
* .subscribeOn(Schedulers.io())
|
||
* .observeOn(AndroidSchedulers.mainThread())
|
||
* .subscribe(user -> { ... }, throwable -> { ... })
|
||
* );
|
||
*/
|
||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||
|
||
// ============================================================
|
||
// 对外暴露(只读)
|
||
// ============================================================
|
||
|
||
public LiveData<Boolean> getLoadingLiveData() {
|
||
return loadingLiveData;
|
||
}
|
||
|
||
public LiveData<String> getErrorLiveData() {
|
||
return errorLiveData;
|
||
}
|
||
|
||
public LiveData<String> getSuccessLiveData() {
|
||
return successLiveData;
|
||
}
|
||
|
||
// ============================================================
|
||
// 受保护的方法(子类调用)
|
||
// ============================================================
|
||
|
||
protected void setLoading(boolean loading) {
|
||
loadingLiveData.postValue(loading);
|
||
}
|
||
|
||
protected void setError(String message) {
|
||
errorLiveData.postValue(message);
|
||
}
|
||
|
||
protected void setSuccess(String message) {
|
||
successLiveData.postValue(message);
|
||
}
|
||
|
||
/**
|
||
* 便捷方法:将 Disposable 添加到 CompositeDisposable 中管理。
|
||
* 子类在发起 RxJava 请求时调用此方法。
|
||
*/
|
||
protected void addDisposable(Disposable disposable) {
|
||
disposables.add(disposable);
|
||
}
|
||
|
||
// ============================================================
|
||
// 生命周期
|
||
// ============================================================
|
||
|
||
@Override
|
||
protected void onCleared() {
|
||
super.onCleared();
|
||
// ViewModel 销毁时,自动取消所有正在执行的 RxJava 任务
|
||
if (!disposables.isDisposed()) {
|
||
disposables.clear();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3.2 BaseAndroidViewModel(需要 Context 时使用)
|
||
|
||
> **适用场景:** ViewModel 需要 `Context` / `Application`(如读取 `SharedPreferences`、访问 `Resources`、使用 `SystemService` 等)。
|
||
> 继承 `AndroidViewModel`,通过 `getApplication()` 获取 `Application` 上下文(**不会泄漏**)。
|
||
> 其余代码结构与 `BaseViewModel` 完全一致。
|
||
|
||
```java
|
||
import android.app.Application;
|
||
import io.reactivex.disposables.CompositeDisposable;
|
||
import io.reactivex.disposables.Disposable;
|
||
|
||
/**
|
||
* MVVM ViewModel 基类(带 Context 版本)
|
||
*
|
||
* 继承 AndroidViewModel,可通过 getApplication() 获取 Application 级别的 Context。
|
||
* Application Context 生命周期与进程一致,不会造成内存泄漏。
|
||
*
|
||
* 职责:
|
||
* - 持有 UI 状态(LiveData)
|
||
* - 处理业务逻辑
|
||
* - 暴露数据给 View 层
|
||
* - 通过 CompositeDisposable 统一管理 RxJava 异步任务
|
||
* - 可通过 getApplication() 获取 Application Context(安全)
|
||
*
|
||
* 禁止:
|
||
* - 持有任何 View 引用(Activity / Fragment / View)
|
||
* - 使用 Activity Context 或持有 View 的 Context(只能使用 Application)
|
||
* - 直接进行网络请求或数据库操作(必须通过 Repository)
|
||
* - 在 ViewModel 中弹 Toast / Dialog / Snackbar
|
||
* - 手动创建 Thread / Executor 执行异步任务(必须使用 RxJava + CompositeDisposable)
|
||
* - 忘记将 Disposable add 到 disposables 中
|
||
*/
|
||
public abstract class BaseAndroidViewModel extends AndroidViewModel {
|
||
|
||
// ============================================================
|
||
// 通用 UI 状态
|
||
// ============================================================
|
||
|
||
/** 全局加载状态 */
|
||
private final MutableLiveData<Boolean> loadingLiveData = new MutableLiveData<>(false);
|
||
|
||
/** 全局错误提示 */
|
||
private final MutableLiveData<String> errorLiveData = new MutableLiveData<>();
|
||
|
||
/** 全局成功提示 */
|
||
private final MutableLiveData<String> successLiveData = new MutableLiveData<>();
|
||
|
||
// ============================================================
|
||
// RxJava 异步管理(核心)
|
||
// ============================================================
|
||
|
||
/**
|
||
* CompositeDisposable 用于统一管理所有 RxJava 订阅。
|
||
* 在 onCleared() 中自动 dispose,防止内存泄漏。
|
||
*/
|
||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||
|
||
// ============================================================
|
||
// 构造方法
|
||
// ============================================================
|
||
|
||
public BaseAndroidViewModel(@NonNull Application application) {
|
||
super(application);
|
||
}
|
||
|
||
// ============================================================
|
||
// 对外暴露(只读)
|
||
// ============================================================
|
||
|
||
public LiveData<Boolean> getLoadingLiveData() {
|
||
return loadingLiveData;
|
||
}
|
||
|
||
public LiveData<String> getErrorLiveData() {
|
||
return errorLiveData;
|
||
}
|
||
|
||
public LiveData<String> getSuccessLiveData() {
|
||
return successLiveData;
|
||
}
|
||
|
||
// ============================================================
|
||
// 受保护的方法(子类调用)
|
||
// ============================================================
|
||
|
||
protected void setLoading(boolean loading) {
|
||
loadingLiveData.postValue(loading);
|
||
}
|
||
|
||
protected void setError(String message) {
|
||
errorLiveData.postValue(message);
|
||
}
|
||
|
||
protected void setSuccess(String message) {
|
||
successLiveData.postValue(message);
|
||
}
|
||
|
||
/**
|
||
* 便捷方法:将 Disposable 添加到 CompositeDisposable 中管理。
|
||
* 子类在发起 RxJava 请求时调用此方法。
|
||
*/
|
||
protected void addDisposable(Disposable disposable) {
|
||
disposables.add(disposable);
|
||
}
|
||
|
||
// ============================================================
|
||
// Context 安全访问
|
||
// ============================================================
|
||
|
||
/**
|
||
* 获取 Application Context(安全,不会泄漏)。
|
||
* 子类可通过此方法访问:
|
||
* - getApplication().getSharedPreferences(...)
|
||
* - getApplication().getResources().getString(...)
|
||
* - getApplication().getSystemService(...)
|
||
*/
|
||
@NonNull
|
||
protected Application getAppContext() {
|
||
return getApplication();
|
||
}
|
||
|
||
// ============================================================
|
||
// 生命周期
|
||
// ============================================================
|
||
|
||
@Override
|
||
protected void onCleared() {
|
||
super.onCleared();
|
||
// ViewModel 销毁时,自动取消所有正在执行的 RxJava 任务
|
||
if (!disposables.isDisposed()) {
|
||
disposables.clear();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> **选择指南:**
|
||
> | 需求 | 继承 |
|
||
> |------|------|
|
||
> | 不需要 Context,纯数据驱动 | `BaseViewModel` |
|
||
> | 需要 SharedPreferences / Resources / SystemService | `BaseAndroidViewModel` |
|
||
> | 需要 Activity Context(弹 Dialog 等) | ❌ 不允许,应通过 View 层 LiveData 回调处理 |
|
||
|
||
---
|
||
|
||
### 3.3 BaseMvvmActivity
|
||
|
||
```java
|
||
/**
|
||
* MVVM Activity 基类
|
||
*
|
||
* 职责:
|
||
* - 绑定 ViewBinding
|
||
* - 初始化 ViewModel
|
||
* - 订阅 ViewModel 中的 LiveData
|
||
* - 将用户操作转发给 ViewModel
|
||
*
|
||
* 禁止:
|
||
* - 在 Activity 中直接进行网络请求或数据库操作
|
||
* - 持有可变 LiveData 的引用
|
||
* - 在 Activity 中写业务逻辑
|
||
*
|
||
* @param <VM> ViewModel 类型,必须继承 BaseViewModel 或 BaseAndroidViewModel
|
||
* @param <VB> ViewBinding 类型
|
||
*/
|
||
public abstract class BaseMvvmActivity<VM extends BaseViewModel, VB extends ViewBinding>
|
||
extends AppCompatActivity {
|
||
|
||
// ============================================================
|
||
// 成员
|
||
// ============================================================
|
||
|
||
protected VM viewModel;
|
||
protected VB binding;
|
||
|
||
// ============================================================
|
||
// 抽象方法(子类必须实现)
|
||
// ============================================================
|
||
|
||
/** 创建 ViewModel 实例 */
|
||
protected abstract VM createViewModel();
|
||
|
||
/** 创建 ViewBinding 实例 */
|
||
protected abstract VB createViewBinding();
|
||
|
||
/** 初始化界面(如设置 Adapter、Listener 等) */
|
||
protected abstract void initView();
|
||
|
||
/** 订阅 ViewModel 的 LiveData */
|
||
protected abstract void observeViewModel();
|
||
|
||
// ============================================================
|
||
// 生命周期
|
||
// ============================================================
|
||
|
||
@Override
|
||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
|
||
// 1. 创建 ViewBinding
|
||
binding = createViewBinding();
|
||
setContentView(binding.getRoot());
|
||
|
||
// 2. 创建 ViewModel
|
||
viewModel = createViewModel();
|
||
|
||
// 3. 订阅 ViewModel
|
||
observeViewModel();
|
||
|
||
// 4. 初始化界面
|
||
initView();
|
||
|
||
// 5. 加载数据
|
||
onLoadData();
|
||
}
|
||
|
||
/**
|
||
* 页面创建完成后加载数据
|
||
* 子类可重写此方法触发首次数据加载
|
||
*/
|
||
protected void onLoadData() {
|
||
// 默认空实现
|
||
}
|
||
|
||
// ============================================================
|
||
// 通用订阅(可选重写)
|
||
// ============================================================
|
||
|
||
/**
|
||
* 订阅全局通用状态(loading / error / success)
|
||
* 子类可在 observeViewModel() 中调用 super.observeCommonState()
|
||
*/
|
||
protected void observeCommonState() {
|
||
viewModel.getLoadingLiveData().observe(this, loading -> {
|
||
if (loading != null && loading) {
|
||
showLoading();
|
||
} else {
|
||
hideLoading();
|
||
}
|
||
});
|
||
|
||
viewModel.getErrorLiveData().observe(this, error -> {
|
||
if (error != null && !error.isEmpty()) {
|
||
showError(error);
|
||
}
|
||
});
|
||
|
||
viewModel.getSuccessLiveData().observe(this, msg -> {
|
||
if (msg != null && !msg.isEmpty()) {
|
||
showSuccess(msg);
|
||
}
|
||
});
|
||
}
|
||
|
||
// ============================================================
|
||
// UI 辅助方法(子类可重写)
|
||
// ============================================================
|
||
|
||
protected void showLoading() {
|
||
// 默认实现:可替换为自定义 LoadingDialog
|
||
// LoadingDialog.show(this);
|
||
}
|
||
|
||
protected void hideLoading() {
|
||
// LoadingDialog.dismiss();
|
||
}
|
||
|
||
protected void showError(String message) {
|
||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||
}
|
||
|
||
protected void showSuccess(String message) {
|
||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||
}
|
||
|
||
// ============================================================
|
||
// 禁止使用的模式(编译期约束)
|
||
// ============================================================
|
||
|
||
/**
|
||
* @deprecated 禁止在 Activity 中直接创建 Handler 做延时操作
|
||
* → 应使用 ViewModel + LiveData 驱动
|
||
*/
|
||
@Deprecated
|
||
protected Handler getHandler() {
|
||
throw new UnsupportedOperationException("禁止在 MVVM Activity 中使用 Handler");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3.4 BaseMvvmFragment
|
||
|
||
```java
|
||
/**
|
||
* MVVM Fragment 基类
|
||
*
|
||
* 职责:
|
||
* - 绑定 ViewBinding
|
||
* - 初始化 ViewModel(默认与 Activity 共享 ViewModel)
|
||
* - 订阅 ViewModel 中的 LiveData
|
||
* - 将用户操作转发给 ViewModel
|
||
*
|
||
* 禁止:
|
||
* - 在 Fragment 中直接进行网络请求或数据库操作
|
||
* - 持有可变 LiveData 的引用
|
||
* - 在 Fragment 中写业务逻辑
|
||
* - 使用 getActivity() 强转后直接操作 Activity(应通过 ViewModel 通信)
|
||
*
|
||
* @param <VM> ViewModel 类型,必须继承 BaseViewModel 或 BaseAndroidViewModel
|
||
* @param <VB> ViewBinding 类型
|
||
*/
|
||
public abstract class BaseMvvmFragment<VM extends BaseViewModel, VB extends ViewBinding>
|
||
extends Fragment {
|
||
|
||
// ============================================================
|
||
// 成员
|
||
// ============================================================
|
||
|
||
protected VM viewModel;
|
||
protected VB binding;
|
||
|
||
/** 是否与 Activity 共享 ViewModel(默认 true) */
|
||
protected boolean shareViewModelWithActivity() {
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// 抽象方法(子类必须实现)
|
||
// ============================================================
|
||
|
||
/** 创建 ViewModel 实例 */
|
||
protected abstract VM createViewModel();
|
||
|
||
/** 创建 ViewBinding 实例 */
|
||
protected abstract VB createViewBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container);
|
||
|
||
/** 初始化界面 */
|
||
protected abstract void initView();
|
||
|
||
/** 订阅 ViewModel 的 LiveData */
|
||
protected abstract void observeViewModel();
|
||
|
||
// ============================================================
|
||
// 生命周期
|
||
// ============================================================
|
||
|
||
@Nullable
|
||
@Override
|
||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||
@Nullable Bundle savedInstanceState) {
|
||
binding = createViewBinding(inflater, container);
|
||
return binding.getRoot();
|
||
}
|
||
|
||
@Override
|
||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||
super.onViewCreated(view, savedInstanceState);
|
||
|
||
// 1. 创建 ViewModel
|
||
viewModel = createViewModel();
|
||
|
||
// 2. 订阅 ViewModel
|
||
observeViewModel();
|
||
|
||
// 3. 初始化界面
|
||
initView();
|
||
|
||
// 4. 加载数据
|
||
onLoadData();
|
||
}
|
||
|
||
@Override
|
||
public void onDestroyView() {
|
||
super.onDestroyView();
|
||
// 释放 ViewBinding 引用,防止内存泄漏
|
||
binding = null;
|
||
}
|
||
|
||
/**
|
||
* 页面创建完成后加载数据
|
||
* 子类可重写此方法触发首次数据加载
|
||
*/
|
||
protected void onLoadData() {
|
||
// 默认空实现
|
||
}
|
||
|
||
// ============================================================
|
||
// 通用订阅(可选调用)
|
||
// ============================================================
|
||
|
||
/**
|
||
* 订阅全局通用状态(loading / error / success)
|
||
*/
|
||
protected void observeCommonState() {
|
||
if (getViewLifecycleOwner() == null) return;
|
||
|
||
viewModel.getLoadingLiveData().observe(getViewLifecycleOwner(), loading -> {
|
||
if (loading != null && loading) {
|
||
showLoading();
|
||
} else {
|
||
hideLoading();
|
||
}
|
||
});
|
||
|
||
viewModel.getErrorLiveData().observe(getViewLifecycleOwner(), error -> {
|
||
if (error != null && !error.isEmpty()) {
|
||
showError(error);
|
||
}
|
||
});
|
||
|
||
viewModel.getSuccessLiveData().observe(getViewLifecycleOwner(), msg -> {
|
||
if (msg != null && !msg.isEmpty()) {
|
||
showSuccess(msg);
|
||
}
|
||
});
|
||
}
|
||
|
||
// ============================================================
|
||
// UI 辅助方法(子类可重写)
|
||
// ============================================================
|
||
|
||
protected void showLoading() {
|
||
// 可替换为自定义 LoadingDialog
|
||
}
|
||
|
||
protected void hideLoading() {
|
||
// LoadingDialog.dismiss();
|
||
}
|
||
|
||
protected void showError(String message) {
|
||
if (getContext() != null) {
|
||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||
}
|
||
}
|
||
|
||
protected void showSuccess(String message) {
|
||
if (getContext() != null) {
|
||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// 工具方法
|
||
// ============================================================
|
||
|
||
/**
|
||
* 获取 Activity 级别的 ViewModel(用于 Fragment 间通信)
|
||
*/
|
||
protected <T extends ViewModel> T getActivityViewModel(Class<T> vmClass) {
|
||
return new ViewModelProvider(requireActivity()).get(vmClass);
|
||
}
|
||
|
||
// ============================================================
|
||
// 禁止使用的模式
|
||
// ============================================================
|
||
|
||
/**
|
||
* @deprecated 禁止在 Fragment 中直接使用 getActivity() 强转
|
||
* → 应通过 ViewModel 或接口回调通信
|
||
*/
|
||
@Deprecated
|
||
protected <T> T getActivityAs(Class<T> clazz) {
|
||
throw new UnsupportedOperationException("禁止强转 Activity,请使用 ViewModel 通信");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、使用示例
|
||
|
||
### 4.1 无需 Context 的 ViewModel(继承 BaseViewModel)
|
||
|
||
```java
|
||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||
import io.reactivex.schedulers.Schedulers;
|
||
import io.reactivex.disposables.Disposable;
|
||
|
||
public class UserViewModel extends BaseViewModel {
|
||
|
||
private final MutableLiveData<User> userLiveData = new MutableLiveData<>();
|
||
private final UserRepository repository;
|
||
|
||
public UserViewModel(UserRepository repository) {
|
||
this.repository = repository;
|
||
}
|
||
|
||
public LiveData<User> getUser() {
|
||
return userLiveData;
|
||
}
|
||
|
||
public void loadUser(String userId) {
|
||
setLoading(true);
|
||
|
||
Disposable disposable = repository.fetchUser(userId)
|
||
.subscribeOn(Schedulers.io())
|
||
.observeOn(AndroidSchedulers.mainThread())
|
||
.subscribe(
|
||
user -> {
|
||
userLiveData.setValue(user);
|
||
setSuccess("加载成功");
|
||
setLoading(false);
|
||
},
|
||
throwable -> {
|
||
setError(throwable.getMessage());
|
||
setLoading(false);
|
||
}
|
||
);
|
||
|
||
// 必须添加到 disposables 中,确保 ViewModel 销毁时自动取消
|
||
addDisposable(disposable);
|
||
}
|
||
|
||
public void updateUserName(String userId, String newName) {
|
||
setLoading(true);
|
||
|
||
Disposable disposable = repository.updateName(userId, newName)
|
||
.andThen(repository.fetchUser(userId)) // 更新后刷新数据
|
||
.subscribeOn(Schedulers.io())
|
||
.observeOn(AndroidSchedulers.mainThread())
|
||
.subscribe(
|
||
user -> {
|
||
userLiveData.setValue(user);
|
||
setSuccess("更新成功");
|
||
setLoading(false);
|
||
},
|
||
throwable -> {
|
||
setError(throwable.getMessage());
|
||
setLoading(false);
|
||
}
|
||
);
|
||
|
||
addDisposable(disposable);
|
||
}
|
||
}
|
||
```
|
||
|
||
> **说明:** `UserRepository` 的方法返回 `Single<User>` / `Completable` 等 RxJava 类型,
|
||
> 由 ViewModel 通过 `disposables` 统一管理订阅生命周期。
|
||
|
||
---
|
||
|
||
### 4.2 需要 Context 的 ViewModel(继承 BaseAndroidViewModel)
|
||
|
||
```java
|
||
import android.app.Application;
|
||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||
import io.reactivex.schedulers.Schedulers;
|
||
import io.reactivex.disposables.Disposable;
|
||
|
||
/**
|
||
* 需要读取 SharedPreferences / Resources 等系统服务时使用 BaseAndroidViewModel
|
||
*/
|
||
public class SettingsViewModel extends BaseAndroidViewModel {
|
||
|
||
private final MutableLiveData<String> appVersionLiveData = new MutableLiveData<>();
|
||
private final MutableLiveData<Boolean> darkModeLiveData = new MutableLiveData<>();
|
||
private final SettingsRepository repository;
|
||
|
||
public SettingsViewModel(Application application, SettingsRepository repository) {
|
||
super(application);
|
||
this.repository = repository;
|
||
}
|
||
|
||
public LiveData<String> getAppVersion() {
|
||
return appVersionLiveData;
|
||
}
|
||
|
||
public LiveData<Boolean> getDarkMode() {
|
||
return darkModeLiveData;
|
||
}
|
||
|
||
/**
|
||
* 读取 Application Context 中的 SharedPreferences
|
||
*/
|
||
public void loadSettings() {
|
||
setLoading(true);
|
||
|
||
// 通过 getAppContext() 安全获取 Application Context
|
||
String version = getAppContext()
|
||
.getPackageManager()
|
||
.getPackageInfo(getAppContext().getPackageName(), 0)
|
||
.versionName;
|
||
appVersionLiveData.postValue(version);
|
||
|
||
// 从 SharedPreferences 读取设置
|
||
boolean darkMode = getAppContext()
|
||
.getSharedPreferences("settings", Application.MODE_PRIVATE)
|
||
.getBoolean("dark_mode", false);
|
||
darkModeLiveData.postValue(darkMode);
|
||
|
||
setLoading(false);
|
||
}
|
||
|
||
/**
|
||
* 保存设置到 SharedPreferences + 远程同步
|
||
*/
|
||
public void saveDarkMode(boolean enabled) {
|
||
setLoading(true);
|
||
|
||
// 本地持久化(使用 Application Context,安全)
|
||
getAppContext()
|
||
.getSharedPreferences("settings", Application.MODE_PRIVATE)
|
||
.edit()
|
||
.putBoolean("dark_mode", enabled)
|
||
.apply();
|
||
|
||
// 远程同步
|
||
Disposable disposable = repository.syncDarkMode(enabled)
|
||
.subscribeOn(Schedulers.io())
|
||
.observeOn(AndroidSchedulers.mainThread())
|
||
.subscribe(
|
||
() -> {
|
||
darkModeLiveData.setValue(enabled);
|
||
setSuccess("设置已保存");
|
||
setLoading(false);
|
||
},
|
||
throwable -> {
|
||
setError(throwable.getMessage());
|
||
setLoading(false);
|
||
}
|
||
);
|
||
|
||
addDisposable(disposable);
|
||
}
|
||
}
|
||
```
|
||
|
||
> **关键点:**
|
||
> - 构造函数必须接收 `Application` 并传给 `super(application)`
|
||
> - 通过 `getAppContext()` 获取 Context,绝不使用 Activity Context
|
||
> - 其余 RxJava 订阅管理与 `BaseViewModel` 完全一致
|
||
|
||
### 4.3 具体 Activity(使用 BaseViewModel)
|
||
|
||
```java
|
||
public class UserProfileActivity extends BaseMvvmActivity<UserViewModel, ActivityUserProfileBinding> {
|
||
|
||
@Override
|
||
protected UserViewModel createViewModel() {
|
||
UserRepository repository = new UserRepository();
|
||
return new ViewModelProvider(this, new ViewModelProvider.Factory() {
|
||
@Override
|
||
public <T extends ViewModel> T create(Class<T> modelClass) {
|
||
return (T) new UserViewModel(repository);
|
||
}
|
||
}).get(UserViewModel.class);
|
||
}
|
||
|
||
@Override
|
||
protected ActivityUserProfileBinding createViewBinding() {
|
||
return ActivityUserProfileBinding.inflate(getLayoutInflater());
|
||
}
|
||
|
||
@Override
|
||
protected void initView() {
|
||
binding.btnRefresh.setOnClickListener(v -> {
|
||
viewModel.loadUser("123");
|
||
});
|
||
|
||
binding.btnUpdate.setOnClickListener(v -> {
|
||
viewModel.updateUserName("123", "新名字");
|
||
});
|
||
}
|
||
|
||
@Override
|
||
protected void observeViewModel() {
|
||
observeCommonState();
|
||
|
||
viewModel.getUser().observe(this, user -> {
|
||
if (user != null) {
|
||
binding.tvName.setText(user.getName());
|
||
binding.tvAge.setText(String.valueOf(user.getAge()));
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 具体 Fragment
|
||
|
||
```java
|
||
public class UserDetailFragment extends BaseMvvmFragment<UserViewModel, FragmentUserDetailBinding> {
|
||
|
||
@Override
|
||
protected UserViewModel createViewModel() {
|
||
UserRepository repository = new UserRepository();
|
||
return new ViewModelProvider(requireActivity(), new ViewModelProvider.Factory() {
|
||
@Override
|
||
public <T extends ViewModel> T create(Class<T> modelClass) {
|
||
return (T) new UserViewModel(repository);
|
||
}
|
||
}).get(UserViewModel.class);
|
||
}
|
||
|
||
@Override
|
||
protected FragmentUserDetailBinding createViewBinding(LayoutInflater inflater, ViewGroup container) {
|
||
return FragmentUserDetailBinding.inflate(inflater, container, false);
|
||
}
|
||
|
||
@Override
|
||
protected void initView() {
|
||
binding.tvTitle.setText("用户详情");
|
||
}
|
||
|
||
@Override
|
||
protected void observeViewModel() {
|
||
observeCommonState();
|
||
|
||
viewModel.getUser().observe(getViewLifecycleOwner(), user -> {
|
||
if (user != null) {
|
||
binding.tvEmail.setText(user.getEmail());
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.5 具体 Activity(使用 BaseAndroidViewModel)
|
||
|
||
```java
|
||
public class SettingsActivity extends BaseMvvmActivity<SettingsViewModel, ActivitySettingsBinding> {
|
||
|
||
@Override
|
||
protected SettingsViewModel createViewModel() {
|
||
SettingsRepository repository = new SettingsRepository();
|
||
// BaseAndroidViewModel 需要传入 Application
|
||
return new ViewModelProvider(this, new ViewModelProvider.Factory() {
|
||
@Override
|
||
public <T extends ViewModel> T create(Class<T> modelClass) {
|
||
return (T) new SettingsViewModel(getApplication(), repository);
|
||
}
|
||
}).get(SettingsViewModel.class);
|
||
}
|
||
|
||
@Override
|
||
protected ActivitySettingsBinding createViewBinding() {
|
||
return ActivitySettingsBinding.inflate(getLayoutInflater());
|
||
}
|
||
|
||
@Override
|
||
protected void initView() {
|
||
binding.switchDarkMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||
viewModel.saveDarkMode(isChecked);
|
||
});
|
||
}
|
||
|
||
@Override
|
||
protected void observeViewModel() {
|
||
observeCommonState();
|
||
|
||
viewModel.getAppVersion().observe(this, version -> {
|
||
binding.tvVersion.setText("版本:" + version);
|
||
});
|
||
|
||
viewModel.getDarkMode().observe(this, enabled -> {
|
||
binding.switchDarkMode.setChecked(enabled != null && enabled);
|
||
});
|
||
}
|
||
|
||
@Override
|
||
protected void onLoadData() {
|
||
viewModel.loadSettings();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、禁止规范(AI Agent 必须遵守)
|
||
|
||
### 5.1 ❌ ViewModel 层禁止
|
||
|
||
| 禁止行为 | 原因 |
|
||
|----------|------|
|
||
| 持有 Activity / Fragment / View 引用 | 导致内存泄漏,ViewModel 生命周期长于 View |
|
||
| 在 BaseViewModel 中持有任何 Context 引用 | BaseViewModel 不应依赖 Context,如需 Context 应改用 BaseAndroidViewModel |
|
||
| 在 BaseAndroidViewModel 中使用 Activity Context | 只能使用 Application Context(通过 getAppContext()),Activity Context 会泄漏 |
|
||
| 直接进行网络请求(如 new OkHttpCall()) | 应委托给 Repository |
|
||
| 直接操作数据库(如 Room DAO 调用) | 应委托给 Repository |
|
||
| 弹 Toast / Dialog / Snackbar | ViewModel 不应感知 UI 组件 |
|
||
| 手动创建 Thread / Executor / AsyncTask | 必须使用 RxJava + CompositeDisposable |
|
||
| 发起 RxJava 请求后不 add 到 disposables | 导致内存泄漏,ViewModel 销毁后任务仍在执行 |
|
||
| 在 onCleared 之外手动调用 `disposables.clear()` | 应由基类统一管理,子类不应干预销毁时机 |
|
||
| 暴露 MutableLiveData 给外部 | 应只暴露 LiveData(不可变) |
|
||
| 在 ViewModel 中写死 Context 相关资源(R.string.xxx) | 降低可测试性,应通过 Repository 或资源 ID 传递 |
|
||
| 创建独立的 Disposable 而不加入 CompositeDisposable | 必须统一通过 `addDisposable()` 管理 |
|
||
|
||
### 5.2 ❌ View 层(Activity / Fragment)禁止
|
||
|
||
| 禁止行为 | 原因 |
|
||
|----------|------|
|
||
| 直接调用 Retrofit / OkHttp / Room | 破坏分层架构 |
|
||
| 持有 MutableLiveData 引用并直接 setValue | 破坏单向数据流 |
|
||
| 在 Activity/Fragment 中写业务逻辑 | 降低可维护性和可测试性 |
|
||
| 使用 findViewById 替代 ViewBinding | 低效且易出错 |
|
||
| 在 onDestroyView 之后访问 binding | 导致空指针 |
|
||
| 使用静态变量存储 View / Context | 严重内存泄漏 |
|
||
| 在 Fragment 中通过 getActivity() 强转调用 Activity 方法 | 应使用 ViewModel 或接口回调 |
|
||
|
||
### 5.3 ❌ 数据层禁止
|
||
|
||
| 禁止行为 | 原因 |
|
||
|----------|------|
|
||
| Repository 直接持有 ViewModel 引用 | 数据层不应感知 UI 层 |
|
||
| Model 中混杂业务逻辑 | Model 只做数据定义和存储 |
|
||
| 在 Repository 中弹 Toast / Dialog | 数据层不应感知 UI |
|
||
|
||
---
|
||
|
||
## 六、代码生成检查清单
|
||
|
||
AI Agent 在生成 MVVM 相关代码时,必须逐项确认:
|
||
|
||
- [ ] ViewModel 是否继承 `BaseViewModel` 或 `BaseAndroidViewModel`?
|
||
- [ ] 需要 Context 的 ViewModel 是否改为继承 `BaseAndroidViewModel`(而非在 BaseViewModel 中持有 Context)?
|
||
- [ ] Activity 是否继承 `BaseMvvmActivity`?
|
||
- [ ] Fragment 是否继承 `BaseMvvmFragment`?
|
||
- [ ] ViewModel 中是否没有任何 View / Activity Context 引用?
|
||
- [ ] BaseAndroidViewModel 中是否只通过 `getAppContext()` 获取 Application Context?
|
||
- [ ] 所有数据获取是否通过 Repository?
|
||
- [ ] View 层是否只通过 LiveData 观察数据?
|
||
- [ ] View 层是否只调用 ViewModel 方法,不直接操作数据?
|
||
- [ ] MutableLiveData 是否声明为 `private`,对外只暴露 `LiveData`?
|
||
- [ ] Fragment 的 `onDestroyView` 中是否置空了 binding?
|
||
- [ ] 是否没有使用 `findViewById`?
|
||
- [ ] 所有 RxJava 订阅是否通过 `addDisposable()` 添加到 `disposables` 中?
|
||
- [ ] ViewModel 中是否没有手动创建 Thread / Executor / AsyncTask?
|
||
- [ ] Repository 方法返回类型是否为 RxJava 类型(Single / Observable / Completable / Maybe)?
|
||
|
||
---
|
||
|
||
## 七、目录结构建议
|
||
|
||
```
|
||
app/src/main/java/com/example/
|
||
├── base/
|
||
│ ├── BaseViewModel.java (无需 Context 时继承)
|
||
│ ├── BaseAndroidViewModel.java (需要 Context 时继承)
|
||
│ ├── BaseMvvmActivity.java
|
||
│ └── BaseMvvmFragment.java
|
||
├── model/
|
||
│ ├── User.java
|
||
│ └── ApiResponse.java
|
||
├── repository/
|
||
│ ├── UserRepository.java
|
||
│ ├── SettingsRepository.java
|
||
│ └── RemoteDataSource.java
|
||
├── viewmodel/
|
||
│ ├── UserViewModel.java (extends BaseViewModel)
|
||
│ ├── SettingsViewModel.java (extends BaseAndroidViewModel)
|
||
│ └── HomeViewModel.java
|
||
├── view/
|
||
│ ├── activity/
|
||
│ │ ├── UserProfileActivity.java
|
||
│ │ └── SettingsActivity.java
|
||
│ └── fragment/
|
||
│ └── UserDetailFragment.java
|
||
└── network/
|
||
├── ApiService.java
|
||
└── RetrofitClient.java
|
||
```
|
||
|
||
---
|
||
|
||
## 八、所需依赖(build.gradle)
|
||
|
||
```groovy
|
||
dependencies {
|
||
// Jetpack ViewModel & LiveData
|
||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
|
||
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.2'
|
||
|
||
// RxJava 3
|
||
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
|
||
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
|
||
|
||
// Retrofit + RxJava adapter(如网络层使用 Retrofit)
|
||
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
|
||
|
||
// ViewBinding(在 android {} 块中启用)
|
||
// buildFeatures { viewBinding true }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
> **最后更新:** 2026-08-21
|
||
> **维护者:** Android Team
|
||
> **适用版本:** Android SDK 21+ / Jetpack ViewModel 2.5+ / RxJava 3.x / ViewBinding enabled
|