MVP架構學習

MVP架構學習

M:數據層(數據庫,文件,網絡等...)
V:UI層(Activity,Fragment,View以及子類,Adapter以及子類)
P:中介,關聯UI層和數據層,由於V和M是相互看不到對方的,簡單而言就是不能相互持有對方的引用
MVP只是一種思想,不要把它認爲是一種規範,要學會靈活用戶,下面就帶你們走進MVP模式的學習android

需求

需求很簡單,咱們就作一個簡單的登陸功能,當點擊界面上的Login按鈕,會向後臺發送登陸請求。以下圖所示,數據庫

11

佈局文件服務器

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="xdysite.cn.testdemo.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="login"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:onClick="login"/>
</android.support.constraint.ConstraintLayout>

注:按鈕點擊會調用login方法網絡

方案1

本方案中給出了最樸素的實現方式架構

public class MainActivity extends AppCompatActivity {
    static final String TAG = "MainActivity";

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

    public void login(View v) {
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder().add("username", "admin").add("password", "12345").build();
        Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + call.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "onResponse: " + response.body().string());
            }
        });
    }
}

上圖中當咱們點擊login按鈕的時候會調用MainActivity中的login方法,在login方法中會向服務器發送請求,並等待返回結果(這裏使用了OKHttp,使用異步的方式發送請求)。app

小結

這種設計簡單明瞭,直觀具體,可是它有個侷限性,它將全部的功能所有在一個類中完成,那隻適合單人做戰。咱們想象一下,按照上面的方案,若是咱們讓一我的寫界面(佈局文件),讓一我的寫登陸功能(Activity),那麼寫登陸功能的人某一天將login函數改成了login2了,但他忘記告訴了寫界面的人,那是否是就是出現了問題。即便他告訴了寫界面的人說「你將界面的上的login換爲login2」,人家願不肯換仍是一回事呢!!!異步

方案2

本方案中引入MVP思想,對上面的設計優化一下。ide

Model層

Model咱們就讓其與服務器打交道,來實現登陸功能的邏輯,咱們實現了一個LoginModel類。函數

public class LoginModel {

    void login(String username, String password, final OnResultListener listener) {
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
        Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                listener.onResult(response.body().string());
            }
        });
    }

    public interface OnResultListener {
        void onResult(String result);
    }
}

在LoginModel類對外暴露了一個login的方法來供別人調用,傳入的參數爲用戶名、密碼和監聽器。監聽器做用是當服務器返回結果時調用。
監聽器是LoginModel類的內部接口,須要調用者去實現該接口。佈局

View層

View層就是咱們的Activity和佈局文件。View層將持有的P層的引用。下面是改造後的MainActivity類

public class MainActivity extends AppCompatActivity {
    static final String TAG = "MainActivity";
    LoginPresenter mLoginPresener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLoginPresener = new LoginPresenter(this);
    }

    public void login(View v) {
        mLoginPresener.login("admin", "12345");
    }

    public void showResult(String result) {
        Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
    }
}

在MainActivity類中持有了P層(LoginPresenter)的引用,當用戶點擊登陸時,它會調用P層的login方法,而且這裏還提供了一個showResult來顯示登陸結果(成功/失敗)

Presenter層

在P層充當中介的身份,它同時需瞭解V層和M層的狀況,所以P層將會持有V層和M層的引用。

public class LoginPresenter {
    LoginModel mLoginModel = new LoginModel();
    MainActivity mLoginView;

    public LoginPresenter(MainActivity loginView) {
        mLoginView = loginView;
    }

    public void login(String usernanem, String password) {
        mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
            @Override
            public void onResult(String result) {
                mLoginView.showResult(result);
            }
        });
    }
}

在LoginPresenter類中同時持有了MainActivity的引用和LoginModel的引用,當在MainActivity中調用LoginPresenter的login方法時,LoginPresener會調用LoginModel中的login方法,而後在回調中仍是調用MainActivity的showResult方法。這樣LoginPresenter就完成了中介的職責。

小結

