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