[关闭]
@RitcheeQinG 2020-08-31T07:21:22.000000Z 字数 7999 阅读 444

安卓MVVM规范

Android


四层架构:

使用mvvm的优点在哪:

  1. 结构清晰,从上到下逐层持有
  2. 功能分解,让Activity不至于太过臃肿
  3. 通过ViewModelProviders.of(getActivity()).get() 轻松实现不同Fragment获取相同ViewModel
  4. 自定义Factory来对VM实现依赖注入,高度解耦
  5. 分别开发。在开发阶段,大家都知道切图和接口给的一般都没那么快,但是需求文档肯定早就给好了,在这种情况下是可以先开发VM层的;此外,LiveData持有的数据模型和接口传回来的并不需要一样,LiveData持有的一般是View层需要用的,一般在开发时我们还是能判断哪些属性是View需要用的,可以为此单独创建一个数据模型进行开发,接口返回的数据模型根据需要在VM层进行组装转换即可;当然,直接传接口的数据模型给View应该也可以,毕竟有时候我们不是在开发新代码,而是在改旧代码,特意创建一个新的数据模型显得没必要,此处有待商榷。

缺点:

  1. V和VM的逻辑其实很难摘清楚
  2. VM层可能会替代地变得臃肿,而VM本身并不太好拆分开来
  3. 操作UI没有MVP那么方便(毕竟P持有V,而VM全靠LiveData),很多东西实现起来可能没那么方便
  4. UI相关的逻辑还是得在View里操作,那个其实也算是业务逻辑的一部分,这是没办法的

使用时的注意事项:

  1. 不要为了一个控件里的一个属性写一个ViewModel(比如绑定在XML里面),很不灵活而且很费时间,还有很多内容实现不了
  2. 顺便真写起来其实会发现有时候数据到底是走DO获取还是直接从SP或者数据库拿,这个区分很成谜。我的观点是,如果涉及到了服务器或者涉及到了长期的持久化,那就走DO;如果只是短期存一下,那就直接用。
  3. 关于埋点,我还没想好,由于埋点设计UI事件居多,推荐还是全放View层

View层:

处理UI相关的逻辑,比如假设DataBean里面有一个String,那我们就可以把它设置给TextView,或者弹一个Toast,或者处理一些失败逻辑。

  1. public class MainActivity extends AppCompatActivity() {
  2. private MainPageViewModel mMainPageViewModel;
  3. public void onCreate(Bundle savedInstanceState) {
  4. mMainPageViewModel = ViewModelProviders.of(this, null).get(MainPageViewModel.class);
  5. mMainPageViewModel.pageData.observe(this, new Observer<PageDataBean>() {
  6. @Override
  7. public void onChanged(PageDataBean pageDataBean) {
  8. // ..
  9. }
  10. });
  11. mMainPageViewModel.queryData();
  12. }
  13. }

ViewModel层:

在这层我们在从DAO拿到数据模型以后,可以做一些UI无关,但可能和持久化相关的工作。

