MVP開發(fā)框架的一次嘗試——EasyMVP

前言

用MVP模式開發(fā)安卓相信大家也應(yīng)該不是很陌生了徽惋。各種各樣的mvp開發(fā)模式嘱能,框架也是層出不窮。之前寫過一篇《MVP的一種新的嘗試》 赫段,然后我基于這種模式(activity作為presenters)做了一個(gè)可能問題還是很多的開發(fā)框架:項(xiàng)目地址 乌询。并且根據(jù)這個(gè)框架在前幾天結(jié)合rxjava,retrofit,dagger做了一個(gè)挺簡單的demo app:githubQuery 榜贴。我在想做這套框架之后意外的發(fā)現(xiàn)一個(gè)大牛做的框架和我是一樣的思想!都是借鑒了這篇文章:《借鑒文章》 妹田。做的也是大同小異了竣灌,順便也借鑒了不少,還借鑒了另一個(gè)學(xué)長把a(bǔ)dapter和viewholder解耦的思想秆麸。

傳統(tǒng)mvp模式的不足

  1. 如果把a(bǔ)ctivity作為view初嘹,那么如果activity異常銷毀,那么恢復(fù)數(shù)據(jù)就是個(gè)問題了沮趣。并且還必須要把presenter和view生命周期同步屯烦。如果使用bundle,這是就直接破壞了mvp的設(shè)計(jì)模式房铭,因?yàn)檫@樣難免會(huì)把v和m耦合在一起驻龟。而將activity作為view,可以很方便的使用bundle和model層的數(shù)據(jù)進(jìn)行異掣追耍恢復(fù)翁狐。

  2. context以及各種安卓系統(tǒng)服務(wù),使用intent等等凌蔬。

  3. 開始想用dagger進(jìn)一步把v-p-m解耦露懒,后來發(fā)現(xiàn)不用了闯冷,,

    具體不足請(qǐng)看前言中的借鑒文章懈词!

    EasyMVP

    介紹

    1.使用泛型來使view和presenter解耦蛇耀,通過編譯期間從子activity傳遞過來的view類型反射獲得具體view類型。并且框架完成activity的視圖渲染坎弯,并且提供同步activity生命周期的函數(shù)供開發(fā)者使用纺涤。

    2.默認(rèn)使用butterknife作為view的依賴注入。

    3.adapter作為presenter抠忘,并且與viewholder完全解耦撩炊,viewholder歸入view層。默認(rèn)使用recycle view崎脉。(別再提listview了)

EasyMVP.png

BaseActivityPresenters

public abstract class BaseActivityPresenter<V extends IView> extends AppCompatActivity{

    protected V v;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
          //通過注解獲得對(duì)應(yīng)的view的實(shí)例
            v = getRootViewClass().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
      //調(diào)用view層中生成根視圖的函數(shù)
        v.creatView(getLayoutInflater(), null);
      //調(diào)用view層初始化控件的函數(shù)衰抑,這里默認(rèn)使用butterkinfe了。
        v.initView();
      //視圖渲染
        setContentView(v.getRootView());

      //拋出跟onCreat()函數(shù)同步的函數(shù)給開發(fā)者
        inCreat(savedInstanceState);
    }
    //接觸綁定
    @Override
    protected void onDestroy() {
        super.onDestroy();
        v.removeView();
        v = null;
        inDestory();
    }

    public abstract Class<V> getRootViewClass();
    public abstract void inCreat(Bundle savedInstanceState);
    public abstract void inDestory();
    public void inPause(){}
    public void inRestart(){}
    public void inStop(){}
    public void inResume(){}
    public void inStart(){}

    @Override
    protected void onStart() {
        super.onStart();
        inStart();
    }
我已經(jīng)在代碼里面添加了一些注釋荧嵌。**框架已經(jīng)默認(rèn)了使用butterkinfe**呛踊,要避免重復(fù)依賴。再來看view層的框架代碼啦撮。這樣做其實(shí)是可以看成是在抽出一個(gè)BaseActivity谭网,把一些操作黑箱化。

