最近看了兩篇關于Android實現MVP的文章[一種在android中實現MVP模式的新思路](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/androidweekly/%E4%B8%80%E7%A7%8D%E5%9C%A8android%E4%B8%AD%E5%AE%9E%E7%8E%B0MVP%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%96%B0%E6%80%9D%E8%B7%AF)和[用MVP架構開發Android應用](http://kymjs.com/code/2015/11/09/01/)。
兩篇文章的思路都是一樣的,即把Activity、Fragment作為Presenter,這種方式不同于現在主流的MVP方式,不過它很好的解決了Activity生命周期帶來的問題,而且我認為它讓MVP的實現更加輕松了。
那么問題來了,這么好的思路,我們怎么可以不去實現一下自己的MVP呢? 于是我花了一晚上的時間整出了一個小小的MVP框架——MVPro
### MVPro介紹
MVPro的實現很簡單,思想和上面兩篇文章介紹的一樣,都是將Activity和Fragment作為Presenter,首先一張圖來解釋一下MVPro的原理,
Created with Rapha?l 2.1.0PresenterPresenterViewViewonCreatecreatebindPresentercreatecreatedsetContentViewbindEventcreated
Presenter即我們的Activity或者Fragment, View呢?說白了就是我們從Activity和Fragment中提取出來的和View操作相關的代碼,思路很簡單也很清晰,下面我們就以一個簡單的demo詳細學習一下MVPro的使用吧。
### MVPro使用
因為MVPro是將Activity視為Presenter,所以我們代碼的主線應該是Presenter了,首先上一個Presenter的代碼,
~~~
public class MainActivity extends ActivityPresenterImpl<DataListView>
implements AdapterView.OnItemClickListener, View.OnClickListener {
@Override
public void create(Bundle savedInstance) {
}
@Override
public void created(Bundle savedInstance) {
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mView.toast(position);
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.newdata) newData();
else getData();
}
private void newData() {
new MainBiz().data(new MainBiz.Callback<ArrayList<String>>() {
@Override
public void callback(ArrayList<String> data) {
mView.setData(data);
}
});
}
private void getData() {
new MainBiz().data(new MainBiz.Callback<ArrayList<String>>() {
@Override
public void callback(ArrayList<String> data) {
mView.addData(data);
}
});
}
}
~~~
MainActivity繼承自ActivityPresenterImpl類,而且在代碼中并沒有任何和Activity生命周期相關的代碼,兩個方法create和created是我們唯一關心的重點,但是也是非必須重寫的,這兩個方法都是Presenter提供的方法,他們兩個的區別就是create在setContentView之前調用,而created是在setContentView之后調用。
剩下的幾個方法我們可以猜測到都是從View層發起的,那么接下來我們就來看看View層該如何去寫。從MainActivity實現的泛型中我們可以看出,這里我們對應的View層代碼在DataListView中。
~~~
/**
* Created by qibin on 2015/11/15.
*/
public class DataListView extends ViewImpl {
private Button mButton1, mButton2;
private ListView mListView;
private ArrayAdapter<String> mAdapter;
@Override
public void created() {
mButton1 = findViewById(R.id.newdata);
mButton2 = findViewById(R.id.adddata);
mListView = findViewById(R.id.list);
}
@Override
public void bindEvent() {
EventHelper.click(mPresenter, mButton1, mButton2);
EventHelper.itemClick(mPresenter, mListView);
}
@Override
public int getLayoutId() {
return R.layout.list_layout;
}
public void setData(ArrayList<String> datas) {
mAdapter = new ArrayAdapter<String>(mRootView.getContext(),
android.R.layout.simple_list_item_1, datas);
mListView.setAdapter(mAdapter);
}
public void addData(ArrayList<String> datas) {
mAdapter.addAll(datas);
}
public void toast(int position) {
Toast.makeText(mRootView.getContext(),
mAdapter.getItem(position), Toast.LENGTH_SHORT).show();
}
}
~~~
在View層中,我們并不關心布局文件是怎么設置到Activity上的,我們僅僅在getLayoutId中返回我們要使用的布局文件,和created中去獲取我們需要的控件即可,
不過我們還重寫一個bindEvent方法,在這個方法中我們為控件綁定了事件,這里需要注意一點就是MVPro希望各種事件都在Presenter上implements,因為EventHelper
提供了基于Presenter的事件監聽。
ok, MVPro的使用就這么簡單,不過相信很多人還是摸不著頭腦,所以下面我們將會深入到源碼中,來了解一下MVPro的實現流程。
### MVPro源碼
首先我們還是要從Presenter入手,Presenter的源頭是一個接口,這里面定義了Presenter的必須需要的幾個方法,
~~~
/**
* Presenter的根接口<br />
* Created by qibin on 2015/11/15.
*/
public interface IPresenter<T> {
/**
* 獲取當前presenter泛型的類型
* @return
*/
Class<T> getViewClass();
/**
* View初始化之前可以在此方法做一些操作
*/
void create(Bundle savedInstance);
/**
* View初始化完畢后調用
*/
void created(Bundle savedInstance);
}
~~~
只有三個方法,其中getViewClass是獲取我們對應的View層那個類的class, 例如上面的demo中對應的就是DataListView,create和created是兩個擴展方法分別在onCreate的setContentView之前和之后調用。接下來,我們就來看看我們上面MainActivity繼承的那個ActivityPresenterImpl的代碼,
~~~
/**
* 將Activity作為Presenter的基類 <br />
* Created by qibin on 2015/11/15.
*/
public class ActivityPresenterImpl<T extends IView> extends Activity implements IPresenter<T> {
protected T mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
create(savedInstanceState);
try {
mView = getViewClass().newInstance();
mView.bindPresenter(this);
setContentView(mView.create(getLayoutInflater(), null));
mView.bindEvent();
created(savedInstanceState);
} catch(Exception e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public Class<T> getViewClass() {
return GenericHelper.getViewClass(getClass());
}
@Override
public void create(Bundle savedInstance) {
}
@Override
public void created(Bundle savedInstance) {
}
}
~~~
我估計很多人在沒看到ActivityPresenterImpl源碼之前都會認為它應該很復雜,不過在看后大概都會忍不住吐槽一句:尼瑪,這么少! 對代碼確實少,而且基本都集中在了onCreate中,不過,在看onCreate之前,我們首先來看看getViewClass方法, 這個方法實現自IPresenter,而且僅僅有一行代碼,它的作用就是獲取當前Presenter泛型指定那個View層類,對應上面的demo中就是DataListView了。
接下來回到onCreate中,在onCreate中,我們首先調用了create方法,以便于我們執行一些在setContentView之前的代碼,例如設置無標題啥的。
然后通過getViewClass獲取到了View層的類,并且利用反射得到他的對象mView,接下的幾步都和這個mView相關。
調用mView.bindPresenter方法,將當前Presenter關聯到當前View層上。
調用mView.create方法生成我們布局文件對應的View對象,并且通過setContentView設置布局文件。
調用mView.bindEvent方法,在這個方法中我們可以對一些控件設置事件監聽。
最后我們調用了created方法,以便在開發中書寫初始化控件完畢后的代碼。
ok, Presenter的代碼很簡單,主要是一些流程性的東西,解析來我們來看看View層的代碼是怎么實現的。還是從接口——IView開始,
~~~
/**
* View層的根接口 <br />
* Created by qibin on 2015/11/15.
*/
public interface IView {
/**
* 根據 {@link getLayoutId}方法生成生成setContentView需要的根布局
* @param inflater
* @param container
* @return
*/
View create(LayoutInflater inflater, ViewGroup container);
/**
* 當Activity的onCreate完畢后調用
*/
void created();
/**
* 返回當前視圖需要的layout的id
* @return
*/
int getLayoutId();
/**
* 根據id獲取view
* @param id
* @param <V>
* @return
*/
<V extends View> V findViewById(int id);
/**
* 綁定Presenter
* @param presenter
*/
void bindPresenter(IPresenter presenter);
/**
* {@link created}后調用,可以調用{@link org.loader.helper.EventHelper.click}
* 等方法為控件設置點擊事件,一般推薦使用{@link org.loader.helper.EventHelper.click(IPresenter presenter, View ...views)}
* 方法并且讓你的Presenter實現相應接口。
*/
void bindEvent();
}
~~~
IView接口中定義的方法就相對多了一些,我們一個個的來說一下這些方法存在的理由。
create方法根據提供的layout的id來生成布局對象,上面Presenter的setContentView的參數就是他的返回值。
created會在inflater布局完成后調用,以便我們一些操作。
getLayoutId需要我們去實現,返回需要的布局文件的id。
findViewById提供了一個泛型的查找控件方法,有了它我們再也不用類型轉換了。
bindPresenter綁定對應的Presenter。
bindEvent我們需要實現這個方法來為控件設置監聽。
ok, 在介紹完這些方法后,我們就來看看我們View層的基類都是做了什么工作。
~~~
/**
* View層的基類
* Created by qibin on 2015/11/15.
*/
public abstract class ViewImpl implements IView {
/**
* create方法生成的view
*/
protected View mRootView;
/**
* 綁定的presenter
*/
protected IPresenter mPresenter;
@Override
public View create(LayoutInflater inflater, ViewGroup container) {
mRootView = inflater.inflate(getLayoutId(), container, false);
created();
return mRootView;
}
@Override
public <V extends View> V findViewById(int id) {
return (V) mRootView.findViewById(id);
}
@Override
public void created() {
}
@Override
public void bindPresenter(IPresenter presenter) {
mPresenter = presenter;
}
@Override
public void bindEvent() {
}
}
~~~
在create方法中,我們使用LayoutInflater生成了View對象,并且返回給Presenter用來setContentView,在返回之前我們還調用了created方法,在項目中我們可以在這個方法中查找我們需要的控件。
created和bindEvent方法在這里都是空實現,這樣做的目的就是在我們自己的代碼中不需要任何時候都要實現這兩個方法。
ok, 很簡單,MVPro的代碼大體就這些,整個流程我們也分析完了,那Model層呢?還沒有看到關于Model層的代碼呢!在MVPro中并不關心你業務層是怎么實現,你完全可以使用你現在正在使用的業務層代碼的架構,而不會對MVP產生任何影響。
最后,我們還是要來看看EventHelper是怎么設置事件監聽的。
~~~
public class EventHelper {
public static void click(IPresenter li, View ...views) {
if(!(li instanceof View.OnClickListener)) return;
click((View.OnClickListener) li, views);
}
public static void click(View.OnClickListener li, View ...views) {
if(views == null || views.length == 0) return;
for(View v : views) v.setOnClickListener(li);
}
...
}
~~~
這里拿click事件為例,可以看到我們的參數是一個IPresenter類型, 而且是判斷了你傳遞的這個Presenter是不是實現了View.OnClickListener接口,如果實現了,就進行強制類型轉換使用,從這里我們也可以看到,為了盡量減少我們的Presenter和View層的代碼耦合,MVPro并沒有使用泛型,也不建議從Presenter傳遞OnClickListener對象,而是建議我們的Presenter直接實現OnClickListener接口,這樣做,EventHelper會自動幫我們完成類型的檢查和監聽的設置。
恩,到這里,我們還有一個helper沒有看實現,就是那個獲取泛型類型的幫助類。
~~~
/**
* Created by qibin on 2015/11/15.
*/
public class GenericHelper {
public static <T> Class<T> getViewClass(Class<?> klass) {
Type type = klass.getGenericSuperclass();
if(type == null || !(type instanceof ParameterizedType)) return null;
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] types = parameterizedType.getActualTypeArguments();
if(types == null || types.length == 0) return null;
return (Class<T>) types[0];
}
}
~~~
這個類中僅僅一個方法,不過可能大部分人對這里面的代碼非常陌生,這里做的事很直接,就是根據我們提供的class,來獲取這個類實現的那個泛型的類型,打個比方,下面的代碼獲取到的就是java.lang.String,
~~~
class Child extends Super<String> {}
~~~
ok, 文章到這里就要結束了,MVPro的總體實現還是非常簡單的,如果大家有什么疑問或者感覺MVPro需要什么改進的地方,大家可以在下面為我留言,我會不斷去完善這個小框架的。
最后是MVPro的下載地址,這里提供了MVPro的源碼(AS版)、jar包、實例代碼,[https://github.com/qibin0506/MVPro](https://github.com/qibin0506/MVPro)