MVP是個(gè)好東西,可是最近項(xiàng)目一直用的是mvc模式,先馬克下之前鼓搗過(guò)的mvp框架,過(guò)年回家再用它重構(gòu)下代碼。
先上依賴庫(kù)####
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.android.support:design:24.2.1'
compile 'com.android.support:recyclerview-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.2.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
建議寫死依賴的版本號(hào),而不要使用“+”,避免版本升級(jí)帶了一些奇葩的問(wèn)題。
依賴retrolambda####
在app.build依賴
apply plugin: 'me.tatarka.retrolambda'
再加上
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
然后在整個(gè)項(xiàng)目的build文件上面加入:
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
OK,到這里我們的環(huán)境搭建就完成了
首先我們看下項(xiàng)目的目錄結(jié)構(gòu):

BaseContract(基本的文件類,我們可以在里面寫上view層,model層,Presenter層的interface)
/**
* Created by Ly on 2016/11/2.
*/
public class BaseContract {
}
BaseModel(基本的model層,所有耗時(shí)操作應(yīng)該寫在model層中)
/**
* Created by Ly on 2016/11/2.
*/
public interface BaseModel {
}
BaseView層(寫入跟用戶交互的方法集合類,比如showTosast,showDialog)
/**
* Created by Ly on 2016/11/2.
*/
public interface BaseView {
void TsShow(String msg);
}
看下我們的基本baseActivity.java:
package com.Ly.BaseJustTalk.base;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.MenuItem;
import android.widget.Toast;
import com.Ly.BaseJustTalk.R;
import butterknife.ButterKnife;
/**
* Created by Ly on 2017/1/12.
*/
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
private SwipeRefreshLayout mRefreshLayout;
private AppBarLayout mAppBar;
private Toolbar mToolbar;
private ProgressDialog mProgressBar;
protected Context mContext;
private boolean mIsRequestDataRefresh = false;
private static final String EXTRA_KEY = "extra";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dogetExtra();
mContext = this;
// 允許為空,不是所有的頁(yè)面都要實(shí)現(xiàn)這個(gè)模式
if (createPresenter() != null) {
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
setContentView(provideContentViewId());
ButterKnife.bind(this);
mAppBar = findViewById(R.id.app_bar_layout);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
if (mToolbar != null && mAppBar != null) {
setSupportActionBar(mToolbar); //把Toolbar當(dāng)做ActionBar給設(shè)置
if (canBack()) {
ActionBar actionBar = getSupportActionBar();
if (null != actionBar) {
//設(shè)置ActionBar一個(gè)返回箭頭,主界面沒(méi)有,次級(jí)界面有
actionBar.setDisplayHomeAsUpEnabled(true);
}
if (Build.VERSION.SDK_INT >= 21) {
//Z軸浮動(dòng)
mAppBar.setElevation(10.6F);
}
}
}
if (isSetRefresh()) {
setupSwipeRefresh();
}
}
public static void doStartActivity(Context context, Bundle bundle, Class<?> clz) {
Intent intent = new Intent(context, clz);
if (bundle != null) {
intent.putExtra(EXTRA_KEY, bundle);
}
context.startActivity(intent);
}
protected abstract void dogetExtra();
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 此時(shí)android.R.id.home即為返回箭頭
if (item.getItemId() == android.R.id.home) {
onBackPressed();
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
/**
* 生成下拉刷新
*/
private void setupSwipeRefresh() {
mRefreshLayout = findViewById(R.id.swipe_refresh);
if (null != mRefreshLayout) {
mRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);
mRefreshLayout.setProgressViewOffset(true,
0,
(int) TypedValue.applyDimension
(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
.getDisplayMetrics()));
}
}
/**
* 設(shè)置刷新
*
* @param requestDataRefresh
*/
public void setRefresh(boolean requestDataRefresh) {
if (mRefreshLayout == null) {
return;
}
if (!requestDataRefresh) {
mIsRequestDataRefresh = false;
mRefreshLayout.postDelayed(() -> {
if (mRefreshLayout != null) {
mRefreshLayout.setRefreshing(false);
}
}, 1000);
} else {
mRefreshLayout.setRefreshing(true);
}
}
/**
* 數(shù)據(jù)刷新
*/
public void requestDataRefresh() {
mIsRequestDataRefresh = true;
}
/**
* 判斷當(dāng)前頁(yè)面是否可以返回,
* 主界面不可以返回 子界面可以放回
*
* @return
*/
public boolean canBack() {
return false;
}
/**
* 判斷子Activity是否需要上下拉刷新功能
*
* @return
*/
public Boolean isSetRefresh() {
return false;
}
/**
* 創(chuàng)建P
*
* @return
*/
protected abstract T createPresenter();
/**
* 引入布局文件
*
* @return
*/
protected abstract int provideContentViewId();
protected void ShowTs(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
protected void ShowDialog() {
ShowDialog(null);
}
protected void ShowDialog(String msg) {
if (TextUtils.isEmpty(msg)) {
msg = getString(R.string.loading);
}
mProgressBar = ProgressDialog.show(this, null, msg);
}
protected void DissDialog() {
if (mProgressBar != null && mProgressBar.isShowing()) {
mProgressBar.dismiss();
}
}
}
相對(duì)應(yīng)的BaseFragment.java 代碼如下:
package com.Ly.BaseJustTalk.base;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.Ly.BaseJustTalk.R;
import butterknife.ButterKnife;
/**
* Created by Ly on 2011/1/12.
*/
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
protected Context mContext;
protected T mPresenter;
private boolean mIsRequestDataRefresh = false;
private SwipeRefreshLayout mRefreshLayout;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(createViewLayoutId(), container, false);
ButterKnife.bind(this, rootView);
initView(rootView);
if (isSetRefresh()) {
setupSwipeRefresh(rootView);
}
return rootView;
}
@Override
public void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
private void setupSwipeRefresh(View view) {
mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh);
if (mRefreshLayout != null) {
mRefreshLayout.setColorSchemeResources(R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_3);
mRefreshLayout.setProgressViewOffset(true, 0, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
mRefreshLayout.setOnRefreshListener(this::requestDataRefresh);
}
}
public void requestDataRefresh() {
mIsRequestDataRefresh = true;
}
public void setRefresh(boolean requestDataRefresh) {
if (mRefreshLayout == null) {
return;
}
if (!requestDataRefresh) {
mIsRequestDataRefresh = false;
mRefreshLayout.postDelayed(() -> {
if (mRefreshLayout != null) {
mRefreshLayout.setRefreshing(false);
}
}, 1000);
} else {
mRefreshLayout.setRefreshing(true);
}
}
protected abstract T createPresenter();
protected abstract int createViewLayoutId();
protected void initView(View rootView) {
}
public Boolean isSetRefresh() {
return true;
}
}
Ok,直接看實(shí)例代碼來(lái)理解,就用登錄界面的吧。