IView

public interface IView{

    void creatView(LayoutInflater inflater, ViewGroup parent);

    View getRootView();

    int getRootViewId();

    void initView();

  //調(diào)用butterkinfe的unbind()方法
    void removeView();
}
確定了幾個(gè)view代理層最基本的操作赃春。

BaseViewImpl

/**
 * Created by Zane on 15/12/18.
 * 將view加載的過程寫在抽象類愉择,做到代碼復(fù)用。
 */
public abstract class BaseViewImpl implements IView{

    protected View view;
    protected final SparseArray<View> mViews = new SparseArray<View>();

 //根據(jù)activity傳遞過來的參數(shù)和具體view類傳遞過來的id生成根視圖织中。
    @Override
    final public void creatView(LayoutInflater inflater, ViewGroup parent) {
        int resourceId = getRootViewId();

        if (resourceId == 0){
            throw new RuntimeException("rootview's id can't be null");
        }

        view = inflater.inflate(resourceId, parent, false);
    }

 //這就是baseactivitypresenter中調(diào)用用來渲染試圖的方法锥涕。
    @Override
    final public View getRootView() {
        return view;
    }
    //由子類去重寫
    @Override
    public abstract int getRootViewId();

    //添加注解view方式
    @Override
    final public void initView() {
        ButterKnife.bind(this, view);
    }

    @Override
    final public void removeView() {
        ButterKnife.unbind(this);
    }

BaseListViewHolder

這個(gè)是可以和baseviewimpl對(duì)應(yīng)的,是一個(gè)viewholder的抽象父類狭吼。用的同樣的方法通過中間層做到view holder和adapter解耦层坠。區(qū)別不是太大!

EasyMVP2.png
/**
 * Created by Zane on 15/12/18.
 * 這個(gè)中間的base層用來做到viewholder與adapter的解耦刁笙。
 */
public abstract class BaseListViewHolderImpl<M extends Object> extends RecyclerView.ViewHolder{

    public BaseListViewHolderImpl(View itemView) {
        super(itemView);
    }

    //生成viewholder的構(gòu)造方法破花。
    public BaseListViewHolderImpl(ViewGroup parent, @LayoutRes int res){
        super(LayoutInflater.from(parent.getContext()).inflate(res, parent, false));
    }

    public abstract void initView();

    public abstract void setData(M data);

    protected <T extends View> T $(@IdRes int id) {
        return (T) itemView.findViewById(id);
    }

}
我繼承了recycle view的view holder,并且暴露出來一個(gè)構(gòu)造函數(shù)疲吸,相當(dāng)于代理層的構(gòu)造函數(shù)座每,然后函數(shù)里面調(diào)用了recycle view中viewholder的構(gòu)造方法,以此來生成最終的view holder摘悴。

而M則是recycle view展示的model的數(shù)據(jù)類型峭梳。以此保證view holder和adapter數(shù)據(jù)類型的一致。方便下面的操作蹂喻。

setData()方法的設(shè)計(jì)有點(diǎn)像接口回調(diào)葱椭,在holder的子類里面實(shí)現(xiàn)這個(gè)函數(shù)捂寿,在adapter里面進(jìn)行回調(diào)。

BaseListAdapterPresenter

我的這個(gè)框架的啟蒙文章里面也說挫以,適配器是可以作為presenter的者蠕。這個(gè)抽象類的主要任務(wù)就是調(diào)用具體holder子類的構(gòu)造函數(shù)窃祝,并且傳遞數(shù)據(jù)給setData()方法掐松,形成接口回調(diào)。
/**
 * Created by Zane on 15/12/18.
 */
public abstract class BaseListAdapterPresenter<M extends Object> extends RecyclerView.Adapter<BaseListViewHolderImpl>{

    protected Context mContext;
    protected List<M> mDatas;

