如何實現自己的Android MVP框架?


 

       相信熟悉android開發的童鞋對MVP框架應該都不陌生吧,網上很多關於android中實現MVP的文章,大家可以直接搜索學習。這些文章中,MVP的實現思路基本都是把Activity、Fragment作為Presenter,這種方式不同於現在主流的MVP方式,不過卻好的解決了Activity生命周期帶來的問題,且讓MVP的實現更加輕松了。

      小編覺得很多關於android MVP框架的實現的文章都寫的非常不錯,且今天在網上看到一位童鞋利用上面所說的思路實現自己的MVP框架 — MVPro,更是讓人眼前一亮。下面小編和大家分享分享這位牛叉叉的童鞋是怎么實現他的MVPro的,一起來圍觀吧。

 

MVPro的原理

       和以往大家實現MVP的思想類似,將Activity和Fragment作為Presenter,為了形象直觀,我們通過下面這張圖,來展示MVPro的原理:

 

MVPro的實現很簡單,思想和上面兩篇文章介紹的一樣,都是將Activity和Fragment作為Presenter,首先一張圖來解釋一下MVPro的原理,

 

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 www.maiziedu.com 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 www.maiziedu.com 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 www.maiziedu.com 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方法,以便在開發中書寫初始化控件完畢后的代碼。

 

Presenter的代碼很簡單,主要是一些流程性的東西,接下來我們來看看View層的代碼是怎么實現的。還是從接口——IView開始,

 

/**

 * View層的根接口 <br />

 * Created by www.maiziedu.com 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我們需要實現這個方法來為控件設置監聽。

 

 在介紹完這些方法后,我們就來看看我們View層的基類都是做了什么工作。

 

/**

 * View層的基類

 * Created by www.maiziedu.com 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方法在這里都是空實現,這樣做的目的就是在我們自己的代碼中不需要任何時候都要實現這兩個方法。

 

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 www.maiziedu.com 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> {}

 

以上就是利用Android MVP框架的實現思想,實現自己MVP框架的具體方法和源碼,總的來說,掌握了思路和方法后,實現起來還是不難的。如果大家在自己的MVP框架實現過程中,有更好的思路或方法,也歡迎補充分享。

 

相關文章:《Android React Native組件的生命周期及回調函數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM