1. 程式人生 > 其它 >Base封裝之我的最簡MVP架構

Base封裝之我的最簡MVP架構

緒論

最近懶癌症犯了,好久都沒寫部落格了,當然也在做一些東西,也在整理自己一直以來使用的一些技術點,從Retrofit到OkGO,從ListView到RecycleView,從Java到Kotlin….總之一直在嘗試新的技術,今天分享一下自己一直所用的MVP,整理完了分享給大家,有不合適或者不正確的地方還希望大家多多指正,共同交流。

對了 打一波廣告 我的新的個人部落格 http://hankkin.cn/

好了接下來開始我們的MVP

背景

眾所周知 MVP這種架構模式已經出現很久了,大體時間應該是2014年吧,現在網上的關於MVP的文章也很多,各式各樣的關於MVP的架構知識都湧現出來,可想而知現在這種架構有多麼火,還有目前風頭正勁的MVVM,當然我並不覺得我現在寫MVP有些晚,因為每個人都有每個人的架構,每個人都可以根據自己的邏輯封裝出來自己的架構模式,今天我介紹的便是我自己通過專案總結出來的MVP

什麼是MVP

MVP知識點

MVP - Model-View-Presenter

MVP和MVC的區別僅僅在於P和Control,MVC中View和Model是互通的可以互相通訊,在Android中View一般代表著我們的xml進行介面的描述,而對於模型Model部分則大多對應於本地的資料檔案或網路獲取的資料體,很多情況下我們對這些資料的處理也會在這一層中進行,最後的控制器Controller則當之無愧的是右Activity承擔。

而MVP中view通過presenter訪問model,大大的減小了耦合性,業務邏輯都交給P處理,通過P訪問V層更改UI。MVP模式可以分離顯示層與邏輯層,它們之間通過介面進行通訊,降低耦合。理想化的MVP模式可以實現同一份邏輯程式碼搭配不同的顯示介面,因為它們之間並不依賴與具體,而是依賴於抽象。這使得Presenter可以運用於任何實現了View邏輯介面的UI,使之具有更廣泛的適用性,保證了靈活度。

這裡不多介紹MVC了,相信大家都很熟悉

MVP的優缺點

優點:

  • 降低耦合度,實現了M層和V層的完全分離,可以修改V層不影響M層
  • 模組職責劃分明顯,層次清晰
  • P層可以複用,一個P可以對應多個V,不需要修改P的邏輯
  • 單元測試更加簡單方便
  • 程式碼靈活度高

缺點:

  • V層和P層互動頻繁
  • 程式碼量多,類變多了

總結

  • M層負責儲存、檢索、操縱資料,代表著一類元件或者類,這些元件或類可以向外部提供資料,同時也能從外部獲取資料將資料儲存起來
  • V層負責將資料UI呈現給使用者。一般的檢視UI只包含介面,並不包含介面邏輯,V層收P層控制,在Android中一般是Activity、Fragment、View、ViewGroup。。。
  • P層作為V層和M層的中間樞紐,處理使用者互動的業務邏輯

MVP實現

1.基本實現

我們都知道一般MVP架構一共需要以下四步:

  • 定義一個interface介面XView,對應的Activity,Fragment實現這個interface
  • 編寫Molde,裡面的業務邏輯主要包括網路請求獲取資料,資料庫讀取等耗時操作,通過M層回撥給P層通知V層更新UI
  • 編寫Presenter,P層持有V和M的引用,實現P層的回撥,並且回撥給V層更新
  • Activity中呼叫P執行業務邏輯,更新UI

具體程式碼就不貼了,相信瞭解過MVP的都會寫基本的程式碼

但是問題也就出來了,由於P層需要和V層進行通訊,更新UI時需要持有V層的view物件,那麼我們每個P裡面一般都用構造去初始化這個View,類多了之後感覺很煩,而View層裡的一些常用的方法我們也可以封到base裡面,比如loading的顯示隱藏,空佈局和錯誤佈局的顯示…

2.Base封裝

1.BaseView
package com.hankkin.xlibrary.mvp;import android.view.View;
/**
 * Created by hankkin on 2017/3/29.
 */public interface BaseView {

    /**
     * 顯示loading框
     */
    void showProgress();

    /**
     * 隱藏loading框
     */
    void hideProgress();

    void toast(CharSequence s);

    void toast(int id);

    void toastLong(CharSequence s);

    void toastLong(int id);


    /**
     * 顯示空資料佈局
     */
    void showNullLayout();

    /**
     * 隱藏空資料佈局
     */
    void hideNullLayout();

    /**
     * 顯示異常佈局
     * @param listener
     */
    void showErrorLayout(View.OnClickListener listener);

    void hideErrorLayout();
}
2.BasePresenter
package com.hankkin.xlibrary.mvp;
/**
 * Created by hankkin on 2017/3/29.
 */public abstract class BasePresent<T>{
    public T view;

    public void attach(T view){
        this.view = view;
    }

    public void detach(){
        this.view = null;
    }
}

我們在BasePresenter裡面去初始化View物件,同時提供釋放View物件以防止記憶體溢位

3.MvpActivity
package com.hankkin.hlibrary.base;
import android.os.Bundle;
import android.support.annotation.Nullable;

