将ViewModel改为继承BaseAndroidViewModel,通过构造器注入Application,使用addDisposable管理RxJava订阅,LiveData改为私有并提供getter,移除RxLifecycle依赖。
36 KiB
36 KiB
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。
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完全一致。
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
/**
* 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
/**
* 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)
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)
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)
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
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)
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)
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