一步一步帶你認識MVP+Retrofit+Rxjava並封裝(一)

24K純帥豆發表於2017-10-16

一步一步帶你認識MVP+Retrofit+Rxjava並封裝(一)

一步一步帶你認識MVP+Retrofit+Rxjava並封裝(二)

序言:這本來是LZ一直想寫的一個系列的文章(哎呀,說的好像自己挺牛逼似的>_<)當下最流行的設計模式之一的MVP再配上當下最流行的網路請求框架之一的Retrofit+Rxjava(這裡我也不引發戰爭了,PHP是世界上最好的語言grin: :grin: :grin: ),相信大部分人看簡書部落格的時候都會經常看到高仿某某APP(基於MVP+Retrofit+Rxjava)類的文章,反正LZ是經常看到,好了,扯淡就扯到這裡,下面開始我們的正題:

1、首先我們來很不情願的簡單介紹一下MVP這個東東,為何說不情願,首先,這些概念性的東西我自己都有點看不下去,其次網上講這個的東西實在是太多了。官方的解釋為經典MVC模式的演化版,這裡我們也不詳細講啥是MVC了,就是一種古老而又神奇的模式。講一個很簡單的栗子:

有一家早餐店,他們家賣包子、饅頭、油條、粥等等這些東西,他們需要用到最重要的東西是麵粉(原材料:大米),每次早餐店老闆都從一家麵粉店(大米店)訂購,而這些大米都是麵粉店老闆去農民伯伯那裡收購的。在這個小例子當中,農民伯伯的任務就是生產出大米,這其實就相當於MVP模式中的Model,大米就是實體,生產大米就是業務的邏輯;麵粉店老闆負責收購大米然後加工成麵粉,這其實就相當於MVP模式中的Presenter,負責完成農民伯伯和早餐店老闆這兩邊的間接交易;最後早餐店老闆的任務就是負責將麵粉做成各種各樣的早餐供大家享用,這其實就相當於MVP模式中的View,負責將麵粉做成各種各樣的食物呈現在大家眼前。好了,相信通過這個栗子大家應該對MVP模式有一定的瞭解了,下面來看一張圖:

盜用洋神的圖片
盜用洋神的圖片

2、相信用過MVP的同志們都有體會,每個人對於MVP模式的理解都不一樣,這樣導致寫出來的程式碼也都風格迥異,但是思想都是一樣的(即降低ModelView之間的耦合度,使得程式碼變的更清晰),所以即將學習MVP的小夥伴們看部落格的時候不要驚慌,因為你們會看到各種的程式碼,下面我們一起來看一下LZ寫的(寫的不好的地方歡迎指正):

(1)、首先我們來看一下Model,這部分我理解的就是資料獲得的地方,換句話說就是進行網路請求的地方(或者本地資料的獲取),這裡我寫了一個Base類,將所有的Model請求資料相同的部分都放到了一起:

public class BaseModel extends BaseRetrofit {

    private static final String TAG = "BaseModel";

    protected CygApi mServletApi;   //所有的註解介面

    protected Map<String, String> mParams = new HashMap<>();

    public BaseModel() {
        super();
        mServletApi = mRetrofit.create(CygApi.class);
    }

    //獲取公共引數
    @Override
    protected Map<String, String> getCommonMap() {
        Map<String, String> commonMap = new HashMap<>();
        commonMap.put("user_id", String.valueOf(UserDao.getInstance().getUserId()));
        commonMap.put("token", String.valueOf(UserDao.getInstance().getToken()));
        return commonMap;
    }

    //新增一個引數
    protected void addParams(String key, String value) {
        if (TextUtils.isEmpty(key)) {
            Log.e(TAG, "the key is null");
            return;
        }
        mParams.put(key, value);
    }

    //新增多個引數
    protected void addParams(Map<String, String> params) {
        if (null == params) {
            Log.e(TAG, "the map is null");
            return;
        }
        mParams.putAll(params);
    }
}複製程式碼

這裡網路請求用的是Retrofit+RxJava,這一部分我打算放到下一篇再講,在這個Base類裡面主要新增了公共引數和新增普通引數。來看看一個登入的Model

public class LoginModel extends BaseModel {

    public static LoginModel getInstance() {
        return getPresent(LoginModel.class);
    }

    public void execute(String phone, String password, Observer<User>     observer) {
        addParams("username", phone);
        addParams("password", password);
        Observable observable = mServletApi.getUserInfo(mParams).map(new HttpFunction());
        toSubscribe(observable, observer);
    }
}複製程式碼

(2)、這裡我是用的單例模式(其實我也不知道這樣寫單例會不會有錯,用類去例項化一個物件>_<我覺得沒啥問題,哈哈哈),然後execute方法就是進行網路請求了,在Presenter中呼叫這個方法就行了。然後我們來看一下BasePresenter

public class BasePresenter<VIEW> {

    private WeakReference<VIEW> mViews;

    protected void attachView(VIEW view) {
        mViews = new WeakReference<VIEW>(view);
    }

    protected VIEW getView() {
        return isViewAttached() ? mViews.get() : null;
    }

