-
Notifications
You must be signed in to change notification settings - Fork 2
MVP
经典的意思,就是又老又香 ^-^
提到Android MVP(Model-View-Presenter)就会想到MVC(Model-View-Controller),C就是Web开发中经常提到的Controller,P则是Android中用来分离Activity逻辑与界面的Presenter。
MVP核心思想:
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
一图胜千言:
- 视图View:Activity和Fragment
- 逻辑Presenter:业务逻辑和业务管理类等
- 模型Model:SharedPreferences、数据库访问(Dao)和网络交互(Api)
Modulize项目使用MVP作为基本的开发框架(以登录为例)。
Model层负责数据交互,包括网络交互、本地数据库交互以及SharedPreferences数据存取。在lib-common中添加抽象类BaseModel,LoginModel等业务模块继承自BaseModel。
public abstract class BaseModel {
}网络访问使用无话可说的okHttp,结合优雅的Retrofit,加以RxJava,真香!
使用okHttpClient实例管理全局http访问:
public class OkHttp3Util {
private static OkHttpClient mOkHttpClient;
/**
* 获取OkHttpClient对象实例
*
* @return
*/
public static OkHttpClient getOkHttpClient() {
if (null == mOkHttpClient) {
// build design mode
mOkHttpClient = new OkHttpClient.Builder()
// cookie manager
.cookieJar(new CookiesManager())
// 网络请求日志
.addInterceptor(loggingInterceptor)
// 自定义拦截器
.addInterceptor(new CommonIntercepter())
// set timeout of connection, reading and writing
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(cache)
.build();
}
return mOkHttpClient;
}
}
在lib-common中新建ServiceGenerate类管理、创建Retrofit接口访问实例,
public class ServiceGenerator {
private static final String API_SERVICE = "http://xxxx:8080/api/";
/**
* 在gson中加入时间格式化,DateDeserializer\DateSerializer为自定义转换类.
*/
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();
/**
* API Retrofit.
*/
private static Retrofit apiGenerator = new Retrofit.Builder()
.baseUrl(API_SERVICE)
// 自定义转换器一定要在gsonConverter前面,否则gson会拦截所有的解析方式
.addConverterFactory(CustomConverterFactory.create())
// Gson Converter
.addConverterFactory(GsonConverterFactory.create(gson))
// Callback Handler RxJava
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(OkHttp3Util.getOkHttpClient())
.build();
}为了统一处理Http接口返回,创建Response响应类,应当和后台接口保持一致的gson格式:
public class Response<T> {
private int code;
private String message;
private T data;
...
}基于Retrofit的登录Api如下:
public interface LoginApi {
/**
* user login
*
* @param username username
* @param password password
* @return user info
*/
@FormUrlEncoded
@POST("login")
Observable<Response<User>> loginStu(@Field("username") String username, @Field("password") String password);
}
使用J神家的的GreenDao,这个移动端ORM框架还是需要好好学习下的,本文仅介绍GrrenDao在MVP中的使用。在lib-db中创建DBHelper用于管理数据库连接和数据访问对象(Dao)实例:
public class DBHelper {
... instance init
public <T> AbstractDao getDao(Class<T> clazz) {
return session.getDao(clazz);
}
}
使用SP存储用户偏好设置或登录认证数据等碎片数据。
Model中持有Retrofit实例(api)、数据库访问对象(Dao)以及SP等本地存储对象:
public class LoginModel extends BaseModel {
private static final String TAG = "LoginModel";
private LoginApi api;
private UserDao userDao;
private SharedPreferences userPreference;
public LoginModel() {
// 使用ServiceGenerator生成api访问类
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中:
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);
}
}Presenter持有Model实例,Presenter初始化时实例化Model,在lib-common中加入BasePresenter:
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:
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()) {
// 请求成功 回调VIew层进行页面刷新
mView.onViewEvent(BaseView.VIEW_LOADED, response.getData());
// 把用户信息保存在本地
mModel.setUser(user);
} else {
// 请求失败 回调View层报错
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构造函数中初始化。
在lib-common中定义BaseView,
void toast(@StringRes int resId);
/**
* 用于Presenter中吐司提示
*/
void toast(String res);
<T extends View> T findViewById(@IdRes int resId);
/**
* 用于Presenter回调界面操作
*/
void onViewEvent(int code, Object param);
/**
* 在界面中统一处理数据、网络异常
*/
void onViewState(int state);
void onViewState(Response response);
/**
* 加载、取消Dialog
*/
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
public abstract class BaseActivity<TPresenter extends BasePresenter> extends AppCompatActivity implements BaseView {
protected Handler mUIHandler;
protected TPresenter mPresenter;
protected KProgressHUD mHud;
// 获取界面layout资源文件
@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());
// init
this.init();
this.initViewAndData(savedInstanceState);
// EventBus
EventBus.getDefault().register(this);
}
/**
* before set contentView
*/
private void beforeSetContentView() {
// NoTitle
requestWindowFeature(Window.FEATURE_NO_TITLE);
// ScreenPortrait
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
/**
* before create
*/
private void beforeCreate() {
// 统一设置主题
setTheme(UIConfig.getInstance(getApplicationContext()).getThemeId());
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(CommonEvent event) {
// EventBus统一处理全局异常
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);
}
// cancel loading hud
cancelHud();
}
}
/**
* init view, e.g commonTitleBar.
*/
private void init() {
// view init
...
// 从子类拿到Presenter实例
this.mPresenter = this.getPresenter();
// 使用第三方库作为Loading Dialog
this.mHud = KProgressHUD.create(this);
}
@Override
public void onViewState(int state) {
// 全局异常处理
...
}
@Override
public void onViewState(Response response) {
// 根据Response处理服务器http响应
...
}
@Override
protected void onDestroy() {
super.onDestroy();
this.mUIHandler = null;
// Unregister EventBus
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) {
// 加载等待Dialog
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();
}
}
}
- loadHud():参考第三方库https://github.com/Kaopiz/KProgressHUD
类似BaseActivity,加入一些对宿主Activity的回调。
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,本人用在项目开发中问题不大。
@ 2018 L.L Dong
