Android模塊開發框架 LiveData+ViewModel

Android模塊開發框架 LiveData+ViewModel

前言

爲什麼選擇LiveData+ViewModel

  • LiveData+ViewModel是Android Architecture Component開發組件的一部分,主要的目的是爲了解決android開發過程當中的由於Activity及Fragment生命週期引發的一些常見問題,譬如:內存泄露,異步任務引發空指針,橫豎屏切換界面刷新問題,固然它的做用遠不止於此,好比:LiveData的觀察者模型能夠保障界面在第一時間更新到最新的數據(固然你的LifecycleOwner必須是Alive狀態),解決了多端寫入數據的同步問題;使用LiveData實現View和VM的自動綁定(一般這個綁定的數據流向是單向的,VM->View).另外值得一提的是,AAC框架內部維護了一個ViewModel的內存緩存池,而且會監聽Activity或Fragment的生命週期,在destory的時候自動清空緩存.所以,對於開發者而言,只須要聚焦在業務開發,幾乎不用對接生命週期接口.
  • 感興趣的同窗能夠去看看官方的詳細文檔和Demo

MVP仍是MVVM?

  • mvvm相比mvp最大的區別就是實現了v和vm(p)的自動綁定,mvp中的v和p之間存在較多的接口依賴,不利於擴展及測試,mvvm一般存在一個Binding中介層,經過註解+apt(或反射)的方式,解除v和vm直接的接口依賴,固然mvvm相比於mvp的進步不只僅是代碼解耦,也是從"面向功能接口編程"到"響應式編程"的思想轉變,一切皆是數據(指令)流(ui<->數據<->model)
  • 官方推薦使用MVVM框架,結合DataBinding依賴注入框架實現View和VM的雙向綁定,考慮到使用DataBinding依賴於xml佈局配置,且有較大的理解成本,咱們此次沒有采用嚴格意義上的MVVM框架,而是選擇折中方案:
    • VM->View:經過LiveData實現數據的單向流動
    • View->VM:依然採用傳統的接口實現,可是全部的執行結果都依賴LiveData回傳給View

經典的依賴原則

clean_architecture_reloaded_main.png | center | 608x339

  • 一個框架的好壞,一般會有如下幾個衡量指標:
    • 是否能夠解決當前的業務問題
    • 是否具有好的可擴展性
    • 是否具有好的可測試性
    • 是否遵循模塊化設計原則
    • 邏輯,界面,數據是否分離
  • 固然,還有更多的衡量指標,咱們在這裏不一一列舉,上圖所示的是一個圓環依賴結構,從內到外分別是:業務數據->業務邏輯層->接口適配層->界面,遵循"依賴倒置原則",內部圓圈不能依賴外部圓圈

框架介紹

模塊的內部層級

clean_architecture_reloaded_layers.png | center | 482x372

  • 聽從單向依賴原則,咱們的模塊內部也劃分了一下三個層級,從下往上分別是:
    • 數據層:
      • 主要用來提供界面展現及交互所須要數據,一般會定義獲取數據的策略接口,選擇不一樣的實現(DB,內存,網絡等)
      • 不依賴其餘層級,被邏輯層依賴
    • 邏輯層(領域層)
      • 這一層跟業務強相關,包含複雜的業務邏輯,譬如:獲取數據,提交數據,數據存儲策略的選擇及數據融合等
      • 依賴數據層,被展現層依賴
    • 展現層(表現層)
      • 這一層的主要工做有如下幾個:
        • 構建用戶可見的界面
        • 爲界面展現提供必要的數據
        • 接收並處理用戶交互事件
      • 複雜的業務邏輯都委派給邏輯層(領域層)來處理,這裏的ViewModel能夠理解成一個接口適配器,只負責創建與View之間的通訊渠道,而後傳遞數據或接受指令,自身並不處理複雜的業務邏輯
      • 依賴邏輯層(領域層)

各層級介紹

展現層:使用LiveData實現MVVM的單向綁定

clean_architecture_reloaded_mvvm_app.png | center | 422x202

  • View與VM之間的通訊有兩種
    • View->VM,一般是用戶交互行爲產生的一些指令(可能攜帶一些數據,譬如:用戶登陸行爲會攜帶帳號密碼)
    • VM->View,一般是界面展現所須要的數據(也多是狀態,譬如:加載數據失敗,展現一個Toast提示等)
  • 咱們來舉一個簡單的案例,一個列表界面,須要刷新數據並展現,會有如下幾個必要步驟:
    • 首先,View持有一個ViewModel實例(本身實例化,或則外部傳參均可以)
    • 經過ViewModel獲取一個LiveData對象(同一類LiveData在ViewModel內只能有一個實例),並開始觀察這個LiveData對象(俗稱subscribe)
    • ViewModel接收到"刷新數據"的指令,委派給具體的UseCase來執行
    • UseCase從數據源獲取到數據,寫入到LiveData
    • LiveData通知全部觀察者(固然,會先判斷observer依附的LifecycleOwner是否alive),其中就包括View
    • View從LIveData中獲取到最新的完整的數據列表,刷新展現界面

