演示效果:
這里的演示效果是利用TabLayout+ViewPager+Fragment完成的空执,由于本篇文章不是講具體實(shí)現(xiàn)艰躺,所以和MVP思想不相關(guān)的具體實(shí)現(xiàn)代碼我會(huì)忽略一部分。
前言
MVP開發(fā)模式陷揪,算是老生常談的話題了,最近一段時(shí)間也比較熱門,網(wǎng)上相關(guān)的資料不少肠槽,這里就不再做重復(fù)的描述。
這里談一下個(gè)人的一些理解:
由于Android平臺(tái)的特殊性奢啥,從MVC的角度來講秸仙,XML布局文件作為視圖層(V),Activity作為控制層(C)桩盲,一些具體的業(yè)務(wù)操作寂纪,如讀寫數(shù)據(jù)庫,網(wǎng)絡(luò)訪問等作為業(yè)務(wù)邏輯層(M),單純的XML布局又不能滿足V層的需要捞蛋,例如我們需要對(duì)一個(gè)控件進(jìn)行動(dòng)態(tài)的隱藏和顯示孝冒,這點(diǎn)單純的XML是辦不到的,此時(shí)我們只能在Activity里去做實(shí)現(xiàn)了拟杉,這樣子下來庄涡,使得Activity既是V又是C,隨著業(yè)務(wù)的擴(kuò)展捣域,代碼邏輯也漸漸變得復(fù)雜起來啼染,加上MVC模式的設(shè)計(jì),Model層和View層又是直接交互的焕梅,導(dǎo)致Activity的職責(zé)會(huì)變得很重迹鹅,慢慢的變得臃腫不堪,不易擴(kuò)展贞言,更不要提測(cè)試了斜棚。
既然有問題的存在,自然就有解決問題的辦法该窗,此時(shí)MVP出現(xiàn)了弟蚀,在我看來MVP模式其實(shí)就是從并不標(biāo)準(zhǔn)的MVC模式演化而來的,它減輕了Activity的職責(zé)酗失,簡化了Activity的代碼义钉,把復(fù)雜的邏輯交給了P層來處理,較低了系統(tǒng)的耦合度规肴,更加方便了測(cè)試捶闸。
這樣的開發(fā)模式下來,也使得各層的職責(zé)更加單一了拖刃,V層只關(guān)心用戶的輸入請(qǐng)求動(dòng)作删壮,P層相當(dāng)于V層和M層的“信使”只關(guān)心轉(zhuǎn)發(fā)數(shù)據(jù),來自V層的請(qǐng)求數(shù)據(jù)兑牡,來自M層的響應(yīng)數(shù)據(jù)央碟,而M層則只需要關(guān)心的具體的業(yè)務(wù)邏輯操作即可。
言歸正傳
說了這么多均函,來點(diǎn)實(shí)戰(zhàn)吧亿虽,這里我演示一個(gè)比較常見的功能,列表的下拉刷新和上拉加載數(shù)據(jù)边酒。
所涉及的開源框架:
網(wǎng)絡(luò)加載框架:Retrofit
圖片加載框架:Picasso
響應(yīng)式編程框架:Rxjava
注解框架:ButterKnife
帶有下拉刷新经柴,上拉加載的RecyclerView:XRecyclerView
JSON數(shù)據(jù)的解析:Gson
數(shù)據(jù)來源
這里的來源是來自GANK.IO的福利妹紙圖,然后利用GsonFormat生成JavaBean墩朦,代碼太長我就不貼了坯认。
關(guān)于基類
View層
/** * 基類View接口
* Created by chenwei.li
* Date: 2016-01-11
* Time: 00:22
*/
public interface IBaseView {
void showLoadingDialog();
void cancelLoadingDialog();
void showErrorMsg(String errorMsg);
}
這個(gè)基礎(chǔ)View接口需要讓每個(gè)具體的業(yè)務(wù)View都去繼承實(shí)現(xiàn),里面封裝了一些常用的方法,例如我們?cè)诩虞d數(shù)據(jù)的時(shí)候都為了良好的用戶體驗(yàn)牛哺,都需要顯示進(jìn)度條陋气,或者彈出對(duì)話框來提醒用戶正在進(jìn)行,當(dāng)數(shù)據(jù)加載完畢都需要關(guān)閉這個(gè)提醒引润,當(dāng)加載出錯(cuò)的時(shí)候巩趁,需要提醒用戶出錯(cuò)信息。
/**
* 基類Fragment
* Created by chenwei.li
* Date: 2016-01-03
* Time: 23:52
*/
public abstract class BaseFragment extends Fragment implements IBaseFragment {
//當(dāng)前Fragment視圖對(duì)象
protected View mView;
/**
* Activity對(duì)象淳附,避免getActivity()出現(xiàn)null
*/
protected Activity mActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity) context;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/**
* 綁定視圖
*/
if (mView == null) {
mView = inflater.inflate(bindLayout(), container, false);
}
/**
* 依賴注入
*/
ButterKnife.bind(this, mView);
/**
* 創(chuàng)建Fragment
*/
createFragment(savedInstanceState);
/**
* 初始化視圖,默認(rèn)值
*/
initView();
/**
* 獲取對(duì)應(yīng)數(shù)據(jù)(網(wǎng)絡(luò)议慰,數(shù)據(jù)庫)
*/
getData();
return mView;
}
@Override
public void onDestroy() {
super.onDestroy();
/**
* 解綁ButterKnife
*/
ButterKnife.unbind(this);
}
}
/**
* 基類Fragment(懶加載)
* Create by: chenwei.li
* Date: 2016-05-26
* time: 09:15
* Email: lichenwei.me@foxmail.com
*/
public abstract class BaseLazyFragment extends Fragment implements IBaseFragment {
//當(dāng)前Fragment視圖對(duì)象
protected View mView;
/**
* 是否加載布局
*/
protected boolean isPrepare;
/**
* 是否顯示布局
*/
protected boolean isVisiable;
/**
* 是否是第一次加載
*/
protected boolean isFirst;
/**
* Activity對(duì)象,避免getActivity()出現(xiàn)null
*/
protected Activity mActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity) context;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/**
* 綁定視圖
*/
if (mView == null) {
mView = inflater.inflate(bindLayout(), container, false);
isPrepare = true;
isFirst = true;
}
/**
* 依賴注入
*/
ButterKnife.bind(this, mView);
/**
* 創(chuàng)建Fragment
*/
createFragment(savedInstanceState);
/**
* 初始化視圖,默認(rèn)值
*/
initView();
return mView;
}
/**
* 判斷當(dāng)前Fragment是否已經(jīng)顯示
*
* @param isVisibleToUser
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisiable = true;
onVisible();
} else {
isVisiable = false;
}
}
protected void onVisible() {
lazy();
}
/**
* 要進(jìn)行懶加載的數(shù)據(jù)
*/
protected void lazy() {
if (isPrepare && isVisiable && isFirst) {
lazyLoadData();
isFirst = false;
}
}
/**
* 需要進(jìn)行懶加載的數(shù)據(jù)
*/
protected abstract void lazyLoadData();
/**
* 只需要空實(shí)現(xiàn)奴曙,所有的懶加載數(shù)據(jù)都在lazyLoadData里面執(zhí)行
*/
@Override
public void getData() {
}
}
上面是基礎(chǔ)類BaseFragment的封裝别凹,實(shí)現(xiàn)了IBaseFragment接口里的方法,代碼并不復(fù)雜洽糟,大家根據(jù)注釋看下就可以理解了炉菲,這里因?yàn)槲业淖⑷肟蚣苡玫腂utterKnife,所以在創(chuàng)建Frgament的時(shí)候坤溃,我去綁定了View拍霜,當(dāng)Fragment銷毀的時(shí)候,去取消綁定薪介。
這里根據(jù)具體業(yè)務(wù)祠饺,我又多封裝了一個(gè)具有懶加載功能的Fragment,這個(gè)LazyBaseFragment和普通的Fragment區(qū)別在于汁政,當(dāng)我們使用ViewPager去嵌套Fragment的時(shí)候吠裆,由于ViewPaer的原因,默認(rèn)它會(huì)加載當(dāng)前頁面的左右各一個(gè)頁卡烂完,有時(shí)候用戶需要看當(dāng)前頁面并不執(zhí)行滑動(dòng)操作去看其他的,但是程序已經(jīng)加載了頁面诵棵,勢(shì)必會(huì)造成一些資源浪費(fèi)抠蚣,所以我根據(jù)setUserVisibleHint(boolean isVisibleToUser)
和標(biāo)志位的判斷,來確定在頁面加載且用戶滑到當(dāng)前頁的時(shí)候才去加載數(shù)據(jù)的履澳。這點(diǎn)的想法起源來自簡書的APP嘶窄,大家看下圖就能明白:
很明顯,簡書也運(yùn)動(dòng)了Fragment的懶加載距贷,這里可能有朋友會(huì)說關(guān)于ViewPager里的setOffscreenPageLimit(int pgaeSize)
方法柄冲,這個(gè)方法的作用是控制ViewPager保留當(dāng)前頁面的左右各幾個(gè)頁面的數(shù)據(jù)狀態(tài),而不讓它進(jìn)行滑動(dòng)銷毀忠蝗,也可以決定ViewPager一次性加載幾個(gè)頁卡现横,所以把它設(shè)置為0不就解決了,其實(shí)不是的,如果你用去看過ViewPager的源碼戒祠,你就會(huì)發(fā)現(xiàn)這樣的一段代碼:
/**
* Set the number of pages that should be retained to either side of the
* current page in the view hierarchy in an idle state. Pages beyond this
* limit will be recreated from the adapter when needed.
*
* <p>This is offered as an optimization. If you know in advance the number
* of pages you will need to support or have lazy-loading mechanisms in place
* on your pages, tweaking this setting can have benefits in perceived smoothness
* of paging animations and interaction. If you have a small number of pages (3-4)
* that you can keep active all at once, less time will be spent in layout for
* newly created view subtrees as the user pages back and forth.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
而這里的
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
所以不管你是設(shè)置0骇两,或者更小的數(shù),默認(rèn)它都是為1的姜盈,也就是默認(rèn)加載3個(gè)頁卡低千,保留當(dāng)前頁卡的左右頁卡狀態(tài)。
Presenter層
/**
* 基類Presenter接口
* Create by: chenwei.li
* Date: 2016-05-31
* time: 14:25
* Email: lichenwei.me@foxmail.com
*/
public interface IBasePresenter<V extends IBaseView> {
/**
* V層注入引用
* @param mvpView
*/
void attachView(V mvpView);
/**
* 銷毀V層引用
*/
void detachView();
}
這里封裝了2個(gè)方法馏颂,用來使View層的Activity和Fragment和P層的生命狀態(tài)保持一致示血,當(dāng)頁面銷毀的時(shí)候,這次Context上下文的引用也就沒有理由存在了救拉,這里我們把它置為null难审,便于GC回收。
/**
* 基類BasePresenter
* Create by: chenwei.li
* Date: 2016-05-31
* time: 11:14
* Email: lichenwei.me@foxmail.com
*/
public class BasePresenter<T extends IBaseView> implements IBasePresenter<T> {
private T mMvpView;
@Override
public void attachView(T mvpView) {
this.mMvpView = mvpView;
}
@Override
public void detachView() {
this.mMvpView = null;
}
public T getMvpView() {
return mMvpView;
}
}
基類Presenter近上,讓所有的P層繼承剔宪。
具體代碼
View層
/**
* View層妹紙福利
* Create by: chenwei.li
* Date: 2016-05-26
* time: 22:06
* Email: lichenwei.me@foxmail.com
*/
public interface IWealView extends IBaseView {
void getWealList(int pageSize, int currentPage);
void onRefreshData(List<WealEntity.ResultsBean> resultsBean);
void onLoadMoreData(List<WealEntity.ResultsBean> resultsBean);
}
這里提供了操作接口,因?yàn)樵诒卷撁娴臉I(yè)務(wù)只是單純的加載列表數(shù)據(jù)壹无,刷新數(shù)據(jù)葱绒,所以這里定義了3個(gè)方法,用來滿足業(yè)務(wù)斗锭。
/**
* 福利Fragment
* Create by: chenwei.li
* Date: 2016-05-25
* time: 11:19
* Email: lichenwei.me@foxmail.com
*/
public class WealFragment extends BaseLazyFragment implements IWealView {
@Bind(R.id.rv_wealView)
XRecyclerView mRvWealView;
//福利適配器與數(shù)據(jù)源
private WealAdapter mWealAdapter;
private List<WealEntity.ResultsBean> mResultsBeans;
//福利P層
private WealPresenter mWealPresenter;
//記錄當(dāng)前頁數(shù)地淀,默認(rèn)為1
private int mCurrentPage = 1;
/**
* 懶加載數(shù)據(jù)
*/
@Override
protected void lazyLoadData() {
getWealList(10, mCurrentPage);
}
@Override
public int bindLayout() {
return R.layout.fragment_weal;
}
@Override
public void createFragment(Bundle savedInstanceState) {
}
@Override
public void initView() {
//注入P層
mWealPresenter = new WealPresenter();
mWealPresenter.attachView(this);
//創(chuàng)建數(shù)據(jù)源,設(shè)置適配器
mResultsBeans = new ArrayList<WealEntity.ResultsBean>();
mWealAdapter = new WealAdapter(mResultsBeans);
mRvWealView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
mRvWealView.setAdapter(mWealAdapter);
//設(shè)置動(dòng)畫岖是,刷新樣式
mRvWealView.setItemAnimator(new DefaultItemAnimator());
mRvWealView.setArrowImageView(R.drawable.iconfont_downgrey);
//監(jiān)聽刷新數(shù)據(jù)
mRvWealView.setLoadingListener(new XRecyclerView.LoadingListener() {
@Override
public void onRefresh() {
mCurrentPage = 1;
getWealList(10, mCurrentPage);
}
@Override
public void onLoadMore() {
getWealList(10, mCurrentPage);
}
});
}
@Override
public void showLoadingDialog() {
}
@Override
public void cancelLoadingDialog() {
}
/**
* 顯示錯(cuò)誤信息
*
* @param errorMsg
*/
@Override
public void showErrorMsg(String errorMsg) {
ToastUtil.showShort(MyApplication.getContext(), errorMsg);
}
/**
* 獲取數(shù)據(jù)
*
* @param pageSize
* @param currentPage
*/
@Override
public void getWealList(int pageSize, int currentPage) {
mWealPresenter.getWealList(pageSize, currentPage);
mCurrentPage++;
}
/**
* 下拉刷新數(shù)據(jù)回調(diào)
* @param resultsBean
*/
@Override
public void onRefreshData(List<WealEntity.ResultsBean> resultsBean) {
mResultsBeans.clear();
mResultsBeans.addAll(resultsBean);
mWealAdapter.notifyDataSetChanged();
mRvWealView.refreshComplete();
}
/**
* 上拉加載數(shù)據(jù)回調(diào)
* @param resultsBean
*/
@Override
public void onLoadMoreData(List<WealEntity.ResultsBean> resultsBean) {
mResultsBeans.addAll(resultsBean);
mWealAdapter.notifyDataSetChanged();
mRvWealView.loadMoreComplete();
if (resultsBean.size() < 10) {
//當(dāng)加載新數(shù)據(jù)的條數(shù)不足10條帮毁,則關(guān)閉上拉加載
mRvWealView.setLoadingMoreEnabled(false);
}
}
@Override
public void onDestroy() {
super.onDestroy();
mWealPresenter.detachView();
}
}
這里采用了懶加載LazyFragment,當(dāng)頁面加載完畢且用戶當(dāng)前界面處于它的時(shí)候豺撑,會(huì)調(diào)用執(zhí)行l(wèi)azyData加載數(shù)據(jù)烈疚,當(dāng)下拉刷新,上拉加載的時(shí)候會(huì)對(duì)應(yīng)的執(zhí)行方法聪轿,且改變當(dāng)前頁碼爷肝。這里在頁面初始化加載和銷毀的時(shí)候,分別去調(diào)用了P層的注入和銷毀方法陆错,用來保存生命狀態(tài)一致灯抛。
Presenter層
/**
* 獲取福利Presenter層
* Create by: chenwei.li
* Date: 2016-05-26
* time: 22:20
* Email: lichenwei.me@foxmail.com
*/
public class WealPresenter extends BasePresenter<IWealView> {
private IWealModel iWealModel = new WealModel();
@Override
public void attachView(IWealView mvpView) {
super.attachView(mvpView);
}
@Override
public void detachView() {
super.detachView();
}
/**
* 根據(jù)加載數(shù)量和頁碼加載數(shù)據(jù)
*
* @param pageSize
* @param currentPage
*/
public void getWealList(int pageSize, final int currentPage) {
iWealModel.getWealList(pageSize, currentPage, new Subscriber<WealEntity>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
getMvpView().showErrorMsg(e.getMessage());
}
@Override
public void onNext(WealEntity wealEntity) {
if (wealEntity.getResults() != null) {
if (currentPage == 1) {
getMvpView().onRefreshData(wealEntity.getResults());
} else {
getMvpView().onLoadMoreData(wealEntity.getResults());
}
}
}
});
}
}
這里沒什么好說的,只是作為“信使”音瓷,傳遞了View層的請(qǐng)求給Model層对嚼,并通過Rxjava的訂閱回調(diào)對(duì)應(yīng)的方法,再次傳遞數(shù)據(jù)給View層绳慎。
Model層
/**
* 網(wǎng)絡(luò)接口包裝類
* Create by: chenwei.li
* Date: 2016-05-23
* time: 00:43
* Email: lichenwei.me@foxmail.com
*/
public class RetrofitWapper {
private static RetrofitWapper mRetrofitWapper;
private Retrofit mRetrofit;
/**
* 將構(gòu)造函數(shù)私有化
*/
private RetrofitWapper() {
mRetrofit = new Retrofit.Builder().baseUrl(Constant.BASE_URL).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
}
/**
* 獲取RetrofitWapper實(shí)例(單例模式)
*
* @return
*/
public static RetrofitWapper getRetrofitWapperInstance() {
if (mRetrofitWapper == null) {
synchronized (RetrofitWapper.class) {
mRetrofitWapper = new RetrofitWapper();
}
}
return mRetrofitWapper;
}
/**
* 創(chuàng)建接口訪問入口
* @param service
* @param <T>
* @return
*/
public <T> T create(Class<T> service) {
return mRetrofit.create(service);
}
public class Constant {
public static final String BASE_URL = "http://gank.io/api/";
}
}
這里寫了一個(gè)幫助類纵竖,用來提供Retrofit的實(shí)例(單例)漠烧,由于Retrofit底層默認(rèn)是提供Okhttp支持的,所以如果這里要對(duì)Okhttp進(jìn)行個(gè)性化定制也是很容易的磨确,例如:
private RetrofitWapper() {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
okHttpClient.connectTimeout(5, TimeUnit.SECONDS);
mRetrofit = new Retrofit.Builder().client(okHttpClient.build()).baseUrl(Constant.BASE_URL).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
}
再來看下Retrofit的service:
/**
* Gank.io接口類
* Create by: chenwei.li
* Date: 2016-05-26
* time: 16:11
* Email: lichenwei.me@foxmail.com
*/
public interface GankApiService {
/**
* 獲取福利列表
*
* @param pageSize
* @param currentPage
* @return
*/
@GET("data/福利/{pageSize}/{currentPage}")
Observable<WealEntity> getWealList(@Path("pageSize") int pageSize, @Path("currentPage") int currentPage);
}
Model層的業(yè)務(wù)接口及實(shí)現(xiàn):
/**
* 獲取福利Model層接口
* Create by: chenwei.li
* Date: 2016-05-26
* time: 22:31
* Email: lichenwei.me@foxmail.com
*/
public interface IWealModel {
void getWealList(int pageSize,int currentSize, Subscriber<WealEntity> wealEntitySubscriber);
}
/**
* Create by: chenwei.li
* Date: 2016-05-26
* time: 22:28
* Email: lichenwei.me@foxmail.com
*/
public class WealModel implements IWealModel {
private GankApiService mGankApiService;
/**
* 獲取福利列表數(shù)據(jù)
*
* @param pageSize
* @param currentSize
* @param wealEntitySubscriber
*/
@Override
public void getWealList(int pageSize, int currentSize, Subscriber<WealEntity> wealEntitySubscriber) {
mGankApiService = RetrofitWapper.getRetrofitWapperInstance().create(GankApiService.class);
mGankApiService.getWealList(pageSize, currentSize).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(wealEntitySubscriber);
}
}
這里的代碼很簡單沽甥,就不再做過多的文字描述了,對(duì)于Rxjava和Retrofit不熟悉的朋友可以網(wǎng)上查看下相關(guān)資料乏奥。
最后
關(guān)于軟件開發(fā)設(shè)計(jì)模式摆舟,其實(shí)并沒有最好的選擇,只有更適合的選擇邓了,很多時(shí)候我們拋開業(yè)務(wù)去談設(shè)計(jì)恨诱,架構(gòu)都是飄渺的,我認(rèn)為開發(fā)還是需要靈活點(diǎn)骗炉,不應(yīng)該被某模式某架構(gòu)所束縛照宝,這樣變得好像是為了目的而目的,已經(jīng)脫離了設(shè)計(jì)模式最初的設(shè)計(jì)意義句葵。
比如這個(gè)MVP厕鹃,對(duì)于業(yè)務(wù)很簡單的小型APP來說,有時(shí)候明明只需要幾個(gè)類就可以解決的事情乍丈,非要使用MVP剂碴,會(huì)使其多了一大堆接口,導(dǎo)致開發(fā)成本變大轻专,但也不是全然無用忆矛,至少它的代碼層次清晰了。