MVP+Dagger2設計,MVP架構模式實現新思路 (Demo)

desaco發表於2016-03-06

> MVP+Dagger2

-- Dagger2 是Google 的新一代依賴注入框架。

Android MVP使用Dagger2的sample程式碼- https://github.com/ChineseLincoln/Dagger2Mvp
將MVP,Dagger,Retrofit,Rxjava等技術相結合並用於快速開發的框架-https://github.com/JessYanCoding/MVPArms
MVP初體驗與MVP引入Dagger2初體驗- https://blog.csdn.net/shoushow_yeping/article/details/71421627

Dagger2+MVP的簡單封裝- https://blog.csdn.net/Sean_css/article/details/79624156

-- Dagger2 與 MVP 的結合(解耦和擴充套件以及團隊協作),基本思路:
 1.全域性Component通過AppComponent進行管理,大多設定單例模式;
 2.將Activity和Fragment Component中通用的抽出,為BaseViewComponent;

 3.上層PresenterComponent繼承BaseViewComponet,DataBandingComponent只能繼承android.databinding.DataBindingComponent,但可以將BaseViewModule 包含進來。其他Component使用時可以繼承BaseViewComponent。

傳統MVP用在專案中是真的方便還是累贅?-https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548821&idx=1&sn=255a2c1552ddef3542c2648a29eea26e&chksm=f1180368c66f8a7e3c0d70902f07621dac48c28a1a8b30e3d90e0ae531c80b3325292306feca&scene=21#wechat_redirect
MVPArms- https://github.com/JessYanCoding/MVPArms/wiki
-- 總結一下MVP的缺點
1.粒度不好控制,控制不好就需要寫過多的類和介面
2.如要重用presenter可能會實現過多不需要的介面
3.Presenter和View通過介面通訊太繁瑣,一旦View層需要的資料變化,那麼對應的介面就需要
MVP需要建立太多的類和介面,並且每次通訊都需要繁瑣的通過介面傳遞資訊
支付寶團隊使用的T-MVP框架,是通過將Activity或Fragment作為Presenter,將UI操作抽到Delegate中,作為View層。

-- Dragger,控制反轉;inject,依賴注入。
依賴注入的框架dragger2 和ButterKnife,控制反轉(IOC:Inversion of Control))
依賴注入是實現控制反轉的方式之一(另一方式是依賴查詢),目的就是為了讓呼叫者和被呼叫者之間解耦。
ButterKnife:https://github.com/JakeWharton/butterknife
Dagger2 主頁: https://github.com/google/dagger

 控制反轉:物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。
 依賴注入:被動的接收物件,在類A的例項建立過程中即建立了依賴的B物件,通過型別或名稱來判斷將不同的物件注入到不同的屬性中。

  依賴注入有如下實現方式:
· 基於介面。實現特定介面以供外部容器注入所依賴型別的物件。
· 基於 set方法。實現特定屬性的public set方法,來讓外部容器呼叫傳入所依賴型別的物件。
· 基於建構函式。實現特定引數的建構函式,在新建物件時傳入所依賴型別的物件。
· 基於註解。基於Java的註解功能,在私有變數前加“@Autowired”等註解,不需要顯式的定義以上三種程式碼,便可以讓外部容器傳入對應的物件。該方案相當於定義了public的set方法,但是因為沒有真正的set方法,從而不會為了實現依賴注入導致暴露了不該暴露的介面(因為set方法只想讓容器訪問來注入而並不希望其他依賴此類的物件訪問)。

 依賴查詢:主動索取響應名稱的物件,獲得依賴物件的時間也可以在程式碼中自由控制。
依賴反轉原則:
 1.高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象介面。
 2.抽象介面不應該依賴於具體實現。而具體實現則應該依賴於抽象介面。

> MVP (介面回撥與解耦)

 -- MVP的實現方式很多,這裡介紹兩種:

1.以Activity和Fragment作為View(檢視層),Presenter管理業務邏輯;
2.使用Activity和Fragment作為presenters,View單獨抽出處理檢視相關。
 -- 使用Activity和Fragment作為Presenters為何要採用這種方式呢,基於兩點來考慮:
 Activity Fragment本身就有生命週期的管理,這種管理類似於業務邏輯,所以要歸為Presenter;
 Activity Fragment生命週期變化時,會帶來業務邏輯的變化,直接作為Presenter,可以避免業務邏輯的複雜。
  -- MVP Model 層主要的職責有:
1.從網路,資料庫,檔案,感測器,第三方等資料來源讀寫資料。
2.對外部的資料型別進行解析轉換為APP內部資料交由上層處理。
3.對資料的臨時儲存,管理,協調上層資料請求。
  View層,在 MVP 開發中,View 層通常指的是 Activity 、Fragment、View、ViewGroup 等。主要職責:
1.提供 UI 互動
2.在 Presenter 的控制下修改 UI。
3.將業務事件交由 Presenter 處理

  P層,Presenter 層主要是連線 View 層和 Model 層的橋樑,負責把 View 層需要資料從 Model 層拿到,返回給 View 層;

-- MVP 架構專案MinimalistWeather(GitHub),將網路庫升級到了Retrofit2+OKHttp3 https://github.com/BaronZ88https://github.com/BaronZ88/MinimalistWeather,開源天氣App,採用MVP+RxJava+Retrofit2+OKHttp3+Dagger2+RetroLambda等開源庫來實現.

MVP的主要思想就是解耦View和Model。TheMVP- https://github.com/kymjs/TheMVP
適用於小型專案的 Android MVP 架構- https://github.com/0xZhangKe/AndroidMVP
 使小型專案也可以很自然的使用 MVP 架構,實現高內聚,低耦合。

一個demo專案https://github.com/wongcain/MVP-Simple-Demo

Android-CleanArchitecture(MVP)- https://github.com/android10/Android-CleanArchitecture

https://github.com/youxin11544/mvp_hybride_framwork (這是一個Android MVP模型良好的架構設計,同時也做了Android和HTML 5互動架構,用到了RxJava+Retrofit+MVP+泛型縮減mvp+模板模式+命令模式+觀察者模式+管理者模式 +簡單工廠模式。

-- MVP:
android-architecture- https://github.com/googlesamples/android-architecture/tree/master
Android-CleanArchitecture- https://github.com/android10/Android-CleanArchitecture
iosched- https://github.com/google/iosched

Android架構(一)MVP架構在Android中的實踐- http://blog.csdn.net/johnny901114/article/details/54783106
 MVP的架構有如下好處:
1)降低了View和Model的耦合,通過Presenter層來通訊;
2)把檢視層抽象到View介面,邏輯層抽象到Presenter介面,提高了程式碼的可讀性、可維護性;
3)Activity和Fragment功能變得更加單一,只需要處理View相關的邏輯;
4)Presenter抽象成介面,就可以有多種實現,方便單元測試。

MVP架構:

 View Layer: 只負責UI的繪製呈現,包含Fragment和一些自定義的UI元件,View層需要實現ViewInterface介面。Activity在專案中不再負責View的職責,僅僅是一個全域性的控制者,負責建立View和Presenter的例項;
 Model Layer: 負責檢索、儲存、運算元據,包括來自網路、資料庫、磁碟檔案和SharedPreferences的資料;
 Presenter Layer: 作為View Layer和Module Layer的之間的紐帶,它從model層中獲取資料,然後呼叫View的介面去控制View;
 Contract: 我們參照Google的demo加入契約類Contract來統一管理View和Presenter的介面,使得某一功能模組的介面能更加直觀的呈現出來,這樣做是有利於後期維護的。

使用Activity和Fragment作為檢視層(View)真的合適麼?
    目前很多使用了MVP模式的android 專案,基本上都是將activity和fragment作為檢視層來進行處理的.而presenters通常是通過繼承自被檢視層例項化或者注入的物件來得到的. 誠然,我同意說,這種方式可以節省掉很多讓人厭煩的"import android.."語句, 並且將presenters從activity的生命週期中分割出來以後, 專案後續的維護會變得簡便很多.這種思路是正確的, 但是,從另一個角度來說, activity 有一個很複雜的生命週期(fragment的生命週期可能會更復雜), 而這些生命週期很有可能對你專案的業務邏輯有非常重大的影響. Activity 可以獲取上下文環境和多種android系統服務. Activity中傳送Intent,啟動Service和執行FragmentTransisitons等。而這些特性在我看來絕不應該是檢視層應該涉及的領域(檢視的功能就是現實資料和從使用者那裡獲取輸入資料,在理想的情況下,檢視應該避免業務邏輯).