經過MVP咱們將界面的處理和與服務器交互邏輯分離的開來,若是View層代碼被修改了,那麼M層的代碼將不會受任何影響,反之依然。這樣就解決了方案一種出現的爭端。這是MVP最簡單的運用了,說它簡單那麼存在不完善的地方。就拿V和P來講,LoginPresenter中調用了MainActivity的showResult方法,當這個方法被更名的話,在LoginPresenter中也要作一樣的修改。並且在建立LoginPresenter時也只能接受MainActivity對象,當你的老闆有天說咱們的登陸換成LoginFragment了,那你又要再寫一個接受LoginFragment對象的LoginPresenter了。下來咱們繼續優化上的設計

方案3

爲了更好的解耦,那麼咱們將會在View層引入接口類,接口的一大特性就是解耦。由於接口意味着規範,接口中的方法必需要實現,並且接口類一旦肯定,那麼不多發生修改了。

Model層

Model層的代碼咱們一點都不動

public class LoginModel {

    void login(String username, String password, final OnResultListener listener) {
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
        Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                listener.onResult(response.body().string());
            }
        });
    }

    public interface OnResultListener {
        void onResult(String result);
    }
}

View層

在View層中咱們定義了接口類LoginView,目的就是爲了讓V和P解耦。

接口類
public interface LoginView {
    void showResult(String result);
}
具體類
public class MainActivity extends AppCompatActivity implements LoginView {
    static final String TAG = "MainActivity";
    LoginPresenter mLoginPresener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLoginPresener = new LoginPresenter(this);
    }

    public void login(View v) {
        mLoginPresener.login("admin", "12345");
    }

    @Override
    public void showResult(String result) {
        Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
    }
}

具體類實現了LoginView接口,則代表showResult方法不是MainActivity全部了,它屬於接口類了,成爲了一種規範。

Presenter

P層如今持有的不是一個具體的View層對象了,它是面向接口的。無論你是Activity仍是Fragment,只要你實現了LoginView接口就好。並且它也不用擔憂View層胡亂改的問題了,只要你實現LoginView這個接口。

public class LoginPresenter {
    LoginModel mLoginModel = new LoginModel();
    LoginView mLoginView;

    public LoginPresenter(LoginView loginView) {
      mLoginView = loginView;
    }

    public void login(String usernanem, String password) {
        mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
            @Override
            public void onResult(String result) {
                mLoginView.showResult(result);
            }
        });
    }
}
小結

咱們將V和P經過接口的方式進行解耦了,咱們在MVP架構上又往前走了一步。可是,若是仔細研究代碼的話,咱們會發現有內存泄露的問題。當網絡請求發出去後,若是咱們立馬關閉Activity,那麼Activity會獲得釋放嗎?答案是不會,這個留給你們去思考。

補充

補充部分是對方案3的小優化,主要解決Activity引發的內存泄露的問題。

對Presenter優化

引入attcheView和detachView方法來綁定視圖和解除視圖,如今這兩個方法都比較單薄,可是咱們在這個方法中能夠根據業務須要添加別的邏輯了。

public class LoginPresenter {
    LoginModel mLoginModel = new LoginModel();
    LoginView mLoginView;

    public void login(String usernanem, String password) {
        mLoginModel.login(usernanem, password, new LoginModel.OnResultListener() {
            @Override
            public void onResult(String result) {
                if (mLoginView != null)
                mLoginView.showResult(result);
            }
        });
    }

    public void attcheView(LoginView loginView) {
        mLoginView = loginView;
    }

    public void detachView() {
        mLoginView = null;
    }
}
對MainActivity進行改進
public class MainActivity extends AppCompatActivity implements LoginView {
    static final String TAG = "MainActivity";
    LoginPresenter mLoginPresener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLoginPresener = new LoginPresenter();
        mLoginPresener.attcheView(this);
    }

    public void login(View v) {
        mLoginPresener.login("admin", "12345");
    }

    @Override
    public void showResult(String result) {
        Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLoginPresener.detachView();
        mLoginPresener = null;
    }
}