邏輯層:UseCase處理複雜邏輯

usecase.png | center | 434x411

  • 前面已經提到了,usecase主要用來處理複雜的業務邏輯,減輕ViewModel負擔
  • BaseUseCase能夠看作是一個模板方法類(固然這個模板不必定適用全部業務場景),內部會作一些"線程調度""LiveData賦值"等業務無相關的操做,具體的業務邏輯交給子類實現
  • 這裏有一個Either<Failure, T>返回值,這個是java 8函數式編程的一個特性,相似於c語言裏的union(共同體),主要用來以類型安全的方式返回兩個(或多個)值,感興趣的同窗能夠自行google

數據層:Repository策略

clean_archictecture_reloaded_repository.png | center | 373x313

  • 定義一個獲取(讀/寫)數據的策略接口,實現不一樣的數據讀寫策略,也能夠是多個策略的組合使用,根據具體的業務場景來決定,最大的好處就是可擴展性好,邏輯層(領域層)不用關心數據具體從哪裏來

使用指南

如何界定一個獨立的子模塊

  • 模塊劃分有兩種典型的思路,"按功能用途分模塊","按業務特性分模塊",前者的一個常規作法就是按照Model,View,Present(Controler)等角色對文件進行分組,這樣作最大的弊端就是不利於業務拆分及多人協做編程,因此,咱們推薦按照"業務特性分模塊",譬如:主界面,詳情頁,登陸頁等都是一個相對獨立的模塊
  • 而後,如何界定一個獨立的子模塊,須要知足下面幾個條件:
    • 相對獨立的界面展現(android裏的一個Activity或一個Fragment)
    • 相對獨立的數據來源(你的界面渲染所須要的數據,能夠經過獨立的數據倉庫獲取,譬如:獨立的服務端api接口,獨立的數據表)
    • 用戶交互產生的影響儘量的收斂在界面內(譬如:下拉刷新產生的數據只用來渲染當前頁面)
    • 具有一個閉環的生命週期(模塊使用的內存是可回收的,不建議用單例來實現跨模塊內存共享)
  • 簡單歸納就是:若是一個模塊在脫離其餘模塊的狀況下,依然能以缺省的方式獨立運行,那麼它就是一個相對獨立的模塊

搭建一個子模塊

  • 咱們以一個列表界面爲例子,運行效果:

device-2018-05-23-202059.png | center | 345x613

  • 按照如下步驟開發
    • Step1 數據層:數據倉庫實現
      • 定義數據Bean
      public class HotContentItem {
      
          public String id;
          public String name;
          public String desc;
          public long timeStamp;
      }
      複製代碼
      • 數據倉庫策略實現(數據是本地mock的)
      public class HotContentNetRepository {
      
          //mock數據
          public Either<? extends Failure, List<HotContentItem>> refreshNew() {
              try {
                  Thread.sleep(1000L);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              Either<? extends Failure, List<HotContentItem>> result;
              Random random = new Random();
              boolean success = random.nextInt(10) > 3;
              if (success) {
                  result = Either.right((mockItemList(0)));
              } else if (random.nextInt(10) > 3) {
                  result = Either.right(Collections.<HotContentItem>emptyList());
              } else {
                  result = Either.left(new NetworkFailure());
              }
              return result;
          }
      }
      複製代碼
    • Step2 邏輯層:數據倉庫選擇及使用
      • 省略列這一步,按照業務需求實現不一樣的數據倉庫組合使用
    • Step3 邏輯層:實現UseCase(示例代碼:刷新數據)
    public class HotContentRefreshNew extends BaseUseCase<List<HotContentItem>, Void> {
    
        private HotContentNetRepository mNetRepository;
    
        public HotContentRefreshNew( MutableLiveData<List<HotContentItem>> data, MutableLiveData<Failure> failure) {
            super(data, failure);
            mNetRepository = new HotContentNetRepository();
        }
    
        @Override
        protected Either<? extends Failure, List<HotContentItem>> loadData(Void aVoid) {
            //從網絡獲取數據
            Either<? extends Failure, List<HotContentItem>> result = mNetRepository.refreshNew();
            if (result.isRight() && CollectionUtil.isEmpty(result.right())) {
                Failure failure = new RefreshNewFailure(RefreshNewFailure.CODE_DATA_EMPTY, "Data is empty!");
                result = Either.left(failure);
            }
            return result;
        }
    
        @Override
        protected Failure processFailure(Failure failure) {
            ...
        }
    }
    複製代碼
    • Step4 展現層:UI框架選擇
      • 示例界面是做爲一個TabLayout的一個Page頁,所以這裏選擇"具有生命週期View"做爲的UI框架,這是個自定的View,實現了LifecycleOwner接口(參考了LifecycleActivity和LifecycleFragment的實現邏輯)
      public abstract class BaseLifecycleView extends FrameLayout implements LifecycleOwner {
      
          private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
          private ViewModelStore mViewModelStore = new ViewModelStore();
      
          public BaseLifecycleView(@NonNull Context context) {
              super(context);
          }
      
          protected abstract void onCreate();
      
          protected abstract void onDestroy();
      
          @Override
          public Lifecycle getLifecycle() {
              return mRegistry;
          }
      
          @Override
          @CallSuper
          protected void onAttachedToWindow() {
              super.onAttachedToWindow();
              mRegistry.handleLifecycleEvent(Event.ON_CREATE);
              onCreate();
              if (getVisibility() == View.VISIBLE) {
                  mRegistry.handleLifecycleEvent(Event.ON_START);
              }
          }
      
          @Override
          @CallSuper
          protected void onDetachedFromWindow() {
              super.onDetachedFromWindow();
              mRegistry.handleLifecycleEvent(Event.ON_DESTROY);
              mViewModelStore.clear();
              onDestroy();
          }
      
          @Override
          @CallSuper
          protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
              super.onVisibilityChanged(changedView, visibility);
              Event event = visibility == View.VISIBLE ? Event.ON_RESUME : Event.ON_PAUSE;
              mRegistry.handleLifecycleEvent(event);
          }
      
          @Override
          @CallSuper
          public void onStartTemporaryDetach() {
              super.onStartTemporaryDetach();
              State state = mRegistry.getCurrentState();
              if (state == State.RESUMED) {
                  mRegistry.handleLifecycleEvent(Event.ON_STOP);
              }
          }
      
          @Override
          @CallSuper
          public void onFinishTemporaryDetach() {
              super.onFinishTemporaryDetach();
              State state = mRegistry.getCurrentState();
              if (state == State.CREATED) {
                  mRegistry.handleLifecycleEvent(Event.ON_START);
              }
          }
      
          protected <T extends ViewModel> T getViewModel(@NonNull ViewModelProvider.NewInstanceFactory modelFactory, @NonNull Class<T> modelClass) {
              return new ViewModelProvider(mViewModelStore, modelFactory).get(modelClass);
          }
      
      }
      複製代碼
    • Step5 展現層:定義本身的LiveData和ViewModel
    public class HotContentViewModel extends BaseViewModel<List<HotContentItem>> {
    
        private HotContentRefreshNew mRefreshNew;
    
        public HotContentViewModel() {
            refreshNew();
        }
    
        public void refreshNew() {
            AssertUtil.mustInUiThread();
            if (mRefreshNew == null) {
                mRefreshNew = new HotContentRefreshNew(getMutableLiveData(), getMutableFailure());
            }
            //經過usecase執行具體的刷新操做
            mRefreshNew.executeOnAsyncThread(null);
        }
    
       ...
    }
    複製代碼
    • Step6 展現層:關聯V和VM
    public class HotContentView extends BaseLifecycleView {
    
        private HotContentViewModel mViewModel;
    
        private SwipeRefreshLayout mSwipeRefreshLayout;
        private AutoLoadMoreRecycleView mRecyclerView;
        private HotContentAdapter mContentAdapter;
    
        public HotContentView(@NonNull Context context) {
            super(context);
            視圖對象初始化
            ...
            mRecyclerView.setLoadMoreListener(new LoadMoreListener() {
                @Override
                public void onLoadMore() {
                    HotContentItem lastOne = CollectionUtil.lastOne(mViewModel.getData().getValue());
                    if (lastOne == null) {
                        mRecyclerView.completeLoadMore("No more data");
                    } else {
                        mViewModel.loadHistory(lastOne);
                    }
                }
            });
            ...
            mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    //刷新數據
                    mViewModel.refreshNew();
                }
            });
        }
    
        @Override
        protected void onCreate() {
            mViewModel = getViewModel(new NewInstanceFactory(), HotContentViewModel.class);
            mViewModel.getData().observe(this, new Observer<List<HotContentItem>>() {
                @Override
                public void onChanged(@Nullable List<HotContentItem> hotContentItems) {
                    //刷新數據成功
                    mContentAdapter.setItemList(hotContentItems);
                    mSwipeRefreshLayout.setRefreshing(false);
                    ...
                }
            });
            ...
        }
    
        @Override
        protected void onDestroy() {
        }
    
    }
    複製代碼