    private boolean isViewAttached() {
        return null != mViews && null != mViews.get();
    }

    protected void detachView() {
        if (null != mViews) {
            mViews.clear();
            mViews = null;
        }
    }
}複製程式碼

BasePresenter裡面我只是關注了View,按照MVP模式的理解,我們應該在這個裡面同時關注ViewModel,確實,很多demo都是這樣乾的,但是LZ前面是用的單例來寫Model,所以在BasePresenter裡面就暫時先關注View,還有一點需要說明的是,這裡對View使用的弱引用,我們都知道View通常來說都是很大隻的存在,為了防止記憶體洩漏,使用弱引用來及時釋放記憶體。來看看一個登入的LoginPresenter

public class LoginPresenter extends BasePresenter<LoginView<User>> {

    public LoginPresenter(LoginView<User> loginView) {
        attachView(loginView);
    }


    public void getUserInfo(BaseImpl baseImpl) {
        LoginModel.getInstance().execute(getView().getUserName(), getView().getPassword(), new CygBaseObserver<User>(baseImpl, "正在登入") {
            @Override
            protected void onBaseNext(User data) {
                UserInfo userInfo = new UserInfo();
                userInfo.setId(data.getId());
                userInfo.setUsername(getView().getUserName());
                userInfo.setToken(data.getToken());
                UserDao.getInstance().deleteAll(UserInfo.class);
                UserDao.getInstance().insertObject(userInfo);
                getView().onRequestSuccessData(data);
            }
        });
    }

    public void toMainActivity(Activity activity) {
        activity.startActivity(new Intent(activity, MainActivity.class));
    }
}複製程式碼

(3)、在登入的LoginPresenter中呼叫LoginModel進行網路請求,只返回一個成功的回撥,失敗的回撥我們在內部處理掉了,然後在回撥成功之後做相應的資料操作(該回撥給View的就回撥給View,該存本地的就存本地)。然後來看看我們的View

public class LoginActivity extends BaseActivity<LoginPresenter> implements LoginView<User> {

    @BindView(R.id.al_et_user_name)
    TextInputEditText alEtUserName;
    @BindView(R.id.al_et_password)
    TextInputEditText alEtPassword;

    @Override
    protected int layoutRes() {
        return R.layout.activity_login;
    }

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter(this);
    }

    @Override
    protected void initView() {

    }

    @Override
    public String getUserName() {
        return alEtUserName.getText().toString().trim();
    }

    @Override
    public String getPassword() {
        return alEtPassword.getText().toString().trim();
    }

    @OnClick(R.id.al_btn_login)
    public void onViewClicked() {
        if (TextUtils.isEmpty(getUserName())) {
            alEtPassword.setError("使用者名稱不能為空");
            return;
        }
        if (TextUtils.isEmpty(getPassword())) {
            alEtPassword.setError("密碼不能為空");
            return;
        }
        mPresenter.getUserInfo(this);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        moveTaskToBack(true);
    }

    @Override
    public void onRequestSuccessData(User data) {
        mPresenter.toMainActivity(this);
    }
}複製程式碼

View中,就是初始化Presenter,然後各種調Presenter中的方法,這裡本來是可以在Presenter中直接呼叫toMainActivity()方法的,為了演示成功回撥之後再回撥給View,這裡我就多做了一步操作。好了,接下來我們來看看介面:

public interface BaseRequestContract<T> {

    void onRequestSuccessData(T data);

}複製程式碼

這裡寫了一個Base介面,由於大多時候我們只關注成功的回撥資料,這裡我也只寫了一個成功回撥的方法(如果你有其他的需求,你可以在這裡加一些公共的方法),如果你有需要的話你可以在子類中寫錯誤回撥的介面,接著我們來看看登入的介面有哪些方法:

public interface LoginView<T> extends BaseRequestContract<T>{

    String getUserName();

    String getPassword();

}複製程式碼

這裡我需要獲得使用者的輸入資訊,所以只簡單定義了兩個方法用來獲取使用者名稱和密碼。到這裡我們的MVP模式就簡單封裝的差不多了,接下來我們來看一下最終的效果吧:

這裡用eclipse+tomcat+mysql簡單寫了一個登入介面,這一部分LZ在之前的部落格中有詳細講解,如有興趣,請移步:

android開發怎麼少的了後端(上)

android開發怎麼少的了後端(中)

android開發怎麼少的了後端(下)

好了,MVP的基本封裝就講到這裡,下一節我們再來講一下Retrofit+Rxjava的簡單封裝及使用,這裡先奉上程式碼:

MVP 主工程程式碼

MVP module工程程式碼

可能很多人就會問了,為什麼會有兩份呢,這裡我給大家看看我的專案工程

LZ把跟主專案無關的邏輯都寫到module中去了,這樣也是為了更好的重用程式碼。如果你直接去下載兩個檔案的話,那麼請你下載之後把cygmodule-master資料夾的名字改成cygmodule,或者你也可以在主工程中將setting.gradle中的String jackchengPath = rootDir.absolutePath + '/../cygmodule'更換成你的路徑

公眾號:Android技術經驗分享
公眾號:Android技術經驗分享

相關文章