Anroid MVP是安卓开发中一个经典的话题,当项目较大、参与的开发人员较多,MVP的优势就体现出来了。
系列文章
Android组件化-基础框架搭建
Android组件化-组件间通信BRouter
Android组件化-风格统一&主题变色
Android组件化-MVP设计模式
一、经典的MVP
经典的意思,就是又老又香 ^-^
1.1 一句话MVP
提到Android MVP(Model-View-Presenter)就会想到MVC(Model-View-Controller),C就是Web开发中经常提到的Controller,P则是Android中用来分离Activity逻辑与界面的Presenter。
MVP核心思想:
1 2
| MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
|
1.2 MVP图解
一图胜千言:
- 视图View:Activity和Fragment
- 逻辑Presenter:业务逻辑和业务管理类等
- 模型Model:SharedPreferences、数据库访问(Dao)和网络交互(Api)
二、Modulize使用MVP
Modulize项目使用MVP作为基本的开发框架(以登录为例)。
2.1 Model层的设计
Model层负责数据交互,包括网络交互、本地数据库交互以及SharedPreferences数据存取。在lib-common中添加抽象类BaseModel,LoginModel等业务模块继承自BaseModel。
1 2 3 4
| public abstract class BaseModel {
}
|
网络交互 - okHttp+Retrofit+Rxjava
网络访问使用无话可说的okHttp,结合优雅的Retrofit,加以RxJava,真香!
使用okHttpClient实例管理全局http访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class OkHttp3Util {
private static OkHttpClient mOkHttpClient;
public static OkHttpClient getOkHttpClient() {
if (null == mOkHttpClient) {
mOkHttpClient = new OkHttpClient.Builder() .cookieJar(new CookiesManager()) .addInterceptor(loggingInterceptor) .addInterceptor(new CommonIntercepter()) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .cache(cache) .build(); }
return mOkHttpClient; } }
|
在lib-common中新建ServiceGenerate类管理、创建Retrofit接口访问实例,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class ServiceGenerator {
private static final String API_SERVICE = "http://xxxx:8080/api/";
private static Gson gson = new GsonBuilder() .registerTypeAdapter(java.util.Date.class, new DateDeserializer()).setDateFormat(DateFormat.LONG) .registerTypeAdapter(java.util.Date.class, new DateSerializer()).setDateFormat(DateFormat.LONG) .create();
private static Retrofit apiGenerator = new Retrofit.Builder() .baseUrl(API_SERVICE) .addConverterFactory(CustomConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(OkHttp3Util.getOkHttpClient()) .build(); }
|
为了统一处理Http接口返回,创建Response响应类,应当和后台接口保持一致的gson格式:
1 2 3 4 5 6 7 8
| public class Response<T> {
private int code; private String message; private T data;
... }
|
基于Retrofit的登录Api如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface LoginApi {
@FormUrlEncoded @POST("login") Observable<Response<User>> loginStu(@Field("username") String username, @Field("password") String password); }
|
数据库交互 - GreenDao
使用J神家的的GreenDao,这个移动端ORM框架还是需要好好学习下的,本文仅介绍GrrenDao在MVP中的使用。在lib-db中创建DBHelper用于管理数据库连接和数据访问对象(Dao)实例:
1 2 3 4 5 6 7 8
| public class DBHelper {
... instance init
public <T> AbstractDao getDao(Class<T> clazz) { return session.getDao(clazz); } }
|
SharedPreferences
使用SP存储用户偏好设置或登录认证数据等碎片数据。
LoginModel
Model中持有Retrofit实例(api)、数据库访问对象(Dao)以及SP等本地存储对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class LoginModel extends BaseModel {
private static final String TAG = "LoginModel";
private LoginApi api; private UserDao userDao; private SharedPreferences userPreference;
public LoginModel() { api = ServiceGenerator.createAPIService(LoginApi.class); userDao = (UserDao) DBHelper.getInstance().getDao(User.class); userPreference = context.getSharedPreferences("user", Context.MODE_PRIVATE); }
public void setUser(User user) { userPreference.put("user", user.getName()); userDao.insert(user); }
public void login(String username, String password, Observer<Response<User>> observer) { rxSubscribe(api.login(username, password), observer); } }
|
Presenter调用LoginModel方法时传递接口参数和Observer,LoginModel接口请求响应后回调Observer,rxSubscribe()定义在BaseModel中:
1 2 3 4 5 6 7 8
| public abstract class BaseModel { protected static <T> void rxSubscribe(Observable<T> observable, Observer<T> observer) { observable.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(observer); } }
|
2.2 Presenter层的设计
Presenter持有Model实例,Presenter初始化时实例化Model,在lib-common中加入BasePresenter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class BasePresenter<TView extends BaseView, TModel extends BaseModel> {
protected TView mView; protected TModel mModel;
public BasePresenter(TView view) { this.mView = view; this.mModel = this.getModel(); }
protected abstract TModel getModel();
public void detach() { this.mView = null; } }
|
LoginPresenter集成BasePresenter,实例化LoginModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class LoginPresenter extends BasePresenter<BaseActivity, LoginModel> {
public LoginPresenter(BaseActivity activity) { super(activity); }
@Override protected LoginModel getModel() { return new LoginModel(); }
public void login(String username, String password) { mView.loadHud(); mModel.loginStudent(username, password, new Observer<Response<User>>() { @Override public void onCompleted() { }
@Override public void onError(Throwable e) { e.printStackTrace(); }
@Override public void onNext(Response<User> response) { mView.cancelHud();
if (response.OK()) { mView.onViewEvent(BaseView.VIEW_LOADED, response.getData()); mModel.setUser(user); } else { mView.onViewEvent(LoginActivity.ERROR, null); } } }); } }
|
本项目在MVP中未使用接口的方式,在View中实现接口,在Presenter中持有实例并进行接口调用,因为使用接口则每个页面都需要新建一个接口类,较为繁琐。
本项目MVP使用BaseView中的抽象方法onViewEvent(),每个View继承BaseView后实现onViewEvent(int code, Object param),Presenter层Attach BaseView后通过mView.onViewEvent()对View进行界面回调处理,View中根据事件code和参数param进行视图处理。
一个Presenter可持有多个Model,定义多个Model对象并在Presenter构造函数中初始化。
2.3 View层的设计
在lib-common中定义BaseView,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void toast(@StringRes int resId);
void toast(String res);
<T extends View> T findViewById(@IdRes int resId);
void onViewEvent(int code, Object param);
void onViewState(int state); void onViewState(Response response);
void loadHud(); void cancelHud();
}
|
- toast():Toast封装,用于在Activity、Fragment或Presenter中弹出用户提示
- findViewById():主要用于fragment中获取元素使用(组件化开发使用ButterKnife较为繁琐,不建议使用)
- onViewEvent():View层的回调,用于Presenter网络请求响应后通知View层
- onViewState():View层的回调。当Presenter层发生错误时统一处理View(网络异常、Http请求错误等)
- loadHud()/cancelHud():加载ProgressDialog,Presenter发请网络请求时、请求结束后,在Presenter层弹出ProgressDialog
BaseActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| public abstract class BaseActivity<TPresenter extends BasePresenter> extends AppCompatActivity implements BaseView {
protected Handler mUIHandler;
protected TPresenter mPresenter;
protected KProgressHUD mHud; @LayoutRes protected abstract int getLayoutResId();
protected abstract void initViewAndData(@Nullable Bundle savedInstanceState);
protected abstract TPresenter getPresenter();
@Override public void onCreate(@Nullable Bundle savedInstanceState) { beforeCreate(); super.onCreate(savedInstanceState); beforeSetContentView(); setContentView(this.getLayoutResId()); this.init(); this.initViewAndData(savedInstanceState);
EventBus.getDefault().register(this); }
private void beforeSetContentView() { requestWindowFeature(Window.FEATURE_NO_TITLE); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); }
private void beforeCreate() { setTheme(UIConfig.getInstance(getApplicationContext()).getThemeId()); }
@Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(CommonEvent event) { BLog.e("[Event]: " + event.code); if (event.code == CommonEvent.Type.NETWORK_ERROR) { onViewState(UIConstants.ViewState.NETWORK_DISCONNECTED); if (this.mCommonEvent != null) { this.mCommonEvent.onCommonEvent(event.code, event.param); } cancelHud(); } }
private void init() { ...
this.mPresenter = this.getPresenter(); this.mHud = KProgressHUD.create(this); }
@Override public void onViewState(int state) { ... }
@Override public void onViewState(Response response) { ... }
@Override protected void onDestroy() { super.onDestroy();
this.mUIHandler = null; EventBus.getDefault().unregister(this); }
@Override public <T extends View> T findViewById(int resId) { return super.findViewById(resId); }
@Override public void toast(@StringRes final int resId) { if (mUIHandler == null) { return; } mUIHandler.post(new Runnable() { @Override public void run() { Toast.makeText( getApplicationContext(), resId, Toast.LENGTH_SHORT).show(); } }); }
@Override public void toast(final String res) { ... }
@Override public void loadHud(int resId) { if (mHud == null) { mHud = KProgressHUD.create(this); } mHud.setStyle(KProgressHUD.Style.SPIN_INDETERMINATE) .setCancellable(true) .setLabel(resId == 0 ? getString(R.string.opt_loading) : getString(resId)) .setAnimationSpeed(1) .setDimAmount(0.5f) .show(); }
@Override public void cancelHud() { if (mHud != null) { mHud.dismiss(); } } }
|
BaseFragment
类似BaseActivity,加入一些对宿主Activity的回调。
参考https://github.com/blackist/modulize/blob/8478eb2a4bdaf7b9f9e2022be0e9462ea82b3eeb/lib-common/src/main/java/org/blackist/common/base/BaseFragment.java
LoginActivity
LoginActivity继承自BaseActivity,实例化LoginPresenter,实现onViewEvent()回调函数:
public class LoginActivity extends BaseActivity<LoginPresenter> implements View.OnClickListener {
private static final String TAG = "LoginActivity";
public static final int ERROR = 1000;
@Override
protected int getLayoutResId() {
return R.layout.main_login_activity;
}
@Override
protected void initViewAndData(@Nullable Bundle savedInstanceState) {
initView();
...
}
@Override
protected LoginPresenter getPresenter() {
return new LoginPresenter(this);
}
@Override
public void onClick(View v) {
...
}
@Override
public void onViewEvent(int code, Object param) {
switch (code) {
case VIEW_LOADED: {
// 登录成功处理
...
startActivity(new Intent(this, MainActivity.class));
finish();
}
break;
case ERROR: {
toast(R.string.main_login_error);
}
break;
default:
}
}
}
通常情况下一个View对应一个Presenter,也可在View中定义多个Presenter对象并在initViewAndData()中初始化
至此,实现了精简版的Android MVP,本人用在项目开发中问题不大。
参考
https://segmentfault.com/a/1190000003927200
https://juejin.im/post/5a61559051882573351a5fb6
(完)