遇到的問題

  • 複雜的UI交互指令如何傳達給ViewModel
    • 在本文開頭"MVP仍是MVVM"框架選型中咱們已經提過,目前並無使用到MVVM的精髓"DataBinding",而是經過LiveData觀察者模式實現V->VM的單向綁定(即:數據能夠從VM自動流向V,可是V的操做指令沒法自動傳遞給VM),所以,複雜交互(譬如:下拉刷新,滾動加載更多)仍是須要經過傳統的MVP思惟在VM中定義功能接口提供給V來調用
  • 除了數據以外,還有狀態會影響界面展現
    • 理想狀態下,VM提供一個LiveData給View使用,這個LiveData包含了View渲染須要的所有數據,可是不少狀況下View並不會只依賴單一類型數據,譬如:下拉刷新操做,會有如下三種結果返回:列表數據,空數據,失敗.對於"列表數據"咱們能夠經過LiveData通知View作總體刷新,可是"空數據""失敗"的狀況也須要在界面上有所提示,而這兩個返回值是不能影響當前的"列表數據"(即:不影響當前的列表展現),而應該看作是獨立與數據以外的"指令"更合適,它們最大的特徵就是"一次性",不須要像"列表數據"那樣存儲處理(能夠理解成是給界面消費的一次性事件)
    • 再回到LiveData,LiveData主要用來存儲相對持久的數據,而且任什麼時候候View從LiveData獲取的數據都必須是"完整的"能夠用來直接渲染界面的,回到上面"下拉刷新"的例子,若是咱們將"空數據""失敗"也經過LiveData封裝,而後由View來觀察這個LiveData(自定義一個Observer),在收到對應的"指令"通知的時候處理"界面提示",這樣彷佛也能知足VM->View的狀態通知需求,問題來了,因爲Observer的生命週期極可能會比LiveData的生命週期更短(取決於Observer依賴的LifecycleOwner)(好比:Observer的生命週期和ViewPager裏的某一個View一致,LiveData的生命週期和Activity一致),那麼當View被複用的時候會再次觀察同一個LiveData,而後自動收到LiveData的通知,獲取LiveData最新的數據(譬如:"失敗"指令),刷新界面(提示"刷新失敗"),這樣就會很奇怪了,明明沒有刷新動做,無緣無故提示"刷新失敗"
    • 解決辦法仍是回到"指令"的特徵"一次性",定義一個DisposableLiveData,每次執行setData(會通知觀察者,也就是View)以後當即將data置空,這樣下次再getData時候就會返回null,而不是一個"未預期的數據"
      • 代碼實現很簡單
      public class DisposableLiveData<T> extends MutableLiveData<T> {
      
          @Override
          public void postValue(T value) {
              super.postValue(value);
              if (value != null) {
                  super.postValue(null);
              }
          }
      
          @Override
          public void setValue(T value) {
              super.setValue(value);
              if (value != null) {
                  super.postValue(null);
              }
          }    
      複製代碼
      • 示例代碼:
      public class HotContentView extends BaseLifecycleView {
      
          private HotContentViewModel mViewModel;
          private SwipeRefreshLayout mSwipeRefreshLayout;
      
      
          public HotContentView(@NonNull Context context) {
              super(context);
              ...
      
              mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                  @Override
                  public void onRefresh() {
                   &emsp;&emsp;//刷新數據
                      mViewModel.refreshNew();
                  }
              });
          }
      
          @Override
          protected void onCreate() {
              mViewModel = getViewModel(new NewInstanceFactory(), HotContentViewModel.class);
            &emsp;...
              mViewModel.getFailure().observe(this, new Observer<Failure>() {
                  @Override
                  public void onChanged(@Nullable Failure failure) {
                      //處理失敗提示
                      if (failure instanceof RefreshNewFailure) {
                          mSwipeRefreshLayout.setRefreshing(false);
                          ToastManager.getInstance().showToast(getContext(), ((RefreshNewFailure)failure).getMessage(),
                              Toast.LENGTH_SHORT);
                      }
                      ...
                  }
              });
          }
      
          @Override
          protected void onDestroy() {
          }
      
      }
      複製代碼
相關文章
相關標籤/搜索