    public BaseListAdapterPresenter(Context mContext){
        this(mContext, null);
    }

  //根據(jù)具體adapter子類構(gòu)造函數(shù)獲得的數(shù)據(jù)去初始化這里的數(shù)據(jù)粪小。
    public BaseListAdapterPresenter(Context mContext, List<M> mDatas){
        this.mContext = mContext;
        this.mDatas = mDatas;
    }

    @Override
    public BaseListViewHolderImpl onCreateViewHolder(ViewGroup parent, int viewType) {

        return OnCreatViewHolder(parent, viewType);

    }

  //給具體adapter子類去實(shí)現(xiàn)大磺,在子類里面調(diào)用具體的holder構(gòu)造函數(shù)
    public abstract BaseListViewHolderImpl OnCreatViewHolder(ViewGroup parent, int viewType);


    public M getItem(int position){
        return mDatas.get(position);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }
}

問題

剛開始這樣寫完了之后探膊,發(fā)現(xiàn)一個(gè)問題就是presenter里面難免會(huì)需要view層的控件杠愧。比如點(diǎn)擊一個(gè)button實(shí)現(xiàn)頁面跳轉(zhuǎn),首先跳轉(zhuǎn)功能當(dāng)然要寫在presenter逞壁,這就是跟傳統(tǒng)mvp不同的一點(diǎn)。然后我就在想如果把view的控件一個(gè)個(gè)的暴露給presenter腌闯,但是如果需求量一大后果不堪設(shè)想绳瘟。。然后我就看到那個(gè)大牛的框架用一個(gè)方法解決了這個(gè)問題:

    protected final SparseArray<View> mViews = new SparseArray<View>();

 final public <T extends View> T bindView(int id) {
         T view2 = (T) mViews.get(id);
        if (view2 == null) {
            view2 = $(id);
            mViews.put(id, view2);
        }
        return view2;
    }

    final public <T extends View> T get(int id) {
        return (T) bindView(id);
    }

    final protected <T extends View> T $(@IdRes int id) {
        return (T) view.findViewById(id);
    }

 //暴露監(jiān)聽函數(shù)
    final public void setOnClickListener(View.OnClickListener listener, int... ids) {
        if (ids == null) {
            return;
        }
        for (int id : ids) {
            get(id).setOnClickListener(listener);
        }
    }
他用了一個(gè)可變長度的數(shù)組來存儲(chǔ)控件姿骏。首先你可以用get(int id)方法或者$(int id)方法(建議就用get()糖声,因?yàn)椴槐刂貜?fù)去findview)去在presenter中獲得控件,但是不到萬不得已別這樣做分瘦,不然就破壞了mvp的設(shè)計(jì)模式蘸泻。再就是很佩服他的setOnClickListener(View.OnClickListener listener, int... ids)方法。完美的解決了在presenter不獲得控件還能完成監(jiān)聽這個(gè)問題嘲玫。

使用

簡單demo:

/**
 * Created by Zane on 16/1/27.
 */
public class MainView2 extends BaseViewImpl {

    @Bind(R.id.button)
    Button button;
    @Bind(R.id.edit)
    EditText edit;

    @Override
    public int getRootViewId() {
        return R.layout.activity_2;
    }

    public void setText(String test){
        edit.setText(test);
    }
}

這是一個(gè)view悦施,看到了,由于框架做了butterkinfe綁定去团,所以直接@Bind()歼争!然后返回自己對(duì)應(yīng)的xml文件id。

/**
 * Created by Zane on 16/1/27.
 */
public class MainActivity2 extends BaseActivityPresenter<MainView2>{

    @Override
    public Class<MainView2> getRootViewClass() {
        return MainView2.class;
    }

    @Override
    public void inCreat(Bundle bundle) {

        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity2.this, MainActivity.class));
            }
        }, R.id.button);
    }

    @Override
    public void inDestory() {

    }
}

