Files
ElderlyDialer/AGENTS.md
TongTongStudio bbf6cc6679 refactor(mvvm): 重构闹钟与应用列表ViewModel为AndroidViewModel
将ViewModel改为继承BaseAndroidViewModel,通过构造器注入Application,使用addDisposable管理RxJava订阅,LiveData改为私有并提供getter,移除RxLifecycle依赖。
2026-08-21 19:10:09 +08:00

1060 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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