其中LoginActiviy為用戶能看到的界面;
Contract是mvp的協(xié)議層;Model為數(shù)據(jù)處理層;Presenter可以理解為橋梁,負(fù)責(zé)溝通連接M層和V層。
在我的理解里的MVP是這個(gè)意思:我們可以看到,這個(gè)類持有了View和Model兩個(gè)模塊,在方法體里面,我們調(diào)用了model的方法去做耗時(shí),在結(jié)果方法體里面我們調(diào)用了view的方法去修改UI,同時(shí)presenter這個(gè)模塊又被view持有了,view可以在聲明周期里面去調(diào)用特定的方法,view和presenter相互溝通,view和model完全隔離,presenter調(diào)控model,presenter溝通全局。
看下我們的登錄界面:
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.5"
android:gravity="center"
android:text="@string/text_welcome"
android:textSize="25sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="@dimen/margin_left"
android:layout_marginRight="@dimen/margin_right"
android:layout_weight="3"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/til_username"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et_username"
style="@style/edit_style"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_username" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/til_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin6">
<EditText
android:id="@+id/et_password"
style="@style/edit_style"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_password" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/bt_login"
style="@style/button_style"
android:layout_width="match_parent"
android:layout_height="@dimen/button_height"
android:layout_marginTop="@dimen/margin20"
android:text="@string/text_login_reg" />
</LinearLayout>
</LinearLayout>
LoginContract.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import com.Ly.BaseJustTalk.base.BaseModel;
import com.Ly.BaseJustTalk.base.BaseView;
/**
* Created by Ly on 2017/1/16.
*/
public class LoginContract {
interface LoginView extends BaseView {
String getUserName();
String getUserPass();
// userName錯(cuò)誤后進(jìn)行提示
void setUserNameErr(String errMsg);
// 密碼錯(cuò)誤后進(jìn)行提示
void setPassErr(String errMsg);
void doJustalkLogin();
void doLoginFial();
}
interface LoginModel extends BaseModel {
/**
* @param userName
* @param Pass
* @return 0 合法賬戶密碼 給予執(zhí)行下一步
* -1 用戶名為空
* -2 用戶密碼為空
* -3 用戶名不合法
* -4 用戶密碼不合法
*/
int isRightUserNamePass(String userName, String Pass);
/**
* 進(jìn)行justalk的登錄
*
* @param userName
* @param pass
* @return
*/
boolean doJustalkLogin(String userName, String pass);
}
interface LoginPresenter {
// 驗(yàn)證用戶輸入的信息
void doVerificationInfo();
// 驗(yàn)證正確后進(jìn)行登錄
void doJustalkLogin();
}
}
LoginModel.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import android.text.TextUtils;
import android.util.Log;
import com.Ly.BaseJustTalk.R;
import com.Ly.BaseJustTalk.application.LyApplication;
import com.justalk.cloud.juslogin.LoginDelegate;
/**
* Created by Administrator on 2017/1/16.
*/
public class LoginModel implements LoginContract.LoginModel {
/**
* 驗(yàn)證賬號(hào)合法性
*
* @param userName
* @param Pass
* @return 0 合法賬戶密碼 給予執(zhí)行下一步
* -1 用戶名為空
* -2 用戶密碼為空
* -3 用戶名不合法
* -4 用戶密碼不合法
*/
@Override
public int isRightUserNamePass(String userName, String Pass) {
if (TextUtils.isEmpty(userName)) {
return -1;
} else if (TextUtils.isEmpty(Pass)) {
return -2;
} else if (userName.length() < 4 || userName.length() > 12) {
return -3;
} else if (Pass.length() < 6 || Pass.length() > 12) {
return -4;
} else
return 0;
}
@Override
public boolean doJustalkLogin( String userName, String pass) {
if (LoginDelegate.getInitState() == LoginDelegate.InitStat.MTC_INIT_FAIL) {
return false;
}
String server = LyApplication.getInstance().getString(R.string.JusTalkCloud_network_address);
String network = null;
if (!server.startsWith("sudp:")) {
network = server;
server = "sarc:arc@AccessEntry:99";
Log.e("testtest", server);
}
if (LoginDelegate.login(userName, pass, server, network)) {
return true;
}
return false;
}
}
LoginPresenter.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import com.Ly.BaseJustTalk.base.BasePresenter;
/**
* Created by Ly on 2017/1/16.
*/
public class LoginPresenter extends BasePresenter<LoginContract.LoginView> implements
LoginContract.LoginPresenter {
private LoginContract.LoginView loginView;
private LoginContract.LoginModel loginModel;
public LoginPresenter(LoginContract.LoginView loginView) {
this.loginView = loginView;
loginModel = new LoginModel();
}
@Override
public void doVerificationInfo() {
int resultcode = this.loginModel.isRightUserNamePass(this.loginView.getUserName(), this.loginView.getUserPass());
switch (resultcode) {
case -1:
this.loginView.setUserNameErr("請(qǐng)輸入用戶名");
break;
case -2:
this.loginView.setPassErr("請(qǐng)輸入用戶密碼");
break;
case -3:
this.loginView.setUserNameErr("請(qǐng)檢查用戶名格式或長(zhǎng)度");
break;
case -4:
this.loginView.setPassErr("請(qǐng)檢查密碼格式或長(zhǎng)度");
break;
case 0:
this.loginView.setUserNameErr(null);
this.loginView.setPassErr(null);
this.loginView.doJustalkLogin();
break;
default:
this.loginView.TsShow("未知錯(cuò)誤");
break;
}
}
@Override
public void doJustalkLogin() {
boolean isLoginSuccess = this.loginModel.doJustalkLogin(this.loginView.getUserName(), this.loginView.getUserPass());
if (!isLoginSuccess) {
this.loginView.doLoginFial();
}
}
}
LoginActivity.java
package com.Ly.BaseJustTalk.ui.activity.Login;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import com.Ly.BaseJustTalk.R;
import com.Ly.BaseJustTalk.base.BaseActivity;
import com.Ly.BaseJustTalk.ui.activity.MainActivity;
import com.Ly.BaseJustTalk.utils.Signer;
import com.justalk.cloud.juslogin.LoginDelegate;
import com.justalk.cloud.lemon.MtcCli;
import com.justalk.cloud.lemon.MtcCliConstants;
import butterknife.Bind;
import butterknife.OnClick;
/**
* Created by Ly on 2017/1/16.
*/
public class LoginActivity extends BaseActivity<LoginContract.LoginView, LoginPresenter>
implements LoginContract.LoginView, LoginDelegate.Callback {
@Bind(R.id.et_username)
EditText etUsername;
@Bind(R.id.til_username)
TextInputLayout tilUsername;
@Bind(R.id.et_password)
EditText etPassword;
@Bind(R.id.til_password)
TextInputLayout tilPassword;
@Bind(R.id.bt_login)
Button btLogin;
private LoginContract.LoginPresenter loginPresenter = new LoginPresenter(this);
@OnClick(R.id.bt_login)
void toLogin() {
loginPresenter.doVerificationInfo();
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter(this);
}
@Override
protected int provideContentViewId() {
return R.layout.activity_login;
}
@Override
public void TsShow(String msg) {
ShowTs(msg);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LoginDelegate.setCallback(this);
}
@Override
protected void dogetExtra() {
}
@Override
public String getUserName() {
return etUsername.getText().toString().trim();
}
@Override
public String getUserPass() {
return etPassword.getText().toString().trim();
}
@Override
public void setUserNameErr(String errMsg) {
tilUsername.setError(errMsg);
}
@Override
public void setPassErr(String errMsg) {
tilPassword.setError(errMsg);
}
@Override
public void doJustalkLogin() {
ShowDialog();
if (MtcCli.Mtc_CliGetState() != MtcCliConstants.EN_MTC_CLI_STATE_LOGINED) {
this.loginPresenter.doJustalkLogin();
} else {
MainActivity.doStartActivity(mContext, null, MainActivity.class);
}
}
@Override
public void doLoginFial() {
ShowTs(getString(R.string.tips_login_fail));
}
@Override
public void mtcLoginOk() {
MainActivity.doStartActivity(mContext, null, MainActivity.class);
}
@Override
public void mtcLoginDidFail() {
ShowTs(getString(R.string.tips_login_fail_justalk));
}
@Override
public void mtcLogoutOk() {
}
@Override
public void mtcLogouted() {
}
@Override
public void mtcAuthRequire(String s, String s1) {
Log.e("LHT", "mtcAuthRequire: " + s + "----" + s1);
String key =
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"tRPBKBjUTEQwaKEjPy8YnX1bUODPB+7hto8KeGJbnCdCcnJdLxPFE7ld1skKIyPi" +
"YkyHj73JqA41ntHML2LNqw5Mhs1pewE4QLCu6icIUNtH8+bL53EhVnfdzwIDAQAB" +
"AoGAA6i6c5xHEGfKzoDHQJgiC5X9ZFAtES5AG3IMJmtF9flQyeoeDzRit+/FwNXi" +
"M1CKohnvLAJTvPs/8TBp5us4rabQ5Hnp+ylr7I2IbYIP2LV6TKkiTq/fBOJBxZiw" +
"qs0tjXxRZnC2IWqoCt/ciE4DXQIYVV3gYMRcKae5KZ3F2LECQQDqL4Sd2xyG0FsW" +
"cKwrlFRQ1cfFrSF/jmm2onkCZgDfq0R5aIGewpbTciLj8rf/Zq0XgAmCa3qQYo6M" +
"7G0OgIXTAkEAxbIC2xJocvPfEFbUd+hEWDFl/3LtQWZSHVLx9SXLXWSRY4/3dyRM" +
"6L78eQ2yWIVF4pxJrIHTbJqhxItlVM/elQJBAJ3jRZ0L8hKufQsHEf0btzD8wQB0" +
"doZCZOF+bumADgy+sp7MJ7/636dVZ1KZ/RWTixWx/DdS8UJRQFygtfI2EoMCQHky" +
"4tFPfb1LiStJMES6nnu6/R8YZB++DQVxPmjeXMjKyN9S+ZGPLZ9axwmnvfjK68c7" +
"rWcWyHlCa35FP0A5l+kCQB5cEu5Au1RkY9XfUodKmFhlCvdY8Ig0JgZ8DC6m+A31" +
"o4xrCoGHiPldKdCo0I7gQ4WMvoVNHCQyNv5qcw9t7uk=";
String code = Signer.signWithKey(key, s, s1, 3600);
LoginDelegate.promptAuthCode(code);
}
@Override
protected void onDestroy() {
super.onDestroy();
LoginDelegate.logout();
}
}
更新 這個(gè)框架里面的循環(huán)嵌套的方法
final String account = "111_111_1111", password = "1234556";
HttpRequest.getApiService().doRegister(account, password).concatMap(new Function<RegisterBean, Flowable<LoginUserBean>>() {
@Override
public Flowable<LoginUserBean> apply(RegisterBean registerBean) throws Exception {
if (registerBean.getStatus().equals(ErrCodeMessage.statusSuc)) {
XLog.e(registerBean.getMessage());
return HttpRequest.getApiService().doLogin(account, password);
} else {
return Flowable.empty();
}
}
}).compose(XApi.<LoginUserBean>getScheduler()).compose(XApi.<LoginUserBean>getApiTransformer())
.compose(this.<LoginUserBean>bindToLifecycle()).subscribe(new ApiSubscriber<LoginUserBean>() {
@Override
protected void onFail(NetError error) {
XLog.e("xxxxxxxxxxxx" + error.getMessage());
}
@Override
public void onNext(LoginUserBean loginUserBean) {
XLog.e("xxxxxxxxxxxx" + loginUserBean.toString());
}
});
如果注冊(cè)失敗的時(shí)候 返回 return Flowable.empty(); 一個(gè)空的