XMVP:一個通過泛型實現的MVP框架2年的演化路
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
- 首先定義契約(Contract),定義View、Model、Presenter的介面,並且都需要繼承自
XContract
- 建立Model實現類
- 建立Presenter實現類繼承
XBasePresenter
,泛型中關聯View介面和Model實現類 - 建立View實現類繼承
XBaseActivity
或其子類,泛型中關聯Presenter實現類
// 契約
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 {}
就這樣,第一階段宣告完畢,然後就是優化和修復一些bug,當然上面的
getGenericClass
這個方法也是後期優化過的結果。
想偷懶了就開發了MVPManager
外掛,快速生成XMVP
程式碼
也就是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