前言
自從官方MVP的Sample出來后,鬧得熱火朝天的MVP,小碼也未能幸免加入MVP大坑中,入坑近2年的MVP的架構(gòu)終于要說拜拜了,最近由于公司項目相對穩(wěn)定,做了一次較大的重構(gòu),原來的MVP架構(gòu)切換到了T-MVVM,模型和視圖徹底分離,整個項目清爽了許多。
問題
MVP缺點:
* Presenter中除了邏輯以外,還有大量的View->Model,Model->View的邏輯操作,造成 Presenter臃腫,維護困難。
* 對UI的渲染放在了Presenter中,所以UI和Presenter的交互會過于頻繁。
* Presenter過多地渲染了UI,往往會使得它與特定的UI的交互頻繁。一旦UI變動,Presenter也需要變
* 接口暴增,可以說代碼量成倍增長,交互都需要通過接口傳遞信息,讓人無法忍受.
基本上用過MVP的碼友們都能發(fā)現(xiàn)了以上諸多弊端。于是小碼就嘗試從傳統(tǒng)的MVP過度到T-MVVM,深度結(jié)構(gòu),告別繁瑣的接傳遞信息。
優(yōu)秀的架構(gòu)能讓軟件高內(nèi)聚、低耦合、可維護、可擴展,其實,對于MVP或者MVVM沒有絕對好壞,MVP->MVVM只不過讓模型和視圖分離得更加的徹底,優(yōu)化成了前者的缺點,如果項目業(yè)務不是很多或者業(yè)務相對簡單,其實完全沒有必要使用MVP/MVVM,反而讓項目變得更為復雜。
T-MVVM代碼,如果有幫助記得star哦
基于ViewModel,LiveData,Retrofit,OkHttp,Rxjava實現(xiàn)T-MVVM體系結(jié)構(gòu)的架構(gòu),泛型限定,深度解耦。
ViewModel優(yōu)點:
* 同步關(guān)聯(lián)生命周期,
* 數(shù)據(jù)共享 ,
* 復用性強 ,
LiveData優(yōu)點:
* 確保UI界面的數(shù)據(jù)狀態(tài),
* 沒有內(nèi)存泄漏,不會因為Activity的不可見導致Crash,
* 不用再人為的處理生命周期,
* 共享資源,
此架構(gòu)未使用DataBinding原由:
* 數(shù)據(jù)綁定增加Bug調(diào)試難度,
* 復雜的頁面,model也會很大,雖然使用方便了也很容易保證了數(shù)據(jù)的一致性,當時長期持有,不利于釋放內(nèi)存,
* 數(shù)據(jù)雙向綁定不利于View重用,
T-MVVM代碼調(diào)用
MVVM的調(diào)用和MVP類似,在MVP中全部由Presenter負責
VieModel之間數(shù)據(jù)同步操,而MVVM中ViewModel到充當了
Presenter的角色,ViewModel是View與 Model的連接器,持有可
被觀察的數(shù)據(jù)持有者和網(wǎng)絡請求操作,數(shù)據(jù)變更實時渲染UI。
T-MVVM架構(gòu)分層代碼
1:先定義BaseViewModel基類
/**
* @author:tqzhang on 18/7/26 16:15
*/
public class BaseViewModel<T extends BaseRepository> extends AndroidViewModel {
public T mRepository;
public BaseViewModel(@NonNull Application application) {
super(application);
mRepository = TUtil.getNewInstance(this, 0);
}
@Override
protected void onCleared() {
super.onCleared();
if (mRepository != null) {
mRepository.unSubscribe();
}
}
}
BaseViewModel通過泛型類型參數(shù)BaseRepository子類初始化Repository數(shù)據(jù)倉庫,同時在activity/fragment走onDestroy()生命周期方法時 BaseViewModel回調(diào)onCleared,即頁面銷毀是用來取消網(wǎng)絡請求或資源釋放等操作。
正常開發(fā)一般不建議直接通過ViewModel獲取網(wǎng)絡數(shù)據(jù),這里我們將工作交給一個新的模塊Repository。Repository只負責數(shù)據(jù)處理,提供干凈的api,同時方便切換數(shù)據(jù)來源。
2:再定義BaseRepository
public abstract class BaseRepository {
protected ApiService apiService;
public BaseRepository() {
if (null == apiService) {
apiService = HttpHelper.getInstance().create(ApiService.class);
}
}
private CompositeSubscription mCompositeSubscription;
protected void addSubscribe(Subscription subscription) {
if (mCompositeSubscription == null) {
mCompositeSubscription = new CompositeSubscription();
}
mCompositeSubscription.add(subscription);
}
public void unSubscribe() {
if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
mCompositeSubscription.clear();
}
}
BaseRepository中內(nèi)容相對簡單,主要是獲取ApiService和網(wǎng)絡請求訂閱容器,方便管理網(wǎng)絡請求,即頁面銷毀是取消網(wǎng)絡請求操作。
3:然后自定義AbsLifecycleFragment基類繼承BaseFragment,BaseFragment可自行編寫。如不需要使用T-MVVM,可自行繼承BaseFragment,互不影響。
public abstract class AbsLifecycleFragment<T extends BaseViewModel> extends BaseFragment {
protected T mViewModel;
/**
* init view
* @param state
*/
@Override
public void initView(Bundle state) {
mViewModel = VMProviders(this, TUtil.getInstance(this, 0));
if (null != mViewModel) {
dataObserver();
}
}
/**
* create ViewModelProviders
*
* @return ViewModel
*/
protected <T extends ViewModel> T VMProviders(BaseFragment fragment, @NonNull Class<T> modelClass) {
return ViewModelProviders.of(fragment).get(modelClass);
}
protected void dataObserver() {
}
/**
* 獲取網(wǎng)絡數(shù)據(jù)
*/
protected void getRemoteData() {
}
}
在initView方法中通過BaseViewModel子類泛型類型參數(shù)獲取Class<T>,在通過ViewModelProviders.of(fragment).get(modelClass))實例化ViewModel,
到此我們的基類基本編寫完畢。
4:下面我們以一個簡單業(yè)務實戰(zhàn)下,獲取文章列表。
4-1:ArticleFragment
/**
* @author:tqzhang on 18/7/2 14:40
*/
public class ArticleFragment extends AbsLifecycleFragment<ArticleViewModel> {
protected TRecyclerView mRecyclerView;
protected StaggeredGridLayoutManager layoutManager;
protected DelegateAdapter adapter;
public static ArticleFragment newInstance() {
return new ArticleFragment();
}
@Override
public int getLayoutResId() {
return R.layout.fragment_list;
}
@Override
public void initView(Bundle state) {
super.initView(state);
mRecyclerView=findViewById(R.id.recycler_view);
initAdapter();
initRecyclerView();
//獲取網(wǎng)絡數(shù)據(jù)
getRemoteData();
}
public void initRecyclerView(){
layoutManager=new new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(layoutManager);
}
//初始化adapter
public void initAdapter(){
adapter= new DelegateAdapter.Builder<>()
.bindArray(ArticleInfoVo.class, new ArticleRem1ItemHolder(context)
, new ArticleRem2ItemHolder(context)
, new ArticleRem3ItemHolder(context))
.bind(HeaderVo.class, new HeaderViewHolder(context, rogressStyle.Pacman))
.bind(FootVo.class, new FootViewHolder(context, ProgressStyle.Pacman))
.build();
//數(shù)據(jù)觀察
@Override
protected void dataObserver() {
mViewModel.getArticleList().observe(this, articleVo -> {
if (null != articleVo) {
mRecyclerView.refreshComplete(articleVo.data.list, false);
}
});
}
//獲取網(wǎng)絡數(shù)據(jù)
@Override
protected void getRemoteData() {
mViewModel.getArticleList(typeId, lastId);
}
}
我們可以看出來ArticleFragment中只有UI初始化,發(fā)請網(wǎng)絡請求action以及數(shù)據(jù)觀察更新UI,列表展示用了TRecyclerView,歡迎star哦面向holder開發(fā)高復用,多類型的刷新庫,從此只關(guān)心你的列表的Item展示。
通過泛型除去了MVP中通過接口傳遞信息的大量代碼,
從此see you Mass implementation of interfaces。
4-1:ArticleViewModel
/**
* @author:tqzhang on 18/7/26 16:15
*/
public class ArticleViewModel extends BaseViewModel<ArticleRepository> {
private MutableLiveData<ArticleVo> mArticleData;
public ArticleViewModel(@NonNull Application application) {
super(application);
}
public LiveData<ArticleVo> getArticleList() {
if (mArticleData == null) {
mArticleData = new MutableLiveData<>();
}
return mArticleData;
}
public void getArticleList(String lectureLevel1, String lastId) {
mRepository.loadArticleRemList(new CallBack<ArticleVo>() {
@Override
public void onNext(ArticleVo articleObject) {
mArticleData.postValue(articleObject);
}
@Override
public void onError(String e) {
}
}
}
ArticleViewModel中持有可被觀察的數(shù)據(jù)持有者LiveData和真正發(fā)起網(wǎng)絡請求動作,在接收到服務端返回的數(shù)據(jù)通過
mArticleData.postValue(articleObject)方式通知注冊的Observer進行數(shù)據(jù)的刷新,此處需注意的是,setValue方法只能在主線程中調(diào)用,而postValue可以在任何線程中調(diào)用,如果是在后臺子線程中更新LiveData的值,必須調(diào)用postValue。
4-3:ArticleRepository
/**
* @author:tqzhang on 18/7/28 13:00
*/
public class ArticleRepository extends BaseRepository {
public void loadArticleRemList(final CallBack<ArticleVo> listener) {
addSubscribe(apiService.getArticleRemList()
.compose(RxSchedulers.io_main())
.subscribe(new RxSubscriber<ArticleVo>() {
@Override
public void onSuccess(ArticleVo articleObject) {
listener.onNext(articleObject);
}
@Override
public void onFailure(String msg) {
listener.onError(msg);
}
}));
}
最后我們的ArticleRepository中就提供不含任何雜質(zhì)的純凈的數(shù)據(jù),此處只提供了網(wǎng)絡層的數(shù)據(jù),在實際應用中可拆分類loacl data和remote data,可根據(jù)實際項目需求自行處理。
至此咋們一個簡單業(yè)務代碼就完成了,是驢子是馬,拉出來溜溜就知道,實踐出真知,效果圖奉上:
之前有碼友們提到多個網(wǎng)絡請求如何處理,如果同一頁面有多個網(wǎng)絡請求操作,其實多網(wǎng)絡請求可以合并處理,統(tǒng)一返回結(jié)果然后組裝數(shù)據(jù)返回,同時也有利于控制空頁面的顯示邏輯。
市面上各種各樣開發(fā)架構(gòu),蘿卜青菜各有所愛,沒有最好的架構(gòu),只有最適合自己的,大家在選型開發(fā)架構(gòu)時理應多多斟酌,當前很火的架構(gòu)并不一定適合自己,結(jié)合自身項目進行舍取。
項目地址github地址:https://github.com/SelfZhangTQ/T-MVVM,歡迎大家交流,star。由于小碼水平有限,有意見或者是建議請各位指正。