refactor(mvvm): 重构闹钟与应用列表ViewModel为AndroidViewModel

将ViewModel改为继承BaseAndroidViewModel,通过构造器注入Application,使用addDisposable管理RxJava订阅,LiveData改为私有并提供getter,移除RxLifecycle依赖。
This commit is contained in:
TongTongStudio committed 2026-08-21 19:10:09 +08:00
1 parent 7e8877a0db
commit bbf6cc6679
57 files changed
+2236 -780

No files matched your search

@@ -1,44 +1,135 @@
package com.ttstd.dialer.base;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import com.ttstd.dialer.base.rx.BaseRxFragment;
/**
* Fragment 基类,提供标准的懒加载框架
* <p>
* 兼容两种宿主场景:
* 1. ViewPager(v1) + FragmentPagerAdapter:通过 {@link #setUserVisibleHint} 判断可见性(老接口,deprecated)
* 2. ViewPager2 / FragmentTransaction.setMaxLifecycle:通过 onResume/onPause 判断可见性
* <p>
* 懒加载核心思路:
* 1. 视图创建完成({@code isViewInitiated}) + 当前页可见({@code isVisibleToUser}) 两个条件同时满足时,
* 才触发首次加载({@link #onLazyLoad()}),之后切回该页不再重复触发。
* 2. {@link #fetchData()} 每次可见都会回调,用于需要刷新的场景。
* 3. View 销毁重建后自动重置懒加载状态,保证重建后的页面能重新渲染。
*/
public abstract class BaseFragment extends BaseRxFragment {
private static final String TAG = "BaseFragment";
/** 视图是否已创建(onViewCreated 之后为 true) */
protected boolean isViewInitiated;
/** 当前 Fragment 是否对用户可见 */
protected boolean isVisibleToUser;
/** 首次数据是否已初始化 */
protected boolean isDataInitiated;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onViewCreated(@androidx.annotation.NonNull android.view.View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewInitiated = true;
// 视图已创建,检查是否满足懒加载条件
prepareFetchData();
}
@Override
public void onResume() {
super.onResume();
// ViewPager2 / setMaxLifecycle 场景下没有 setUserVisibleHint 回调,用 onResume 判断可见性;
// 兼容 ViewPager(v1):off-screen 页 getUserVisibleHint()==false,不会被误判为可见
if (!isViewInitiated || isHidden() || !getUserVisibleHint()) {
return;
}
setVisibleToUser(true);
}
@Override
public void onPause() {
// ViewPager2 / setMaxLifecycle 场景下用 onPause 判断不可见
if (isViewInitiated && !isHidden() && getUserVisibleHint()) {
setVisibleToUser(false);
}
super.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
// View 销毁重建后,重置懒加载状态,保证重建后的页面能重新渲染控件
// 否则 Fragment 实例保留的 isDataInitiated=true 会导致重建后不再触发 onLazyLoad,控件消失
isDataInitiated = false;
isViewInitiated = false;
isVisibleToUser = false;
}
/**
* ViewPager(v1) + FragmentPagerAdapter 场景下,通过此回调精确判断当前页是否可见
*/
@Override
@Deprecated
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
if (!isViewInitiated) {
return;
}
setVisibleToUser(isVisibleToUser);
}
/**
* 统一更新可见性并触发懒加载判断
*/
private void setVisibleToUser(boolean visible) {
if (this.isVisibleToUser == visible) {
return;
}
this.isVisibleToUser = visible;
prepareFetchData();
}
/**
* 触发真实数据加载,子类重写实现具体加载逻辑
*/
public abstract void fetchData();
/**
* 懒加载入口:视图已创建 且 当前页可见 时触发一次
*/
public boolean prepareFetchData() {
return prepareFetchData(false);
}
/**
* 懒加载入口
*
* @param forceUpdate 为 true 时,即使已加载过也强制重新加载
*/
public boolean prepareFetchData(boolean forceUpdate) {
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
if (!isDataInitiated) {
Log.d(TAG, "lazyLoad trigger: " + getClass().getSimpleName());
onLazyLoad();
}
// 只在非强制刷新时标记为已初始化,保证首屏只渲染一次
if (!forceUpdate) {
isDataInitiated = true;
}
fetchData();
//注释掉保证每次都更新数据
// isDataInitiated = true;
return true;
}
return false;
}
/**
* 首次可见时回调,用于做一次性的重操作(如创建控件、加载图片等)
* 默认空实现,子类按需重写
*/
protected void onLazyLoad() {
}
}
@@ -0,0 +1,135 @@
package com.ttstd.dialer.base.mvvm;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* MVVM ViewModel 基类(带 Context 版本)
*
* 继承 {@link AndroidViewModel},可通过 {@link #getAppContext()} 获取 Application 级别的 Context。
* Application Context 生命周期与进程一致,不会造成内存泄漏。
*
* 职责:
* - 持有 UI 状态(LiveData)
* - 处理业务逻辑
* - 暴露数据给 View 层
* - 通过 {@link #addDisposable(Disposable)} 统一管理 RxJava 异步任务
* - 可通过 {@link #getAppContext()} 获取 Application Context(安全)
*
* 禁止:
* - 持有任何 View 引用(Activity / Fragment / View)
* - 使用 Activity Context 或持有 View 的 Context(只能使用 Application)
* - 直接进行网络请求或数据库操作(必须通过 Repository)
* - 在 ViewModel 中弹 Toast / Dialog / Snackbar
* - 手动创建 Thread / Executor / AsyncTask 执行异步任务(必须使用 RxJava + {@link #addDisposable(Disposable)})
* - 忘记将 Disposable add 到 {@link #mDisposables} 中
*/
public abstract class BaseAndroidViewModel extends AndroidViewModel implements IMvvmUiState {
// ============================================================
// 通用 UI 状态
// ============================================================
/** 全局加载状态 */
private final MutableLiveData<Boolean> mLoadingLiveData = new MutableLiveData<>(false);
/** 全局错误提示 */
private final MutableLiveData<String> mErrorLiveData = new MutableLiveData<>();
/** 全局成功提示 */
private final MutableLiveData<String> mSuccessLiveData = new MutableLiveData<>();
// ============================================================
// RxJava 异步管理(核心)
// ============================================================
/**
* CompositeDisposable 用于统一管理所有 RxJava 订阅。
* 在 onCleared() 中自动 dispose,防止内存泄漏。
*/
protected final CompositeDisposable mDisposables = new CompositeDisposable();
// ============================================================
// 构造方法
// ============================================================
public BaseAndroidViewModel(@NonNull Application application) {
super(application);
}
// ============================================================
// 对外暴露(只读)
// ============================================================
public LiveData<Boolean> getLoadingLiveData() {
return mLoadingLiveData;
}
public LiveData<String> getErrorLiveData() {
return mErrorLiveData;
}
public LiveData<String> getSuccessLiveData() {
return mSuccessLiveData;
}
// ============================================================
// 受保护的方法(子类调用)
// ============================================================
protected void setLoading(boolean loading) {
mLoadingLiveData.postValue(loading);
}
protected void setError(String message) {
mErrorLiveData.postValue(message);
}
protected void setSuccess(String message) {
mSuccessLiveData.postValue(message);
}
/**
* 便捷方法:将 Disposable 添加到 {@link #mDisposables} 中管理。
* 子类在发起 RxJava 请求时调用此方法。
*/
protected void addDisposable(Disposable disposable) {
mDisposables.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 (!mDisposables.isDisposed()) {
mDisposables.clear();
}
}
}
@@ -1,6 +1,8 @@
package com.ttstd.dialer.base.mvvm;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
@@ -14,6 +16,33 @@ import com.ttstd.dialer.utils.Logger;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
/**
* MVVM Activity 基类
*
* 职责:
* - 绑定 DataBinding
* - 初始化 ViewModel(默认通过反射自动创建)
* - 订阅 ViewModel 中的 LiveData({@link #observeViewModel()})
* - 订阅全局通用状态({@link #observeCommonState()})
* - 页面创建完成后加载数据({@link #onLoadData()})
* - 将用户操作转发给 ViewModel
*
* 生命周期顺序(onCreate):
* 1. 创建 DataBinding
* 2. 创建 ViewModel({@link #createViewModel()})
* 3. 订阅通用状态({@link #observeCommonState()})
* 4. 订阅业务状态({@link #observeViewModel()})
* 5. 初始化界面({@link #initDataBinding()} / {@link #initView()})
* 6. 加载数据({@link #onLoadData()})
*
* 禁止:
* - 在 Activity 中直接进行网络请求或数据库操作(必须通过 ViewModel → Repository)
* - 持有可变 LiveData 的引用(应通过 LiveData 回调观察)
* - 在 Activity 中写业务逻辑
*
* @param <VM> ViewModel 类型,必须继承 {@link BaseViewModel} 或 {@link BaseAndroidViewModel}
* @param <VDB> ViewDataBinding 类型
*/
public abstract class BaseMvvmActivity<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseTransparentActivity {
private static final String TAG = BaseMvvmActivity.class.getSimpleName();
@@ -22,39 +51,131 @@ public abstract class BaseMvvmActivity<VM extends ViewModel, VDB extends ViewDat
protected VDB mViewDataBinding;
protected Class<VM> vmClass;
// ============================================================
// 生命周期
// ============================================================
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//ViewDataBinding
// 1. 创建 DataBinding
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
mViewDataBinding.setLifecycleOwner(this);
//ViewModel
vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
boolean isAbstract = Modifier.isAbstract(vmClass.getModifiers());
Logger.e(TAG, "isLocalClass:" + vmClass.getSimpleName().equals(ViewModel.class.getSimpleName()) + " isAbstract:" + isAbstract);
if (!isAbstract) {//不是一个抽象类
mViewModel = new ViewModelProvider(this).get(vmClass);
if (mViewModel instanceof BaseViewModel) {
BaseViewModel<?, ?> baseVm = (BaseViewModel<?, ?>) mViewModel;
baseVm.setContext(this);
baseVm.setVDBinding(mViewDataBinding);
baseVm.setLifecycle(getLifecycleSubject());
}
}
// 2. 创建 ViewModel
mViewModel = createViewModel();
// 3. 订阅通用状态
observeCommonState();
// 4. 订阅业务状态
observeViewModel();
// 5. 初始化界面
initDataBinding();
initView();
initData();
// 6. 加载数据
onLoadData();
}
// ============================================================
// 抽象方法(子类必须实现)
// ============================================================
/** 创建 ViewModel 实例(默认通过反射自动创建,子类可按需重写) */
protected VM createViewModel() {
vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
boolean isAbstract = Modifier.isAbstract(vmClass.getModifiers());
if (isAbstract) {
Logger.e(TAG, "ViewModel is abstract: " + vmClass.getSimpleName());
return null;
}
return new ViewModelProvider(this).get(vmClass);
}
/** 初始化 DataBinding(如设置 setClick、绑定表达式等) */
protected abstract void initDataBinding();
/**
* 初始化视图
*/
/** 初始化视图(如设置 Adapter、Listener 等) */
protected abstract void initView();
/** 订阅 ViewModel 中的业务 LiveData */
protected abstract void observeViewModel();
// ============================================================
// 可选重写
// ============================================================
/**
* 初始化数据
* 页面创建完成后加载数据
* 子类可重写此方法触发首次数据加载
*/
protected abstract void initData();
protected void onLoadData() {
// 默认空实现
}
/**
* 订阅全局通用状态(loading / error / success)
* 子类可在 {@link #observeViewModel()} 中调用 super.observeCommonState(),或直接重写
*/
protected void observeCommonState() {
if (!(mViewModel instanceof IMvvmUiState)) return;
IMvvmUiState vm = (IMvvmUiState) mViewModel;
vm.getLoadingLiveData().observe(this, loading -> {
if (loading != null && loading) {
showLoading();
} else {
hideLoading();
}
});
vm.getErrorLiveData().observe(this, error -> {
if (error != null && !error.isEmpty()) {
showError(error);
}
});
vm.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 禁止在 MVVM Activity 中直接创建 Handler 做延时操作
* → 应使用 ViewModel + LiveData 驱动
*/
@Deprecated
protected Handler getHandler() {
throw new UnsupportedOperationException("禁止在 MVVM Activity 中使用 Handler");
}
}
@@ -1,75 +1,106 @@
package com.ttstd.dialer.base.mvvm;
import android.content.Context;
import androidx.databinding.ViewDataBinding;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.lang.ref.WeakReference;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
public abstract class BaseViewModel<VDB extends ViewDataBinding, T> extends ViewModel {
/**
* MVVM ViewModel 基类(无 Context 版本)
*
* 职责:
* - 持有 UI 状态(LiveData)
* - 处理业务逻辑
* - 暴露数据给 View 层
* - 通过 {@link #addDisposable(Disposable)} 统一管理 RxJava 异步任务
*
* 禁止:
* - 持有任何 View 引用(Activity / Fragment / View / Context)
* - 直接进行网络请求或数据库操作(必须通过 Repository)
* - 在 ViewModel 中弹 Toast / Dialog / Snackbar
* - 手动创建 Thread / Executor / AsyncTask 执行异步任务(必须使用 RxJava + {@link #addDisposable(Disposable)})
* - 忘记将 Disposable add 到 {@link #mDisposables} 中
*
* 若需要 Context / Application(如 SharedPreferences、AlarmManager 等),请继承
* {@link BaseAndroidViewModel},通过 {@code getAppContext()} 获取 Application Context。
*/
public abstract class BaseViewModel extends ViewModel implements IMvvmUiState {
// ============================================================
// 通用 UI 状态
// ============================================================
/** 全局加载状态 */
private final MutableLiveData<Boolean> mLoadingLiveData = new MutableLiveData<>(false);
/** 全局错误提示 */
private final MutableLiveData<String> mErrorLiveData = new MutableLiveData<>();
/** 全局成功提示 */
private final MutableLiveData<String> mSuccessLiveData = new MutableLiveData<>();
// ============================================================
// RxJava 异步管理(核心)
// ============================================================
/**
* Google 标准写法:ViewModel 内统一管理 Rx 订阅,
* onCleared 时自动取消,防止内存泄漏。
* CompositeDisposable 用于统一管理所有 RxJava 订阅。
* 在 onCleared() 中自动 dispose,防止内存泄漏。
*/
protected final CompositeDisposable mDisposables = new CompositeDisposable();
// ============================================================
// 对外暴露(只读)
// ============================================================
public LiveData<Boolean> getLoadingLiveData() {
return mLoadingLiveData;
}
public LiveData<String> getErrorLiveData() {
return mErrorLiveData;
}
public LiveData<String> getSuccessLiveData() {
return mSuccessLiveData;
}
// ============================================================
// 受保护的方法(子类调用)
// ============================================================
protected void setLoading(boolean loading) {
mLoadingLiveData.postValue(loading);
}
protected void setError(String message) {
mErrorLiveData.postValue(message);
}
protected void setSuccess(String message) {
mSuccessLiveData.postValue(message);
}
/**
* 将订阅托管给 ViewModel 生命周期
* 便捷方法:将 Disposable 添加到 {@link #mDisposables} 中管理。
* 子类在发起 RxJava 请求时调用此方法,确保 ViewModel 销毁时自动取消订阅。
*/
protected void addDisposable(Disposable disposable) {
mDisposables.add(disposable);
}
// ============================================================
// 生命周期
// ============================================================
@Override
protected void onCleared() {
mDisposables.clear();
super.onCleared();
}
/**
* 当前viewmodel对应的页面binding
*/
protected VDB binding;
public void setVDBinding(ViewDataBinding vdBinding) {
binding = (VDB) vdBinding;
}
public VDB getVDBinding() {
if (binding == null) {
throw new NullPointerException("BaseViewModel >> getVDBinding >> null!!!");
// ViewModel 销毁时,自动取消所有正在执行的 RxJava 任务
if (!mDisposables.isDisposed()) {
mDisposables.clear();
}
return binding;
}
private WeakReference<Context> weakContext;
public void setContext(Context context) {
if (weakContext == null) {
weakContext = new WeakReference<>(context.getApplicationContext());
}
}
public Context getSafeContext() {
if (weakContext == null) {
throw new NullPointerException("BaseViewModel >> getCtx >> null!!!");
}
return weakContext.get();
}
private BehaviorSubject<T> mBehaviorSubject;
public void setLifecycle(BehaviorSubject subject) {
this.mBehaviorSubject = (BehaviorSubject<T>) subject;
}
public BehaviorSubject<T> getLifecycle() {
return mBehaviorSubject;
}
}
@@ -0,0 +1,22 @@
package com.ttstd.dialer.base.mvvm;
import androidx.lifecycle.LiveData;
/**
* MVVM ViewModel 通用 UI 状态接口
*
* {@link BaseViewModel} 与 {@link BaseAndroidViewModel} 均实现本接口,
* 使基类({@link BaseMvvmActivity} / {@link BaseMvvmFragment} / {@link BaseMvvmDialogFragment})
* 能以统一的 {@code VM extends ViewModel} 泛型约束订阅 loading / error / success 状态。
*/
public interface IMvvmUiState {
/** 全局加载状态(只读) */
LiveData<Boolean> getLoadingLiveData();
/** 全局错误提示(只读) */
LiveData<String> getErrorLiveData();
/** 全局成功提示(只读) */
LiveData<String> getSuccessLiveData();
}
@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
@@ -16,20 +17,35 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.ttstd.dialer.base.BaseDialogFragment;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.base.mvvm.IMvvmUiState;
import com.ttstd.dialer.utils.Logger;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
/**
* 优化生命周期管理,去掉冗余的 Bundle 定义
* @param <VM> ViewModel 类型
* MVVM DialogFragment 基类
*
* 职责:
* - 绑定 DataBinding
* - 初始化 ViewModel(默认通过反射自动创建)
* - 订阅 ViewModel 中的 LiveData({@link #observeViewModel()})
* - 订阅全局通用状态({@link #observeCommonState()})
* - 页面创建完成后加载数据({@link #onLoadData()})
* - 将用户操作转发给 ViewModel
*
* 禁止:
* - 在 DialogFragment 中直接进行网络请求或数据库操作(必须通过 ViewModel → Repository)
* - 持有可变 LiveData 的引用(应通过 LiveData 回调观察)
* - 在 DialogFragment 中写业务逻辑
*
* @param <VM> ViewModel 类型,必须继承 {@link BaseViewModel} 或 {@link BaseAndroidViewModel}
* @param <VDB> ViewDataBinding 类型
*/
public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseDialogFragment {
protected final String mTag = this.getClass().getSimpleName();
protected boolean mIsVisible;
protected boolean mHasPrepare;
@@ -48,18 +64,16 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
ctx = new WeakReference<>(context);
}
// ============================================================
// 生命周期
// ============================================================
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ViewModel 初始化移至 onCreate
// ViewModel 初始化
try {
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
mViewModel = new ViewModelProvider(this).get(vmClass);
if (mViewModel instanceof BaseViewModel) {
BaseViewModel<?, ?> baseVm = (BaseViewModel<?, ?>) mViewModel;
baseVm.setContext(getContext());
baseVm.setLifecycle(getLifecycleSubject());
}
mViewModel = createViewModel();
} catch (Exception e) {
Logger.e(mTag, "ViewModel initialization failed: " + e.getMessage());
}
@@ -68,10 +82,8 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// DataBinding 初始化
mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
if (mViewModel instanceof BaseViewModel) {
((BaseViewModel<?, ?>) mViewModel).setVDBinding(mViewDataBinding);
}
return mViewDataBinding.getRoot();
}
@@ -80,9 +92,17 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
super.onViewCreated(view, savedInstanceState);
mViewDataBinding.setLifecycleOwner(getViewLifecycleOwner());
// 订阅通用状态
observeCommonState();
// 订阅业务状态
observeViewModel();
initDataBinding();
initView(getArguments());
initData(savedInstanceState);
initView(savedInstanceState);
// 加载数据
onLoadData();
mHasPrepare = true;
if (mIsVisible || getUserVisibleHint()) {
@@ -96,9 +116,6 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
super.onDestroyView();
mHasPrepare = false;
// 及时解绑 Binding,防止内存泄漏
if (mViewModel instanceof BaseViewModel) {
((BaseViewModel<?, ?>) mViewModel).setVDBinding(null);
}
mViewDataBinding = null;
}
@@ -122,14 +139,85 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
public void fetchData() {
}
// ============================================================
// 抽象方法(子类必须实现)
// ============================================================
/** 创建 ViewModel 实例(默认通过反射自动创建,子类可按需重写) */
protected VM createViewModel() {
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return new ViewModelProvider(this).get(vmClass);
}
@LayoutRes
protected abstract int getLayoutId();
/** 初始化 DataBinding(如设置 setClick、绑定表达式等) */
protected abstract void initDataBinding();
protected abstract void initView(Bundle bundle);
/** 初始化视图(如设置 Adapter、Listener 等) */
protected abstract void initView(Bundle savedInstanceState);
protected abstract void initData(Bundle savedInstanceState);
/** 订阅 ViewModel 中的业务 LiveData */
protected abstract void observeViewModel();
// ============================================================
// 可选重写
// ============================================================
/**
* 页面创建完成后加载数据
* 子类可重写此方法触发首次数据加载
*/
protected void onLoadData() {
// 默认空实现
}
/**
* 订阅全局通用状态(loading / error / success)
*/
protected void observeCommonState() {
if (!(mViewModel instanceof IMvvmUiState)) return;
IMvvmUiState vm = (IMvvmUiState) mViewModel;
vm.getLoadingLiveData().observe(getViewLifecycleOwner(), loading -> {
if (loading != null && loading) {
showLoading();
} else {
hideLoading();
}
});
vm.getErrorLiveData().observe(getViewLifecycleOwner(), error -> {
if (error != null && !error.isEmpty()) {
showError(error);
}
});
vm.getSuccessLiveData().observe(getViewLifecycleOwner(), msg -> {
if (msg != null && !msg.isEmpty()) {
showSuccess(msg);
}
});
}
// ============================================================
// UI 辅助方法(子类可重写)
// ============================================================
protected void showLoading() {
}
protected void hideLoading() {
}
protected void showError(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
protected void showSuccess(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
@@ -16,27 +17,41 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.ttstd.dialer.base.BaseFragment;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.base.mvvm.IMvvmUiState;
import com.ttstd.dialer.utils.Logger;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
/**
* 优化生命周期管理,去掉冗余的 Bundle 定义
* @param <VM> ViewModel 类型
* MVVM Fragment 基类
*
* 职责:
* - 绑定 DataBinding
* - 初始化 ViewModel(默认通过反射自动创建)
* - 订阅 ViewModel 中的 LiveData({@link #observeViewModel()})
* - 订阅全局通用状态({@link #observeCommonState()})
* - 页面创建完成后加载数据({@link #onLoadData()})
* - 支持懒加载({@link #onEnter()} / {@link #onExit()})
* - 将用户操作转发给 ViewModel
*
* 禁止:
* - 在 Fragment 中直接进行网络请求或数据库操作(必须通过 ViewModel → Repository)
* - 持有可变 LiveData 的引用(应通过 LiveData 回调观察)
* - 在 Fragment 中写业务逻辑
* - 使用 getActivity() 强转后直接操作 Activity(应通过 ViewModel 通信)
*
* @param <VM> ViewModel 类型,必须继承 {@link BaseViewModel} 或 {@link BaseAndroidViewModel}
* @param <VDB> ViewDataBinding 类型
*/
public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDataBinding> extends BaseFragment {
protected final String mTag = this.getClass().getSimpleName();
/**
* 是否可见(用于懒加载逻辑)
*/
/** 是否可见(用于懒加载逻辑) */
protected boolean mIsVisible;
/**
* View 是否准备完毕
*/
/** View 是否准备完毕 */
protected boolean mHasPrepare;
protected VM mViewModel;
@@ -54,18 +69,16 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
ctx = new WeakReference<>(context);
}
// ============================================================
// 生命周期
// ============================================================
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ViewModel 初始化
try {
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
mViewModel = new ViewModelProvider(this).get(vmClass);
if (mViewModel instanceof BaseViewModel) {
BaseViewModel<?, ?> baseVm = (BaseViewModel<?, ?>) mViewModel;
baseVm.setContext(getContext());
baseVm.setLifecycle(getLifecycleSubject());
}
mViewModel = createViewModel();
} catch (Exception e) {
Logger.e(mTag, "ViewModel initialization failed: " + e.getMessage());
}
@@ -74,11 +87,8 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// ViewDataBinding 初始化
// DataBinding 初始化
mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
if (mViewModel instanceof BaseViewModel) {
((BaseViewModel<?, ?>) mViewModel).setVDBinding(mViewDataBinding);
}
return mViewDataBinding.getRoot();
}
@@ -86,11 +96,18 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewDataBinding.setLifecycleOwner(getViewLifecycleOwner());
// 订阅通用状态
observeCommonState();
// 订阅业务状态
observeViewModel();
initDataBinding();
// 去掉重复的 bundle 和 savedInstanceState 定义,直接从参数/方法获取
initView(getArguments());
initData(savedInstanceState);
initView(savedInstanceState);
// 加载数据
onLoadData();
mHasPrepare = true;
// 检查初始可见性
@@ -105,9 +122,6 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
super.onDestroyView();
mHasPrepare = false;
// 及时解绑 Binding,防止内存泄漏
if (mViewModel instanceof BaseViewModel) {
((BaseViewModel<?, ?>) mViewModel).setVDBinding(null);
}
mViewDataBinding = null;
}
@@ -127,14 +141,91 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
}
}
// ============================================================
// 抽象方法(子类必须实现)
// ============================================================
/** 创建 ViewModel 实例(默认通过反射自动创建,子类可按需重写) */
protected VM createViewModel() {
Class<VM> vmClass = (Class<VM>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return new ViewModelProvider(this).get(vmClass);
}
@LayoutRes
protected abstract int getLayoutId();
/** 初始化 DataBinding(如设置 setClick、绑定表达式等) */
protected abstract void initDataBinding();
protected abstract void initView(Bundle bundle);
/** 初始化视图(如设置 Adapter、Listener 等) */
protected abstract void initView(Bundle savedInstanceState);
protected abstract void initData(Bundle savedInstanceState);
/** 订阅 ViewModel 中的业务 LiveData */
protected abstract void observeViewModel();
// ============================================================
// 可选重写
// ============================================================
/**
* 页面创建完成后加载数据
* 子类可重写此方法触发首次数据加载
*/
protected void onLoadData() {
// 默认空实现
}
/**
* 订阅全局通用状态(loading / error / success)
* 子类可在 {@link #observeViewModel()} 中调用 super.observeCommonState(),或直接重写
*/
protected void observeCommonState() {
if (!(mViewModel instanceof IMvvmUiState)) return;
IMvvmUiState vm = (IMvvmUiState) mViewModel;
vm.getLoadingLiveData().observe(getViewLifecycleOwner(), loading -> {
if (loading != null && loading) {
showLoading();
} else {
hideLoading();
}
});
vm.getErrorLiveData().observe(getViewLifecycleOwner(), error -> {
if (error != null && !error.isEmpty()) {
showError(error);
}
});
vm.getSuccessLiveData().observe(getViewLifecycleOwner(), msg -> {
if (msg != null && !msg.isEmpty()) {
showSuccess(msg);
}
});
}
// ============================================================
// UI 辅助方法(子类可重写)
// ============================================================
protected void showLoading() {
// 默认实现:可替换为自定义 LoadingDialog
}
protected void hideLoading() {
}
protected void showError(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
protected void showSuccess(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
// ============================================================
// 懒加载回调(子类可重写)
// ============================================================
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -150,4 +241,17 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
public boolean isAttached() {
return getCtx() != null && isAdded();
}
// ============================================================
// 禁止使用的模式(编译期约束)
// ============================================================
/**
* @deprecated 禁止在 MVVM Fragment 中通过 getActivity() 强转后直接操作 Activity
* → 应通过 ViewModel 通信
*/
@Deprecated
protected android.app.Activity getActivityAs() {
throw new UnsupportedOperationException("禁止在 MVVM Fragment 中使用 getActivity() 强转");
}
}