1. 程式人生 > >XMVP:一個通過泛型實現的MVP框架2年的演化路

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都有共用的地方,此時我們需要作出提取抽象。於是我們就需要繼承XBaseActivityXBaseFragmentXBasePresenter再做一層抽象,這樣如果以後不想用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);
    }
}

這樣,我們就可以不斷的複用BaseRefreshActivityArticleDetailContract來讓重新整理載入統一,當然在您實際的使用過程中肯定還需要調整。這裡只給我了我自己的一些思路和實現方式。

如果我們需要其他的抽象時,只需要注意像重新整理一樣將泛型繼承關係標準上就可以啦。如果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