比如写入文件,写入SP,做一些这样类似的工作。

  1. /**
  2. * 这里引一段Google官方的注释,还是挺有说服力的
  3. * ViewModel is a class that is responsible for preparing and managing the data for
  4. * an {@link android.app.Activity Activity} or a {@link androidx.fragment.app.Fragment Fragment}.
  5. * It also handles the communication of the Activity / Fragment with the rest of the application
  6. * (e.g. calling the business logic classes).
  7. * <p>
  8. * A ViewModel is always created in association with a scope (an fragment or an activity) and will
  9. * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
  10. * finished.
  11. * <p>
  12. * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
  13. * configuration change (e.g. rotation). The new instance of the owner will just re-connected to the
  14. * existing ViewModel.
  15. * <p>
  16. * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
  17. * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
  18. * ViewModel. ViewModels usually expose this information via {@link LiveData} or Android Data
  19. * Binding. You can also use any observability construct from you favorite framework.
  20. * <p>
  21. * ViewModel's only responsibility is to manage the data for the UI. It <b>should never</b> access
  22. * your view hierarchy or hold a reference back to the Activity or the Fragment.
  23. * <p>
  24. * Typical usage from an Activity standpoint would be:
  25. * <pre>
  26. * public class UserActivity extends Activity {
  27. *
  28. * {@literal @}Override
  29. * protected void onCreate(Bundle savedInstanceState) {
  30. * super.onCreate(savedInstanceState);
  31. * setContentView(R.layout.user_activity_layout);
  32. * final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
  33. * viewModel.userLiveData.observer(this, new Observer<User>() {
  34. * {@literal @}Override
  35. * public void onChanged(@Nullable User data) {
  36. * // update ui.
  37. * }
  38. * });
  39. * findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
  40. * {@literal @}Override
  41. * public void onClick(View v) {
  42. * viewModel.doAction();
  43. * }
  44. * });
  45. * }
  46. * }
  47. * </pre>
  48. *
  49. * ViewModel would be:
  50. * <pre>
  51. * public class UserModel extends ViewModel {
  52. * private final MutableLiveData&lt;User&gt; userLiveData = new MutableLiveData&lt;&gt;();
  53. *
  54. * public LiveData&lt;User&gt; getUser() {
  55. * return userLiveData;
  56. * }
  57. *
  58. * public UserModel() {
  59. * // trigger user load.
  60. * }
  61. *
  62. * void doAction() {
  63. * // depending on the action, do necessary business logic calls and update the
  64. * // userLiveData.
  65. * }
  66. * }
  67. * </pre>
  68. *
  69. * <p>
  70. * ViewModels can also be used as a communication layer between different Fragments of an Activity.
  71. * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
  72. * communication between Fragments in a de-coupled fashion such that they never need to talk to
  73. * the other Fragment directly.
  74. * <pre>
  75. * public class MyFragment extends Fragment {
  76. * public void onStart() {
  77. * UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
  78. * }
  79. * }
  80. * </pre>
  81. * </>
  82. */
  83. public class MainPageViewModel extends ViewModel {
  84. // 这里我们泛型里定义的数据模型可以是服务器传回来的,也可以是自定义的。
  85. // 一般来说,修改的不太频繁的话,用服务器的即可
  86. // 理论上自定义一个来实现可能更好一些
  87. public MutableLiveData<UserInfoModel> userInfoData = new MutableLiveData<>();
  88. public MutableLiveData<PageDataBean> mainPageData = new MutableLiveData<>();
  89. private MainPageDAO mainPageDAO;
  90. public void setMainPageDAO(MainPageDAO mainPageDAO) {
  91. this.mainPageDAO = mainPageDAO;
  92. }
  93. public void queryData() {
  94. mainPageDAO.queryData(new RequestResultListener<PageDataBean>() {
  95. @Override
  96. public void onResponse(PageDataBean data) {
  97. mainPageData.postValue(data);
  98. }
  99. });
  100. }
  101. public void getUserInfo() {
  102. mainPageDAO.getUserInfoModel(new RequestResultListener<UserInfoModel>() {
  103. @Override
  104. public void onResponse(UserInfoModel data) {
  105. // 如果有一些持久化相关的逻辑,可以在类似这里做
  106. // 比如: if (null != data)
  107. // SharedPreferens.get().save(data) 之类的
  108. userInfoData.setValue(data);
  109. }
  110. });
  111. }
  112. public static class MainPageViewModelFactory implements ViewModelProvider.Factory {
  113. private static MainPageViewModelFactory instance;
  114. // DAO层依赖
  115. private MainPageDAO mainPageDAO;
  116. // DO层依赖
  117. private IDomainObject<HealthTipBean> healthTipDO;
  118. /**
  119. * 一般实现都应该是懒加载的,按需使用
  120. */
  121. public static MainPageViewModelFactory getInstance() {
  122. if (null == instance) {
  123. instance = new MainPageViewModelFactory();
  124. }
  125. return instance;
  126. }
  127. private MainPageViewModelFactory() {
  128. }
  129. @NonNull
  130. @Override
  131. public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
  132. if (null == healthTipDO) {
  133. healthTipDO = new HealthTipDO();
  134. }
  135. if (null == mainPageDAO) {
  136. mainPageDAO = new MainPageDAO();
  137. mainPageDAO.setHealthTipDO(healthTipDO);
  138. }
  139. try {
  140. T viewModel = modelClass.newInstance();
  141. modelClass.getMethod("setMainPageDAO", MainPageDAO.class).invoke(viewModel, mainPageDAO);
  142. return viewModel;
  143. } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
  144. throw new RuntimeException("Cannot create an instance of " + modelClass, e);
  145. }
  146. }
  147. }
  148. }

