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

36 KiB
Raw Blame History

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 中的数据操作需在 viewModelScopeLifecycle 感知下执行
可测试性 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 是否继承 BaseViewModelBaseAndroidViewModel
  • 需要 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