基於上述的原因,我對目前的主流做法並不贊同,所以我在嘗試使用Activity和Fragment作為Presenters。

使用Activity和Fragment作為presenters的步驟:
1. 去除所有的view
  將Activity和Fragment作為presenter最大的困難就是如何將關於UI的邏輯抽取出來.我的解決方案是: 讓需要作為presenter的activity 或者 fragment來繼承一個抽象的類(或者叫"基類"), 這樣關於View 各種元件的初始化以及邏輯,都可以在繼承了抽象類的方法中進行操作,而當繼承了該抽象類的class需要對某些元件進行操作的時候,只需要呼叫繼承自抽象類的方法,就可以了。
  那麼抽象類怎麼獲取到的view元件呢?在抽象類裡面會有一個例項化的介面,這個介面裡面的init()方法就會對view進行例項化,這個介面如下:
public interface Vu {
    void init(LayoutInflater inflater, ViewGroup container);
    View getView();
}
正如你所見,Vu定義了一個通用的初始化例程,我可以通過它來實現一個容器檢視,它也有一個方法來獲得一個View的例項,每一個presenter將會和它自己的Vu關聯,這個presenter將會繼承這個介面(直接或者間接的去繼承一個來自Vu的介面)

2. 建立一個presenter基類 (Activity)
    有了Vu介面,我們可以通過構建一系列的class來操縱很多不同的view元件,這些class 使用Vu介面來初始化View元件,並通過繼承的方式給子類以操縱view元件的方法,以此來達到將ui 邏輯剝離出activity的目的。在下面的程式碼中,你可以看到,我覆寫了activity的onCreate 、 onCreateView、onDestroy 、 onDestroyView,通過對這些方法的覆寫,就可以對Vu的例項化和銷燬進行精確的控制(vu.init()就是例項化一個view元件)。onBindVu() 和onDestoryVu()是控制view生命週期的兩個方法。通過對actiivty中相關方法的覆寫達到控制元件的生命週期的目的(具體看下面的程式碼,你就明白了), 這樣做的好處就是無論是activity 還是 fragment, 其用與控制view元件建立和銷燬的語句是一樣的(儘量避免定義多餘的函式)。這樣的話,二者之間的切換也會減少一定的阻力(也許你今天的需求是用fragment實現的,但是第二天發現使用fragment會有一個驚天bug,譯者本人就遇到過)。

public abstract class BasePresenterActivity<V extends Vu> extends Activity {
    protected V vu;
    @Override
    protected final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            vu = getVuClass().newInstance();
            vu.init(getLayoutInflater(), null);
            setContentView(vu.getView());
            onBindVu();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected final void onDestroy() {
        onDestroyVu();
        vu = null;
        super.onDestroy();
    }

    protected abstract Class<V> getVuClass();
    protected void onBindVu(){};
    protected void onDestroyVu() {};
}
3. 建立一個基本的presenter(Fragment)
public abstract class BasePresenterFragment<V extends Vu> extends Fragment {
    protected V vu;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Override
    public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = null;
        try {
            vu = getVuClass().newInstance();
            vu.init(inflater, container);
            onBindVu();
            view = vu.getView();
        } catch (java.lang.InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return view;
    }
    @Override
    public final void onDestroyView() {
        onDestroyVu();
        vu = null;
        super.onDestroyView();
    }

    protected void onDestroyVu() {};
    protected void onBindVu(){};
    protected abstract Class<V> getVuClass();

}

 