import com.lzy.okgo.OkGo;/**
 * Created by hankkin on 2017/3/29.
 */public abstract class MvpActivity<V,P extends BasePresent<V>> extends BaseAcitvity{

    protected P presenter;



    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = initPresenter();
    }

    @Override
    protected void onResume() {
        super.onResume();
        presenter.attach((V) this);
    }

    @Override
    protected void onDestroy() {
        presenter.detach();
        OkGo.getInstance().cancelTag(this);
        super.onDestroy();
    }

    public abstract P initPresenter();}

這樣我們在Activity中初始化P,並且連線V,在onDestroy()生命週期中釋放P中引用的V。

Example

我們按照功能模組來構造我們的MVP,可能大家注意到了沒有M層啊,是的,這裡我把M層捨棄掉了,把業務邏輯、網路請求直接放在了P層,大大減少了類的數量,這樣我們每個功能模組只需要新建一個View和一個Presenter就可以滿足了,特殊的需求再通過特殊方法來處理,下面我們舉一個簡單的例子:

網路請求我用的 jeasonlzy 大神的OKGo3,剛出鍋沒幾天,嘗試一下,個人認為封裝的非常非常好,繼承了Rx,Retrofit,相信你會喜歡的。

https://github.com/jeasonlzy/okhttp-OkGo

好了下面看我們的例子吧:

專案結構

看一下專案結構

HomeView

我用的Gank.io裡面的一個介面獲取資料,首先我們定義我們的HomeView,裡面有兩個方法獲取資料成功和獲取失敗

package com.hankkin.mvpdemo.home;
import com.hankkin.hlibrary.BaseView;
/**
 * Created by hankkin on 2017/6/19.
 */public interface HomeView extends BaseView{

    void getDataHttp(String data);

    void getDataHttpFail(String msg);
}
HomePresenter

然後我們定義HomePresenter,裡面只有我們的網路請求,因為我們的BasePresenter持有View物件,所以在回撥中直接呼叫HomeView的兩個成功失敗的方法

package com.hankkin.mvpdemo.home;
import com.hankkin.hlibrary.BasePresent;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.model.Response;
/**
 * Created by hankkin on 2017/6/19.
 */public class HomePresenter extends BasePresent<HomeView> {


    public void getGankData(){
        OkGo.<String>get("http://gank.io/api/data/Android/10/1")
                .tag(this)
                .execute(new StringCallback() {
                    @Override
                    public void onSuccess(Response<String> response) {
                        view.getDataHttp(response.body());
                    }

                    @Override
                    public void onError(Response<String> response) {
                        super.onError(response);
                        view.getDataHttpFail(response.message());
                    }
                });
    }
}
Activity

最後看一下Activity,我們的Activity繼承了MVPActivity並實現了HomeView,同時將泛型物件設為我們的HomeView和HomePresenter,這樣我們就可以直接呼叫P層的網路請求方法,同時也能回撥更新UI

package com.hankkin.mvpdemo;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.view.MenuItem;import android.view.View;
import android.widget.Button;import android.widget.TextView;
import com.hankkin.hlibrary.MvpActivity;
import com.hankkin.mvpdemo.home.HomePresenter;
import com.hankkin.mvpdemo.home.HomeView;
import static com.hankkin.mvpdemo.R.id.btn_get;
public class MainActivity extends MvpActivity<HomeView,HomePresenter> implements HomeView{

    private TextView mTextMessage;
    private Button btnGet;

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    mTextMessage.setText(R.string.home);
                    return true;
                case R.id.navigation_dashboard:
                    mTextMessage.setText(R.string.control);
                    return true;
                case R.id.navigation_notifications:
                    mTextMessage.setText(R.string.notification);
                    return true;
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextMessage = (TextView) findViewById(R.id.message);
        btnGet = (Button) findViewById(btn_get);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

        btnGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showProgress();
                presenter.getGankData();
            }
        });
    }

    @Override
    public HomePresenter initPresenter() {
        return new HomePresenter();
    }


    @Override
    public void getDataHttp(String data) {
        mTextMessage.setText(data);
        hideProgress();
    }

    @Override
    public void getDataHttpFail(String msg) {
        toast(msg);
    }


    @Override
    public void toast(CharSequence s) {
        toast("獲取成功");
    }
}

結論

對於BaseActivity我在之前的文章裡面已經介紹了,還不瞭解的請看

Android談談封裝那些事–BaseActivity和BaseFragment(一)

Android談談封裝那些事–BaseActivity和BaseFragment(二)

也已經優化過了相關的封裝邏輯,也會在接下來的文章繼續介紹的。

下一篇文章我會繼續介紹我的封裝之路,近期會將我的HLibrary提到我的Github上,大家可以star一下我的Github。

程式碼已經上傳到我的Github

https://github.com/Hankkin/MvpDemo

好了是不是很簡單呢?小夥伴們如果有啥好的建議或者覺得不妥的地方希望及時指正,共同交流,謝謝。

其實MVP有好多種,這裡給大家推薦幾個我覺得比較好的

http://www.jianshu.com/p/3a17382d44de#

http://www.jianshu.com/p/9a6845b26856

一步一步實現Android的MVP框架