MVP是模型(Model)、視圖(View)、主持人(Presenter)的縮寫,分別表明項目中3個不一樣的模塊。如圖所示:
javascript
通過這樣的構思,咱們能夠先實踐一下,咱們讓View來實現Model的接口,View來調用presenter,presenter利用面向接口編程的思想來調用接口實現對View的操做。實例以下:java
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.acheng.achengutils.mvp.model.BaseViewController;
import com.acheng.achengutils.mvp.presenter.BasePresenter;
/** * Created by pc859107393 on 2016/6/28. */
public abstract class BaseActivity<T extends BasePresenter, M extends BaseViewController> extends AppCompatActivity {
public String TAG; //當前Activity的標記
protected T mPresenter; //主持人角色
protected abstract T initPresenter(); //獲取到主持人
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TAG = String.format("%s::%s", getPackageName(), getLocalClassName());
mPresenter = initPresenter(); //初始化Presenter,提供主持人,擁有主持人後才能提交界面數據給presenter
setContentView(setLayoutId());
initView();
mPresenter.initData();
initEvent();
doOther();
}
protected void doOther() {
}
public Context getContext() {
return this;
}
protected abstract void initEvent();
protected abstract void initView();
protected abstract int setLayoutId();
@Override
protected void onResume() {
super.onResume();
//若是presenter爲空的時候,咱們須要從新初始化presenter
if (mPresenter == null) {
mPresenter = initPresenter();
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
public void onBackPressed() { //返回按鈕點擊事件
//當Activity中的 進度對話框正在旋轉的時候(數據正在加載,網絡延遲高,數據難以加載),關閉 進度對話框 , 而後能夠手動執行從新加載
super.onBackPressed();
}
/** * 恢復界面後,咱們須要判斷咱們的presenter是否是存在,不存在則重置presenter * * @param savedInstanceState */
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (mPresenter == null)
mPresenter = initPresenter();
}
/** * onDestroy中銷燬presenter */
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter = null;
}
}複製代碼
既然咱們的Activity已經設定好了BaseActivity,咱們須要接着完成BasePresenter,以下:android
import com.acheng.achengutils.mvp.model.BaseViewController;
/** * Created by acheng on 2016/7/14. */
public abstract class BasePresenter<D extends BaseViewController> {
public D model;
/** * 在子類的構造函數中,設定參數爲model,這時候能夠presenter調用接口來實現對界面的操做。 */
public BasePresenter(D model) {
this.model = model;
}
public abstract void initData();
}複製代碼
關於我這個Presenter的設計,我想說的是咱們須要將各層解耦,那麼個人presenter就不該該持有Android程序流轉的必然因子,如Context、Bundle、Intent、View等,若是咱們須要實現對界面的操做,必須經過調用咱們設定好的Model來實現,關於BaseModel更加簡單了,直接是一個空的接口文件,以下:git
public interface BaseViewController {
//這裏面添加實現類須要實現的方法便可
}複製代碼
說了這麼多,咱們直接手底下見真章:github
import android.Manifest;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.TextView;
import com.acheng.achengutils.mvp.view.BaseActivity;
import com.acheng.achengutils.utils.SPHelper;
import com.acheng.achengutils.widgets.AppUpdateDialog;
import com.acheng.achengutils.widgets.MustDoThingDailog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import acheng1314.cn.a3dbuild.MyApplication;
import acheng1314.cn.a3dbuild.R;
import acheng1314.cn.a3dbuild.bean.LoginBean;
import acheng1314.cn.a3dbuild.view.activity.presenter.LoginActivityPresenter;
import acheng1314.cn.a3dbuild.view.activity.viewcontroller.LoginActivityViewController;
import acheng1314.cn.a3dbuild.widgets.MyProgressDialog;
/** * Created by pc859107393 on 2016/9/12 0012. */
public class LoginActivity extends BaseActivity<LoginActivityPresenter, LoginActivityViewController> implements LoginActivityViewController {
private View mBt_login;
private TextView mEt_username; //用戶名
private TextView mEt_password; //密碼s
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
private AppUpdateDialog appPermission; //權限申請對話框
private MyProgressDialog myProgressDialog; //進度對話框
@Override
protected LoginActivityPresenter initPresenter() {
return new LoginActivityPresenter(this); //實例化LoginActivity的Presenter
}
@Override
protected void initEvent() {
mBt_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyApplication.getInstance().outLog(TAG, "MDZZ"); //日誌輸出
//調用Presenter的登陸的網絡請求,將用戶名和密碼傳遞過去
mPresenter.doLogin(mEt_username.getText().toString(), mEt_password.getText().toString());
}
});
}
@Override
protected void initView() {
MyApplication.getInstance().addActivity(this); //將Activity加入堆棧管理
mEt_username = (TextView) findViewById(R.id.mEt_username);
mEt_password = (TextView) findViewById(R.id.mEt_password);
mBt_login = findViewById(R.id.mBt_login);
}
@Override
protected void doOther() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
List<String> permissionsNeeded = new ArrayList<String>();
final List<String> permissionsList = new ArrayList<String>();
if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE))
permissionsNeeded.add("手機存儲空間");
if (!addPermission(permissionsList, Manifest.permission.READ_PHONE_STATE))
permissionsNeeded.add("獲取手機狀態");
if (!addPermission(permissionsList, Manifest.permission.CAMERA))
permissionsNeeded.add("手機相機");
if (!addPermission(permissionsList, Manifest.permission.ACCESS_COARSE_LOCATION))
permissionsNeeded.add("手機位置");
// if (!addPermission(permissionsList, Manifest.permission.WRITE_SETTINGS))
// permissionsNeeded.add("手機設置");
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) { //待申請的權限列表
// Need Rationale
String message = "你必須容許本APP使用:" + permissionsNeeded.get(0);
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showMessageOKCancel(message,
new DialogInterface.OnClickListener() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
});
return;
}
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
}
super.doOther();
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("容許", okListener)
.setNegativeButton("拒絕", null)
.create()
.show();
}
private boolean addPermission(List<String> permissionsList, String permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
if (!shouldShowRequestPermissionRationale(permission))
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<String, Integer>();
// Initial
perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.READ_PHONE_STATE, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.ACCESS_COARSE_LOCATION, PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for ACCESS_FINE_LOCATION
if (perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
//通過用戶受權,得到全部權限
if (appPermission != null) {
appPermission = null;
}
// All Permissions Granted
} else { //未獲得用戶受權
// Permission Denied
appPermission = new AppUpdateDialog(AppUpdateDialog.IMPORTANT, "一些權限未被容許,請在設置中受權!", getContext(), new AppUpdateDialog.NeedDoThing() {
@Override
public void mustDoThing() {
Uri packageURI = Uri.parse("package:" + getPackageName());
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
startActivity(intent);
}
});
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
protected void onResume() {
super.onResume();
doOther();
}
@Override
protected int setLayoutId() {
return R.layout.activity_login;
}
@Override
public void showDailog(String msg) {
new MustDoThingDailog("提示", msg, getContext(), new MustDoThingDailog.NeedDoThing() {
@Override
public void mustDoThings() {
}
});
}
@Override
public void showProgressD() {
if (null == myProgressDialog)
myProgressDialog = new MyProgressDialog("登錄", "正在登陸···", getContext());
else
myProgressDialog.show();
}
@Override
public void disProgressD() {
if (null != myProgressDialog)
myProgressDialog.dismiss();
}
@Override
public void openHome(LoginBean bean) {
SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.username), mEt_username.getText().toString());
SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.password), mEt_password.getText().toString());
SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.userId), bean.getResult().getUserId());
SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.token), bean.getResult().getToken());
startActivity(new Intent(getContext(), HomeActivity.class));
finish();
}
}複製代碼
其實上面咱們當中能夠看到咱們前臺界面拿到用戶數據後,調用presenter的doLogin方法,把用戶名和密碼傳遞過去,而後咱們在Presenter中請求網絡而後再經過調用接口實現數據回傳。以下:編程
import com.acheng.achengutils.gsonutil.GsonUtils;
import com.acheng.achengutils.mvp.presenter.BasePresenter;
import com.acheng.achengutils.utils.CipherUtils;
import com.acheng.achengutils.utils.StringUtils;
import com.kymjs.rxvolley.RxVolley;
import com.kymjs.rxvolley.client.HttpCallback;
import com.kymjs.rxvolley.client.HttpParams;
import com.kymjs.rxvolley.http.VolleyError;
import acheng1314.cn.a3dbuild.MyApplication;
import acheng1314.cn.a3dbuild.bean.LoginBean;
import acheng1314.cn.a3dbuild.hostApi.MyApi;
import acheng1314.cn.a3dbuild.view.activity.viewcontroller.LoginActivityViewController;
/** * Created by pc859107393 on 2016/9/12 0012. */
public class LoginActivityPresenter extends BasePresenter<LoginActivityViewController> {
/** * 在子類的構造函數中,設定參數爲model,這時候能夠presenter調用接口來實現對界面的操做。 * * @param model */
public LoginActivityPresenter(LoginActivityViewController model) {
super(model);
}
@Override
public void initData() {
}
public void doLogin(String name, String pwd) {
//用戶名和密碼不能爲空
if (StringUtils.isEmpty(name) || StringUtils.isEmpty(pwd)) {
model.showDailog("用戶名或密碼不能爲空!"); //調用model的錯誤提示對話框
return;
}
//密碼MD5加密
pwd = CipherUtils.small32md5(pwd);
HttpParams params = new HttpParams();
params.put("userName", name);
params.put("passWord", pwd);
RxVolley.post(MyApi.LoginApi, params, new HttpCallback() {
@Override
public void onSuccess(String t) {
super.onSuccess(t);
//數據不爲空再進行數據處理
try {
if (null != t) {
MyApplication.getInstance().outLog("輸出", t);
LoginBean bean = new GsonUtils().toBean(t, LoginBean.class);
if (null != bean) {
if (bean.getCode() == 0) {
//請求成功
model.openHome(bean);
} else if (bean.getCode() == 1) {
model.showDailog("登陸失敗,賬戶不存在");
} else if (bean.getCode() == 2) {
model.showDailog("登陸失敗,密碼錯誤");
} else {
model.showDailog("登陸失敗,其餘未知錯誤");
}
}
}
} catch (Exception e) {
e.printStackTrace();
model.showDailog("登陸失敗,其餘未知錯誤");
}
}
@Override
public void onFailure(VolleyError error) {
super.onFailure(error);
model.showDailog("登陸失敗,其餘未知錯誤");
}
@Override
public void onFinish() {
super.onFinish();
model.disProgressD(); //model的關閉對話框的接口
}
@Override
public void onPreStart() {
super.onPreStart();
model.showProgressD(); //model的進度對話框
}
});
}
}複製代碼
咱們上面能夠看到咱們如今只要把請求網絡的數據傳遞上去就能夠完成單元測試了,這樣子咱們就達到了咱們數據流轉的單元測試的標準。api
既然咱們都看到了Presenter對model的調用,那麼咱們直接貼上model再對比Activity就能明白了咱們是怎麼完成這個設計的。網絡
public interface LoginActivityViewController extends BaseViewController {
/** * 顯示信息提示對話框 * @param msg message */
void showDailog(String msg);
/** * 顯示進度對話框 */
void showProgressD();
/** * 關閉對話框 */
void disProgressD();
/** * 登錄成功跳轉到其餘界面 * @param bean */
void openHome(LoginBean bean);
}複製代碼
咱們看到這裏,不少哥們可能又會不明白,爲何咱們能控制界面呢?以下:架構
//咱們在程序中,presenter直接調用的model,可是model是被View實現了的。
public class LoginActivity extends BaseActivity<LoginActivityPresenter, LoginActivityViewController> implements LoginActivityViewController {
@Override
public void showDailog(String msg) {
//實現了model的顯示對話框的方法
new MustDoThingDailog("提示", msg, getContext(), new MustDoThingDailog.NeedDoThing() {
@Override
public void mustDoThings() {
}
});
}
@Override
public void showProgressD() {
//這是顯示進度對話框的,實現了model的方法
}
@Override
public void disProgressD() {
//這是實現了moel的關閉進度對話框的方法
}
@Override
public void openHome(LoginBean bean) {
//實現了model的打開其餘頁面的方法
}
}複製代碼
因此咱們的MVP執行的步驟其實就是:用戶執行操做 -> 調用presenter(完成獨立的數據處理) -> 調用model的方法控制界面 -> 展現給用戶app
而後應該又有哥們會問我,爲何你的基類中會有<>這種括號括起來的東西,恩恩這個是泛型,主要是用來講明他們是哪一類的東西,經過泛型來解耦就能夠在基類中整合更多的東西。具體的要我來講明的話,我只能說「就不!!!」,我須要任性一回。關於MVP更好的介紹能夠看下github的項目TheMvp,這個是個人偶像@張濤寫的喲。