4. 一個簡單的例子
如前文所述,我們已經確定了一個框架,現在就來寫一個簡單的例子來進一步的說明. 為了避免篇幅過長,我就寫一個“hello world”的例子。首先要有一個實現Vu介面的類:
public class HelloVu implements Vu {
View view;
TextView helloView;
@Override
public void init(LayoutInflater inflater, ViewGroup container) {
    view = inflater.inflate(R.layout.hello, container, false);
    helloView = (TextView) view.findViewById(R.id.hello);
}
@Override
public View getView() {
    return view;
}
public void setHelloMessage(String msg){
    helloView.setText(msg);
}

}

 

下一步,建立一個presenter來操作這個TextView
public class HelloActivity extends BasePresenterActivity {
@Override
protected void onBindVu() {
    vu.setHelloMessage("Hello World!");
}
@Override
protected Class<HelloVu> getVuClass() {
    return HelloVu.class;
}

}

OK,這樣就大功告成了!!是不是很簡便!
等等...耦合警告!

你可能注意到我的HelloVu類直接實現了Vu介面,我的Presenter的getVuClass方法直接引用了實現類。傳統的MVP模式中,Presenter是要通過介面與他們的View解耦合的。因此,你也可以這麼做。避免直接實現Vu介面,我們可以建立一個擴充套件了Vu的IHelloView介面,然後使用這個介面作為Presenter的泛型型別。這樣Presenter看起來應該是如下這樣的 :

public class HelloActivity extends BasePresenterActivity<IHelloVu> {
    @Override
    protected void onBindVu() {
        vu.setHelloMessage("Hello World!");
    }
    @Override
    protected Class<HelloVuImpl> getVuClass() {
        return HelloVuImpl.class;
    }
}
在我使用強大的模擬工具過程中,我個人並沒有看到在一個介面下面實現Vu所帶來的好處。但是對於我來說一個好的方面是,有沒有Vu介面它都能夠工作,唯一的需求就是最終你會實現Vu。

5. 如何進行測試
通過以上幾步,我們可以發現,去除了UI邏輯之後,Activity變得非常簡潔。並且,相關的測試 也變的非常異常的簡單。請看如下的程式碼:
public class HelloActivityTest {
    HelloActivity activity;
    HelloVu vu;
    @Before
    public void setup() throws Exception {
        activity = new HelloActivity();
        vu = Mockito.mock(HelloVu.class);
        activity.vu = vu;
    }
    @Test
    public void testOnBindVu(){
        activity.onBindVu();
        verify(vu).setHelloMessage("Hello World!");
    }
    }

以上程式碼是一段標準的jnuit單元測試的程式碼,不需要在android裝置中部署執行,只需要在編譯環境中即可測試。大幅度的提高了測試效率。但是,在測試某些方法的時候,你必須要使用android裝置,例如當你想測試activity生命週期中的resume()方法。在缺乏裝置環境的時候,super.resume()會報錯。為了解決這個問題,可以借鑑一些工具,例如Robolectric、還有android gradle 1.1 外掛中內建的testOptions { unitTests.returnDefaultValues = true }。此外,你仍然可以將這些生命週期也抽離出來。例如如下:

@Override
protected final void onResume() {
    super.onResume();
    afterResume();
}

protected void afterResume(){}
現在,你可以在沒有android裝置的情況下,快速的測試了!
意外收穫:使用adapter作為presenter
將Activity作為presente已經足夠狡猾了吧?使用adapter作為presenter,你想過沒有? 好吧,請看如下的程式碼:
public abstract class BasePresenterAdapter extends BaseAdapter {
protected V vu;
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
    if(convertView == null) {
        LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        try {
            vu = (V) getVuClass().newInstance();
            vu.init(inflater, parent);
            convertView = vu.getView();
            convertView.setTag(vu);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    } else {
        vu = (V) convertView.getTag();
    }
    if(convertView!=null) {
        onBindListItemVu(position);
    }
    return convertView;
}
protected abstract void onBindListItemVu(int position);

protected abstract Class<V> getVuClass();

}
如上程式碼,使用adapter作為presenter其實和activity或者fragement幾乎是一樣的,只有一點明顯的區別就是,我把onBingVu替換成了onBindListItemVu(接受int引數),其實我是借鑑了ViewHolder模式。
 

相關文章