DAO层:

理论上应该直接访问接口并把结果返回,不包含额外的工作。

  1. public class MainPageDAO {
  2. // 理想情况下DO能自动注入进来就好了
  3. private IDomainObject<UserInfoModel> userInfoDO
  4. private IDomainObject<PageDataBean> dataBeanDO;
  5. public void queryData(RequestResultListener<PageDataBean> listener) {
  6. Map<String, Object> map = new HashMap<>();
  7. map.put("userId", "Q.R");
  8. healthTipDO.requestData(map, listener);
  9. }
  10. public void getUserInfoModel(RequestResultListener<UserInfoModel> listener) {
  11. userInfoDO.requestData(null, listener);
  12. }
  13. // ... setter 方法
  14. }

DO层:

这层到底是否需要其实还有待商榷,但是其存在的好处是能把DAO层的工作进一步拆分,提高复用性。

  1. public class MainPageDO implements IDomainObject<PageDataBean> {
  2. @Override
  3. public void requestData(Map<String, Object> paraMap, RequestResultListener<PageDataBean> listener) {
  4. OkManager.getInstance().post("url", paraMap, new OkManager.OkManagerStringListner() {
  5. @Override
  6. public void onResponseString(String string, Integer target, Map<String, Object> resultParams) {
  7. PageDataBean pageDataBean = JsonTools.toObject(string, PageDataBean.class);
  8. if (null != listener) {
  9. listener.onResponse(pageDataBean);
  10. }
  11. }
  12. });
  13. }
  14. @Override
  15. public void insertData(PageDataBean data, InsertCallback listener) {
  16. }
  17. }

DO层接口:

  1. import java.util.Map;
  2. public interface IDomainObject<T> {
  3. void requestData(Map<String, Object> paraMap, RequestResultListener<T> listener);
  4. void insertData(T data, InsertCallback listener);
  5. }

回调接口:

请求返回的回调(无论是操作数据库还是API还是SP之类的)

  1. public interface RequestResultListener<T> {
  2. void onResponse(T data);
  3. }

插入数据的回调

  1. public interface InsertCallback {
  2. void onSuccess(Object object);
  3. void onFailed(Exception e, String message);
  4. }

既然MVVM涉及到状态信息,那么状态和生命周期自然是息息相关的,如果对每个Activity的每个生命周期,都按需去调用ViewModel的方法,未免太麻烦,在这里我设想了一个思路:

首先我们提供生命周期接口:

  1. public interface ActivityLifeCycle {
  2. void onActivityCreate();
  3. void onActivityResumed();
  4. ...
  5. }

然后我们整一个ViewModel基类,实现接口:

  1. public class AbstractActivityViewModel implements ActivityLifeCycle {
  2. }

在BaseActivity里面则有方法:

  1. public class BaseActivity extends AppCompatActivity {
  2. // ...
  3. public AbstractActivityViewModel getViewModel() {
  4. return this.mViewModel;
  5. }
  6. }

最后在 Application 中:

  1. ActivityLifecycleCallbacks activityLifecycleCallbacks = new ActivityLifecycleCallbacks() {
  2. @Override
  3. public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
  4. if (activity instanceof BaseActivity) {
  5. (BaseActivity) activity.getViewModel().onActivityCreate();
  6. }
  7. }
  8. // 此处省略其他周期
  9. @Override
  10. public void onActivityDestroyed(Activity activity) {
  11. if (activity instanceof BaseActivity) {
  12. (BaseActivity) activity.getViewModel().onActivityDestroyed();
  13. }
  14. }
  15. };

也许有更好的代码,这里仅提供一个思路,以及,这里省略了一些判空之类的逻辑。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注