返回對(duì)應(yīng)view的class渗勘,并且完成了一次點(diǎn)擊button跳轉(zhuǎn)頁面沐绒。

/**
 * Created by Zane on 15/12/20.
 */
public class MainListViewHolder extends BaseListViewHolderImpl<data> {

    TextView mTextView;

    public MainListViewHolder(ViewGroup parent) {
        super(parent, R.layout.listview_item_layout);
        initView();
    }

    @Override
    public void initView() {
        mTextView = $(R.id.item_text);
    }

    @Override
    public void setData(data data) {
        mTextView.setText(data.getDatas());
    }
}

viewholer中不能使用butterknife默認(rèn)綁定,這個(gè)問題我還沒想到辦法怎么解決旺坠。乔遮。不過直接用$()函數(shù)也是不錯(cuò)的∪∪校看到重寫了父類的抽象方法蹋肮,而這個(gè)函數(shù)在adapter里面被回調(diào)出刷。

/**
 * Created by Zane on 15/12/20.
 */
public class MyRecycleviewAdapter extends BaseListAdapterPresenter<data>{

    public MyRecycleviewAdapter(Context mContext, List<data> datas){
        super(mContext, datas);
    }

    @Override
    public BaseListViewHolderImpl OnCreatViewHolder(ViewGroup parent, int viewType) {
        return new MainListViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(BaseListViewHolderImpl holder, int position) {
        holder.setData(getItem(position));
    }
}

調(diào)用了父類的構(gòu)造函數(shù),返回了具體holder子類坯辩。大家會(huì)發(fā)現(xiàn)我并沒有把holder和adapter完全解耦馁龟。對(duì),我只是把item和adapter解耦了漆魔。使item的邏輯由自己管理坷檩,并且將holder分離出來并入view層。這塊是借鑒了學(xué)長一個(gè)庫的經(jīng)驗(yàn):項(xiàng)目地址:EasyRecycleView

總結(jié)

這是我第一次去嘗試寫一個(gè)框架出來改抡,并且借鑒了很多別人的思想。而且實(shí)踐的次數(shù)不多阿纤,算是自己邁出的第一步吧句灌,以后肯定會(huì)遇到越來越多的問題,但是很值得欠拾。突然發(fā)現(xiàn)我這么一個(gè)小小的框架都需要想很多問題资昧,那些牛逼框架能設(shè)計(jì)出來真是不容易。

未經(jīng)博主同意践惑,不得轉(zhuǎn)載該篇文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尔觉,一起剝皮案震驚了整個(gè)濱河市钟鸵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贡未,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岂昭,死亡現(xiàn)場離奇詭異,居然都是意外死亡棍苹,警方通過查閱死者的電腦和手機(jī)孽鸡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門豆胸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晚胡,“玉大人,你說我怎么就攤上這事骡尽◇锊龋” “怎么了谭贪?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵痘煤,是天一觀的道長姨俩。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么缔恳? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好理肺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布熏兄。 她就那樣靜靜地躺著摩桶,像睡著了一般芦拿。 火紅的嫁衣襯著肌膚如雪侠坎。 梳的紋絲不亂的頭發(fā)上庐完,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼誉简,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼烹吵!你這毒婦竟也來了呀酸?” 一聲冷哼從身側(cè)響起艾栋,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤先较,失蹤者是張志新(化名)和其女友劉穎闲勺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖重挑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谦屑,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布坦康,位于F島的核電站,受9級(jí)特大地震影響隧哮,放射性物質(zhì)發(fā)生泄漏曲秉。R本人自食惡果不足惜承二,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一蛹找、第九天 我趴在偏房一處隱蔽的房頂上張望姜挺。 院中可真熱鬧,春花似錦、人聲如沸礁凡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踊赠。三九已至,卻和暖如春缤灵,著一層夾襖步出監(jiān)牢的瞬間伦籍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國打工腮出, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帖鸦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓胚嘲,卻偏偏與公主長得像作儿,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馋劈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容