XMVP:一個通過泛型實現的MVP框架2年的演化路

image
XMVP框架是我的第一個框架,剛從Android起步第一次瞭解MVP模式時決心寫一個自己的東西框架,到現在已運用在我寫的多個專案中。雖然兩年了,但核心的思路沒有改變,到現在變換也不是太多,精簡了一些程式碼,添加了一些功能。
起步2016
這是個剛出社會找工作痛苦的時期,我個人不太喜歡生活中麻煩的事情。安靜是我的本性,於是想寫個屬於自己的框架,為未來做些鋪墊。於是,便有了XMVP,名字“X”是臭不要臉的加上了自己名字的開頭字母。
目標
該框架的目標很簡單,為了省掉View、Model、Presenter層之間的依賴實現過程,通過簡單的配置,框架自動實現依賴關係
實現的原理:獲取配置的泛型型別,通過反射例項化P層和M層。
程式碼
1.關鍵能讓我開始做這個框架的核心程式碼如下:
傳入物件的Class和需要過濾泛型得的匹配的物件,然後遍歷 klass
中配置的泛型判斷是不是 filterClass
的子類,如果是則找到了配置的泛型型別。
public static <T> Class<T> getGenericClass(Class<?> klass, Class<?> filterClass) { Type type = klass.getGenericSuperclass(); // 獲取父類Class型別,它包含了所配置的泛型型別 if (type == null || !(type instanceof ParameterizedType)) return null; // 判斷是否是泛型型別 ParameterizedType parameterizedType = (ParameterizedType) type; Type[] types = parameterizedType.getActualTypeArguments(); // 由於一個類可能不止配置了一個泛型,獲取該物件所有泛型型別 for (Type t : types) { Class<T> tClass = (Class<T>) t; if (filterClass.isAssignableFrom(tClass)) { // 通過filterClass找到需要的目標型別 return tClass; } } return null; }
2.使用也非常簡單,精簡程式碼如下所示,4步配置就實現了MVP
XContract XBasePresenter XBaseActivity
// 契約 public interface HomeContract { interface Presenter extends XContract.Presenter{} interface View extends XContract.View{} interface Model extends XContract.Model {} } // M層實現 public class HomeModel implements HomeContract.Model {} // P層實現 public class HomePresenter extends XBasePresenter<HomeContract.View, HomeModel> implements HomeContract.Presenter {} // V層實現 public class HomeActivity extends XBaseActivity<HomePresenter> implements HomeContract.View {}
3.最初XMVP框架做出時寫的一篇文章 ofollow,noindex">封了一個Android MVP框架,就叫XMVP吧!
就這樣,第一階段宣告完畢,然後就是優化和修復一些bug,當然上面的 getGenericClass
這個方法也是後期優化過的結果。
想偷懶了就開發了 MVPManager
外掛,快速生成 XMVP
程式碼

image
XMVP
框架開發出來也就1個月之內的事情吧!這時感覺寫契約(Contract),寫
XMVP
各個實現類,都是重複的勞動力,每一個新的介面就得去建立這麼些檔案太過辛苦。結果雖然變得有條理有模組,但是工作量有些重複和增加,有些時候配置泛型忘了還需要看之前是怎麼配置的。
當時其實也有建立MVP檔案的外掛之類的東西,但是不符合 XMVP
的實情,泛型還是得自己動手,於是決心自己寫一個 intellij
外掛,當然在 AS
中也能使用。
建立MVP程式碼截圖,這是最新的建立程式碼介面的截圖
在1.0的基礎上,增加了可將同一個模組放一個包中或將MVP分在對應的包中的選項;增加了可以不是XMVP框架的情況下使用

這是一張動態圖,是一張舊版本的演示圖。只需要和上面的截圖結合來看一下哦

最後還有個逆向增加或刪除XMVP契約中定義方法的功能
會同時更新實現類的方法,本人是寫出這個功能,但幾乎不用的啦

剛剛開發出來MVPManager的時候,我也寫了篇文章介紹 這個AS外掛能幫你快速管理MVP
就這樣,第二階段結束了。其主要目的就是為了解決MVP重複邏輯的程式碼量問題
實踐中的更新
在不斷的實踐運用中也發現了很多沒有考慮到或者忽略的問題,其中最映像深刻的不過於有次上線應用的時候,混淆居然會導致無法建立Presenter熬夜找了很久。
還有就是忽略了Fragment有app包和v4包兩個地方,框架中只寫了一個,考慮的都比較片面。
只有在實踐中才能真正的考驗,一直以來大概就我和少數的小夥伴在使用。雖然用的比較少,但是寫出來後就要對它負責嘛!
使用中的一些個人技巧
1.很多時候,Activity和Presenter,更或者Model都有共用的地方,此時我們需要作出提取抽象。於是我們就需要繼承 XBaseActivity
、 XBaseFragment
、 XBasePresenter
再做一層抽象,這樣如果以後不想用 XMVP
框架有更好的選擇也更好替換哈。如下所示:
BaseActivity.java
public abstract class BaseActivity<T extends XBasePresenter> extends XBaseActivity<T> { @Override public void onInitCircle() { super.onInitCircle(); ButterKnife.bind(this); } }
BasePresenter.java
public class BasePresenter<T extends XContract.View, E extends XContract.Model> extends XBasePresenter<T, E> { protected CompositeDisposable mCompositeDisposable; @Override public void end() { super.end(); if (mCompositeDisposable != null) { mCompositeDisposable.clear(); mCompositeDisposable = null; } } }
向上面這樣,我們通過一箇中間層,處理一些我們需要統一呼叫的或處理的一些東西
2.對於Presenter回撥Model處理後返回的資料監聽,我們可以定義一個通用監聽介面,如下:
public interface GenericListener<T> { void success(T t); void error(int code, String err); }
並且,我們可以對這個介面進行實現,我們可以統一對錯誤資訊做些提示或處理
public abstract class GenericListenerImp<T> implements GenericListener<T> { public GenericListenerImp(/*可以傳入進來base view或base presenter,如果有錯誤可以呼叫對應方法統一處理*/) { } @Override public void error(int code, String err) { // 對錯誤做出統一處理 } }
3.我們最常用的就是重新整理載入列表了,幾乎所有app中都需要,並且在同一個應用中的載入邏輯都是一樣的,於是我們可以將其抽象出來,使用的時候會非常方便。
首先定義一個基礎重新整理契約,每一個有重新整理的view的介面都直接從這裡繼承
public interface BaseRefreshContract { interface Presenter extends XContract.Presenter { /** * 請求資料 */ void requestLoadListData(int page); /** * 請求更新列表資料 */ void requestUpdateListData(); } interface View <X> extends XContract.View { /** * 更新列表成功 */ void updateListSuccess(List<X> datas, boolean isEnd); /** * 更新失敗 */ void updateListFail(String err); /** * 載入資料成功 */ void loadListDataSuccess(List<X> datas, int currentPage, boolean isEnd); /** * 載入資料失敗 * @param err */ void loadListDataFail(String err); /** * 資料已經被載入完 */ void loadListDateOver(); } }
然後抽象View,這裡以Activity為例,Fragment一致。我使用了 SwipeRefreshLayout
作為重新整理, BaseRecyclerViewAdapterHelper
處理填充資料和載入資料
public abstract class BaseRefreshActivity<E ,X extends BaseQuickAdapter<E, BaseViewHolder>, T extends XBasePresenter> extends BaseActivity<T> implements BaseRefreshContract.View<E>, BaseQuickAdapter.RequestLoadMoreListener, SwipeRefreshLayout.OnRefreshListener { protected int currentPage;//當前的頁面 protected X mAdapter; protected boolean isEnd; protected SwipeRefreshLayout swipeLayout; protected RecyclerView mRecyclerView; @Override public void onInitCircle() { super.onInitCircle(); mAdapter = getAdapter(); mRecyclerView = getRecyclerView(); swipeLayout = getSwipeLayout(); swipeLayout.setOnRefreshListener(this); mAdapter.setOnLoadMoreListener(this, mRecyclerView); mRecyclerView.setAdapter(mAdapter); } protected abstract X getAdapter(); protected abstract SwipeRefreshLayout getSwipeLayout(); protected abstract RecyclerView getRecyclerView(); /** * 更新列表成功 */ @Override public void updateListSuccess(List<E> datas, boolean isEnd) { this.isEnd = isEnd; currentPage = 1; mAdapter.setNewData(datas); swipeLayout.setRefreshing(false); if (isEnd) { loadListDateOver(); } else { mAdapter.setEnableLoadMore(true); mAdapter.loadMoreComplete(); } } /** * 更新失敗 */ @Override public void updateListFail(String err) { swipeLayout.setRefreshing(false); mAdapter.setEnableLoadMore(true); ToastUtil.getInstance().showLongT(err); } /** * 載入資料成功 */ @Override public void loadListDataSuccess(List<E> datas, int currentPage, boolean isEnd) { this.currentPage = currentPage; this.isEnd = isEnd; mAdapter.addData(datas); swipeLayout.setEnabled(true); mAdapter.loadMoreComplete(); } /** * 載入資料失敗 * * @param err */ @Override public void loadListDataFail(String err) { swipeLayout.setEnabled(true); mAdapter.loadMoreFail(); ToastUtil.getInstance().showLongT(err); } /** * 資料已經被載入完 */ @Override public void loadListDateOver() { mAdapter.loadMoreEnd(); } @Override public void onRefresh() { if (!swipeLayout.isRefreshing()) { swipeLayout.setRefreshing(true); } getPresenter().requestUpdateListData(); mAdapter.setEnableLoadMore(false); } @Override public void onLoadMoreRequested() { if (isEnd) { loadListDateOver(); return; } swipeLayout.setEnabled(false); getPresenter().requestLoadListData(++currentPage); } protected BaseRefreshContract.Presenter getPresenter() { if (presenter instanceof BaseRefreshContract.Presenter) { return(BaseRefreshContract.Presenter) presenter; } else { throw new RuntimeException("presenter please extends BaseRefreshContract.Presenter"); } } }
使用:定義一個列表頁面的契約
public interface ArticleDetailContract { interface View extends BaseRefreshContract.View<CircleMsgEntity.CommentBean> { } interface Presenter extends BaseRefreshContract.Presenter { } interface Model extends XContract.Model { void catArticleDetails(int articleId, int page, CompositeDisposable cd, NetRequestListener<Result<CircleMsgEntity>> listener); } }
使用:View實現層,由於這是一個公司專案中的類,省略的所有的無關程式碼
CircleMsgEntity.CommentBean
是一個Adapter(ArticleCommentAdapter)填充的實體類
ArticleCommentAdapter
是一個繼承BaseRecyclerViewAdapterHelper框架中的 BaseQuickAdapter
的類,並且該類是這樣的: public class ArticleCommentAdapter extends BaseQuickAdapter<CircleMsgEntity.CommentBean, BaseViewHolder>
必須保證Activity第一個泛型和Adapter的第一個泛型型別一致
public class ArticleDetailActivity extends BaseRefreshActivity<CircleMsgEntity.CommentBean, ArticleCommentAdapter, ArticleDetailPresenter> implements ArticleDetailContract.View { @BindView(R.id.recycler) RecyclerView recycler; @BindView(R.id.refresh) SwipeRefreshLayout refresh; @Override public void onInitCircle() { super.onInitCircle(); onRefresh(); } @Override public int layoutId() { return R.layout.activity_article_detail; } @Override protected ArticleCommentAdapter getAdapter() { return new ArticleCommentAdapter(ArticleCommentAdapter.TYPE_NORMAL); } @Override protected SwipeRefreshLayout getSwipeLayout() { refresh.setColorSchemeResources(R.color.yellow); return refresh; } @Override protected RecyclerView getRecyclerView() { recycler.setLayoutManager(new LinearLayoutManager(this)); return recycler; } }
使用: Presenter實現層
public class ArticleDetailPresenter extends BasePresenter<ArticleDetailContract.View,ArticleDetailModel> implements ArticleDetailContract.Presenter { @Override public void start() { super.start(); mCompositeDisposable = new CompositeDisposable(); } @Override public void requestLoadListData(int page) { model.catArticleDetails(view.getArticleId(), page, mCompositeDisposable, new NetRequestListener<Result<CircleMsgEntity>>() { @Override public void success(Result<CircleMsgEntity> circleMsgEntityResult) { ResultListBean<List<CircleMsgEntity.CommentBean>> resultListBean = circleMsgEntityResult.getData().getCommentList(); view.loadListDataSuccess(resultListBean.getList(), resultListBean.getPage(), resultListBean.getPage() >= resultListBean.getPagecount()); } @Override public void error(String err) { view.loadListDataFail(err); } }); } @Override public void requestUpdateListData() { model.catArticleDetails(view.getArticleId(), 1, mCompositeDisposable, new NetRequestListener<Result<CircleMsgEntity>>() { @Override public void success(Result<CircleMsgEntity> circleMsgEntityResult) { ResultListBean<List<CircleMsgEntity.CommentBean>> resultListBean = circleMsgEntityResult.getData().getCommentList(); view.updateListSuccess(resultListBean.getList(), resultListBean.getPage() >= resultListBean.getPagecount()); view.displayArticleDetail(circleMsgEntityResult.getData()); } @Override public void error(String err) { view.updateListFail(err); } }); } }
Model層,去請求列表資料
public class ArticleDetailModel implements ArticleDetailContract.Model { @Override public void catArticleDetails(int articleId, int page, CompositeDisposable cd, NetRequestListener<Result<CircleMsgEntity>> listener) { Observer<Result<CircleMsgEntity>> observer = ModelHelper.getObserver(cd, listener, true); // 對rxjava返回的資料進行統一處理 Http.getInstance().getArticleDetails(articleId, page, observer); } }
這樣,我們就可以不斷的複用 BaseRefreshActivity
、 ArticleDetailContract
來讓重新整理載入統一,當然在您實際的使用過程中肯定還需要調整。這裡只給我了我自己的一些思路和實現方式。
如果我們需要其他的抽象時,只需要注意像重新整理一樣將泛型繼承關係標準上就可以啦。如果Model也可以抽取出來,複用時只需要繼承抽象出來的Model。
目前
目前XMVP框架已更新到 1.2.2
,廢棄了一些以前的方法(當然現在還能用)。添加了更多View層的輔助方法,為了偷個懶,就直接展示新增的方法程式碼啦!
基本週期
/** * author: xujiaji * created on: 2018/9/4 10:57 * description: 定義View相關週期 <br /> Define View related Cycle */ public interface XViewCycle { /** * 在 super {@link android.app.Activity#onCreate(Bundle)}之前被呼叫<br />will be called before super class {@link android.app.Activity#onCreate(Bundle)} called */ void onBeforeCreateCircle(); /** * 在 super {@link android.app.Activity#onCreate(Bundle)}之前被呼叫,並且有Bundle <br />will be called before super class {@link android.app.Activity#onCreate(Bundle)} called * @param savedInstanceState 該引數不可能為null<br /> this parameter cannot be null */ void onBundleHandle(@NonNull Bundle savedInstanceState); /** * 獲取佈局的id<br /> get layout id * 在 {@link #onBeforeCreateCircle }之後被呼叫 <br /> will be called after {@link #onBeforeCreateCircle } called * @return xml佈局id<br /> xml layout id */ int layoutId(); /** *在這裡面進行初始化<br /> initialize here *在 {@link #layoutId()} 之後被呼叫<br /> will be called after {@link #layoutId()} called */ void onInitCircle(); /** * 這裡面寫監聽事件<br /> write listens event here * 在 {@link #onInitCircle()} 之後被呼叫 <br /> will be called after {@link #onInitCircle()} called */ void onListenerCircle(); }
Activiy中
/** * author: xujiaji * created on: 2018/9/11 15:05 * description: 定義Activity View相關週期 <br /> Define Activity View related Cycle */ public interface XActivityCycle extends XViewCycle { /** * 處理上個頁面傳遞過來的資料 <br /> Handle the data passed from the previous page */ void onIntentHandle(@NonNull Intent intent); }
Fragment中
/** * author: xujiaji * created on: 2018/9/4 10:57 * description: 定義Fragment View相關週期 <br /> Define Fragment View related Cycle */ public interface XFragViewCycle extends XViewCycle { /** * 處理{@link Fragment#getArguments()} 的值,如果有才會呼叫<br /> Handle the value of {@link Fragment#getArguments()} , if it is there, it will be called * @param bundle */ void onArgumentsHandle(@NonNull Bundle bundle); void onVisible(); void onInvisible(); void onLazyLoad(); /** * 忽略{@link #isFirstLoad() }的值,強制重新整理資料,但仍要滿足 {@link #isFragmentVisible()} && {@link #isPrepared()} <br /> * Ignore the value of {@link #isFirstLoad() } to force refresh data, but still satisfy {@link #isFragmentVisible()} && {@link #isPrepared()} */ void setForceLoad(boolean forceLoad); boolean isForceLoad(); boolean isPrepared(); boolean isFirstLoad(); boolean isFragmentVisible(); /** * 是否是在ViewPager中,預設為true * whether in ViewPager, default is true * @return */ boolean isInViewPager(); }
可以看到Fragment中定義的方法是比較多的,因為由於懶載入比較常用,新增了懶載入。我們如果需要載入資料,可直接在 onLazyLoad()
方法中進行。
需要注意:如果Fragment不是和ViewPager結合,需要將 isInViewPager
返回false,預設返回的true。如果不這樣,可能會導致通過FragmentManger提交的Fragment無法呼叫到 onLazyLoad
方法。
最後
通過寫這個框架學到了思考很多東西,並且後期也會繼續更新,我自己寫專案中也在使用。可能有些地方考慮的不充足,謝謝大家也可以提建議。當然這只是MVP的一種實現思路,其他的還是有很多的,這裡大家也許都有一定了解哈。
XMVP地址: https://github.com/xujiaji/XMVP
歡迎大家Star、Fork、PR (〃'▽'〃)