在onCreate中咱們建立了LoginPresenter對象,並將本身與其綁定。當Acitvity要銷燬時,咱們就解除綁定,這樣就防止發送內存泄露了。

方案4

前面的方案將View層進行了處理,使用了接口來使用V和P進行解耦,能不能作進一步處理,好比使用泛型。P層不用關心V層具體是什麼類型。甚至對V層和P層之間的數據交互也作泛型處理。這樣的話耦合程度更小了。

通用接口

將View層和Presenter層作接口化處理,固然Model層也能作接口化處理,因爲時間有限,這裏只實現前兩個。

View接口
public interface IView<D> {
    void showResult(D result);
}

D是表示數據,這樣showResult能夠處理任意類型的數據了

prestener接口
public interface IPresenter<D, V extends IView<D>> {
    void attcheView(V view);

    void detachView();
}

在Presenter中咱們對V也作了泛型處理,這樣Presenter能夠既能夠綁定Activity,又能夠綁定Fragment

通用抽象類

抽象類是對接口作了一點點實現,這個看我的需求了,若是你感受類太多的話能夠把接口剔除掉,直接使用抽象類來作。

Presenter抽象類
public abstract class AbsPresenter<D, V extends IView<D>> implements IPresenter<D, V> {
    private V mView;

    @Override
    public void attcheView(V view) {
        mView = view;
    }

    @Override
    public void detachView() {
        mView = null;
    }

    public V getView() {
        return mView;
    }
}

AbsPresenter類對Presenter接口作了些實現

Activity抽象類
public abstract class BaseActivity<D, V extends IView<D>, P extends AbsPresenter<D, V>> extends AppCompatActivity{
    private P presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (presenter == null) {
            presenter = bindPresenter();
            presenter.attcheView((V)this);
        }
    }

    public abstract P bindPresenter();

    public  P fetchPresenter() {
        return presenter;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (presenter != null)
            presenter.detachView();
    }
}

BaseActivity類對將視圖的綁定與解除抽了出來

具體實現

前面的兩部分很抽象了,能夠應對各類場景了,下面咱們就應用到登陸場景中。

首先是LoginModel
其實Model和Presenter直接也能夠解耦的,能夠定義一個Model接口出來,而Presenter持有Model接口的引用便可。可是因爲時間關係。咱們直接上Model了

public class LoginModel {

    public void login(String username, String password, final OnResultListener listener) {
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder().add("username", username).add("password", password).build();
        Request request = new Request.Builder().url("http://www.xdysite.cn/test").post(body).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                listener.onResult(response.body().string());
            }
        });
    }

    public interface OnResultListener {
        void onResult(String result);
    }
}

下來是View

public interface LoginView extends MvpView<String>{

}

public class MainActivity extends BaseActivity<String, LoginView, LoginPresenter> implements LoginView{

    @Override
    public LoginPresenter bindPresenter() {
        return new LoginPresenter();
    }


    @Override
    public void showResult(final String result) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
            }
        });
    }

     public void login(View v) {
        LoginPresenter presenter = fetchPresenter();
        if (presenter != null)
            presenter.login("admin", "12345");
    }
}

定義了LoginView接口,並在MainActivity中對泛型作了具體化處理,好比數據類型指定爲String類型。並且咱們發現MainActivity中的代碼更少了。

最後是Presenter

public class LoginPresenter extends AbsPresenter<String, LoginView> {
    LoginModel mLoginModel = new LoginModel();

    public void login(String username, String password) {
        mLoginModel.login(username, password, new LoginModel.OnResultListener() {
            @Override
            public void onResult(String result) {
                MvpView<String> loginView = getView();
                if (loginView != null)
                    loginView.showResult(result);
            }
        });
    }
}

總結

咱們經過遞進的方式,一步一步對MVP架構進行完善,並且這是MVP架構中的一種體現方式。其核心思想急就是分離,這種思想在方案2已經體現出來了,因此不要拘泥於某種模版或規範,要靈活運用。

相關文章
相關標籤/搜索