前言
目前 MVP 架构在 Android app 开发中非常流行。通过 谷歌官方 例子和个人的一些理解形成自己的 Retrofit + RxJava 的 MVP 架构,并且用这实际开发当中。看此文需要一定的 Retrofit 和 RxJava 基础,对 mvp-clean 有一定了解。
MVP 简单介绍
- M:Model,数据处理层,获取数据、整理数据等;
- V:View,展示数据层,
Activity
、Fragment
、Dialog
等都可以充当这个角色; - P:Presenter,逻辑处理层,处理数据和 UI 的关系、处理来自 V 层逻辑等;
下图分析:
V 层和 M 层直接关联了吗?并没有,这是 MVP 和 MVC 一个很大的区别。V 层和 M 层是通过 P 层联系起来的。反映到代码中就是 P 持有 V 实例和 M 实例。
具体实现
1. 总体包目录
- base 包:一些 base 基类,
BaseFragment
和BaseMvpFragment
和BaseActivity
、BaseMvpFragment
差不多。 - net 包:网络请求和处理相关的类在此包下。
- utils 包:需要用到的简单工具类。
2. 关键类的说明
- BaseMvpActivity:封装了公共方法的
Activity
,用了处理项目里Activity
一般都会使用到的方法,例如配合 Presenter 统一处理内存泄漏问题。注意:BaseMvpActivity
Presenter 泛型是主 Presenter,其他的为次 Presenter,需要在getPresenter()
中初始化,并且添加到 Presenter 集合中。
public abstract class BaseMvpActivity<P extends BasePresenter<? extends IBaseView>> extends BaseActivity implements IBaseView {
//主Presenter
protected P mPresenter;
//多个Presenter时候需要的容器
private ArraySet<BasePresenter> mPresenters = new ArraySet<>(4);
@Override
protected void init(@Nullable Bundle savedInstanceState) {
initLoading();
mPresenter = getPresenter();
addToPresenters(mPresenter);
initView();
initListener();
initData();
}
@Override
protected void onDestroy() {
for (BasePresenter presenter : mPresenters) {
presenter.detachView();
}
mPresenters.clear();
super.onDestroy();
}
@Override
public void showLoading() {
}
@Override
public void showLoading(String msg) {
}
@Override
public void hideLoading() {
}
@Override
public void showMsg(String msg) {
toastS(msg);
}
/**
* 初始化Presenter,其他多个Presenter也在该方法创建并调用addToPresenters加入到集合
* @return 主Presenter
*/
protected abstract P getPresenter();
/**
* 根据具体项目需求创建loading
*/
protected void initLoading() {
}
/**
* 初始化View
*/
protected void initView(){
}
/**
* 初始化Listener
*/
protected abstract void initListener();
/**
* 初始化数据
*/
protected abstract void initData();
/**
* 把其他的Presenter添加到Presenters集合里
* 这样会自动绑定View和管理内存释放
*/
protected <T extends BasePresenter> void addToPresenters(T presenter) {
presenter.attachView(this);
mPresenters.add(presenter);
}
}
- BasePresenter:Presenter 基类,里面包含 Presenter 需要公共方法,例如统一处理内存泄漏问题。Presenter 我坚决不把
Context
(Activity
、Fragment
之类的)传进来,要使用Context
就用Application
的Context
。带个Context
可能有些方便,但是我觉得很影响我的单元测试。
public abstract class BasePresenter<V extends IBaseView> {
private V mView;
//Disposable容器,收集Disposable,主要用于内存泄漏管理
private CompositeDisposable mDisposables;
protected V getView() {
return mView;
}
/**
* @param view 绑定View
*/
@SuppressWarnings("unchecked")
public <T extends IBaseView> void attachView(T view) {
this.mView = (V) view;
mDisposables = new CompositeDisposable();
}
/**
* 解绑关联
*/
public void detachView() {
mDisposables.clear();
mDisposables = null;
mView = null;
}
/**
* @param disposable 添加Disposable到CompositeDisposable
* 通过解除disposable处理内存泄漏问题
*/
protected boolean addDisposable(Disposable disposable) {
if (isNullOrDisposed(disposable)) {
return false;
}
return mDisposables.add(disposable);
}
/**
* @param d 判断d是否为空或者dispose
* @return true:一次任务未开始或者已结束
*/
protected boolean isNullOrDisposed(Disposable d) {
return d == null || d.isDisposed();
}
/**
* @param d 判断d是否dispose
* @return true:一次任务还未结束
*/
protected boolean isNotDisposed(Disposable d) {
return d != null && !d.isDisposed();
}
/**
* 获取 Model 实例
*/
protected <M extends IBaseModel> M getModel(Class<M> clazz) {
return ModelManager.getInstance().create(clazz);
}
}
- HttpManager:网络访问管理,例如 baseUrl 配置;
public final class HttpManager {
private Retrofit mRetrofit;
private String mBaseUrl;
private OkHttpClient mOkHttpClient;
private Boolean debug = true;
private static final Logger LOG = Logger.getLogger(HttpManager.class.getName());
private HttpManager() {
}
public static HttpManager getInstance() {
return Holder.INSTANCE;
}
/**
* @param mBaseUrl 设置BaseUrl
* 放在第一位设置
*/
public HttpManager setBaseUrl(String mBaseUrl) {
this.mBaseUrl = mBaseUrl;
return Holder.INSTANCE;
}
/**
* 设置OkHttpClient
*/
public HttpManager setOkHttpClient(OkHttpClient okHttpClient) {
this.mOkHttpClient = okHttpClient;
return Holder.INSTANCE;
}
/**
* @param retrofit 设置retrofit
* 放在最后设置
*/
public void setRetrofit(Retrofit retrofit) {
this.mRetrofit = retrofit;
}
/**
* debug
*/
public HttpManager setDebug(Boolean debug) {
this.debug = debug;
return Holder.INSTANCE;
}
/**
* @return mRetrofit.create(clazz)
*/
public <T> T getApiService(Class<T> clazz) {
return mRetrofit.create(clazz);
}
/**
* 自带创建retrofit
*/
public Retrofit createRetrofit() {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(mBaseUrl)
.client(mOkHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(ObserveOnMainCallAdapterFactory.createMainScheduler())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()));
return builder.build();
}
/**
* @return OkHttpclient
*/
public OkHttpClient createDefaultClient() {
...
}
private static class Holder {
private static final HttpManager INSTANCE = new HttpManager();
}
/**
* info 等级log
*/
public static class InterceptorLogInfo implements HttpLoggingInterceptor.Logger {
@Override
public void log(@NonNull String message) {
LOG.log(Level.INFO, message);
}
}
}
- HttpResultObserver:
DisposableSingleObserver
的子类,用来接收网络数据回调;
public abstract class HttpResultObserver<T> extends DisposableSingleObserver<T> {
@Override
public void onSuccess(T t) {
//dispose 一次任务
dispose();
onResult(t);
}
@Override
public void onError(Throwable e) {
//dispose 一次任务
dispose();
onFailure(e);
}
/**
* @param t 获取结果
*/
protected abstract void onResult(T t);
/**
* @param e 获取结果失败
*/
protected abstract void onFailure(Throwable e);
}
- ObserveOnMainCallAdapterFactory:指定数据回调到主线程。配合 Retrofit 使用的。
public final class ObserveOnMainCallAdapterFactory extends CallAdapter.Factory{
private final Scheduler mScheduler;
public ObserveOnMainCallAdapterFactory(Scheduler scheduler) {
this.mScheduler = scheduler;
}
@Nullable
@Override
public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations, @NonNull Retrofit retrofit) {
Class<?> rawType = getRawType(returnType);
if (rawType != Single.class) {
return null;
}
final CallAdapter<Object, Single<?>> delegate =
(CallAdapter<Object, Single<?>>) retrofit.nextCallAdapter(this, returnType, annotations);
return new CallAdapter<Object, Object>() {
@Override
public Type responseType() {
return delegate.responseType();
}
@Override
public Object adapt(@NonNull Call<Object> call) {
Single<?> s = delegate.adapt(call);
return s.observeOn(mScheduler);
}
};
}
/**
* 在android主线程处理下游数据
*/
public static CallAdapter.Factory createMainScheduler() {
return new ObserveOnMainCallAdapterFactory(AndroidSchedulers.mainThread());
}
}
- ModelManager:创建管理 Model,这里我把 Model 看做是数据仓库,用来提供直观数据(比如提供 User 数据)的。Model 是通过 ModelManager 创建的。每个 Model 只创建一次。因为 Model 相对比较独立而且不需要频繁创建销毁。
public final class ModelManager {
private final ConcurrentHashMap<Class<? extends IBaseModel>, ? extends IBaseModel> DATA_CACHE;
private ModelManager() {
DATA_CACHE = new ConcurrentHashMap<>(8);
}
/**
* @return ModelManager单例实例
*/
public static ModelManager getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final ModelManager INSTANCE = new ModelManager();
}
/**
* 创建获取 Model 层实例
* @param clazz IBaseModel 子类 class
*/
@SuppressWarnings("unchecked")
public <M extends IBaseModel> M create(final Class<M> clazz) {
IBaseModel model = DATA_CACHE.get(clazz);
if (model != null) {
return (M) model;
}
synchronized (DATA_CACHE) {
model = DATA_CACHE.get(clazz);
if (model == null) {
try {
Constructor<M> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
model = constructor.newInstance();
} catch (... e) {
....
}
}
}
return (M) model;
}
}
更多代码细节可以去 我的GitHub 看下。
3. 使用方式
以在一个 Activity 请求 GitHub 两个接口为例,一个接口请求个人用户信息,一个接口请求公司用户信息,所以例子将模拟两个模块,user 和 orgs 。使用过程中你会发现一个View 可以有多个 Presenter,一个 Activity 可以实现多个 View 接口。其实还不止这些,其实 Presenter 也可以用有多个 Model。
完整例子
- 目录结构
XxContract
为模板创建生成,可以在Setting > Editor > File and Code Templates > Files
设置。我的模板如下:
package ${PACKAGE_NAME};
#parse("File Header.java")
public interface ${NAME}{
interface View extends IBaseView{
}
interface Presenter{
}
}
目录结构
- 在合适的位置初始化 HttpManager
public class SimpleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HttpManager.getInstance()
.setBaseUrl("https://api.github.com/")
.setDebug(BuildConfig.DEBUG)
.setOkHttpClient(HttpManager.getInstance().createDefaultClient())
.setRetrofit(HttpManager.getInstance().createRetrofit());
}
}
- MainActivity: 一般主页可能会有多个 Presenter,这里有两个 Presenter
public class MainActivity extends BaseMvpActivity<UserPresenter> implements UserContract.View, OrgContract.View {
...
//公司信息Presenter(次Presenter)
private OrgPresenter mOrgPresenter;
private ProgressDialog mLoading;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initLoading() {
mLoading = new ProgressDialog(this);
}
@Override
protected UserPresenter getPresenter() {
mOrgPresenter = new OrgPresenter();
addToPresenters(mOrgPresenter);
return new UserPresenter();
}
@Override
protected void initView() {
...
}
@Override
protected void initListener() {
//点击获取个人信息
btnClick.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View v) {
mPresenter.getUser("togallop");
}
});
//点击获取用户信息
btnClick2.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View v) {
mOrgPresenter.getOrg("google");
}
});
}
@Override
public void showUser(String msg) {
tvMsg.setText(msg);
}
@Override
public void showOrg(String org) {
tvMsg.setText(org);
}
@Override
public void showLoading(String msg) {
mLoading.setMessage(msg);
if (!mLoading.isShowing()) {
mLoading.show();
}
}
@Override
public void showLoading() {
if (!mLoading.isShowing()) {
mLoading.show();
}
}
@Override
public void showMsg(String msg) {
toastS(msg);
}
@Override
public void hideLoading() {
if (mLoading.isShowing()) {
mLoading.dismiss();
}
}
}
- UserPresenter:联结
UserContract.View
和UserMode
l,在 Presenter 里,addDisposable(disposable)
主要作用是用来防止内存泄漏的,如果网络请求还没结束,页面已经关闭,则可能造成内存泄漏,通过addDisposable(disposable)
管理,可以切断 P 和 V 的关联,从而防止内存泄漏,也可以取消网络请求。
public class UserPresenter extends BasePresenter<UserContract.View> implements UserContract.Presenter {
private UserModel mModel;
public UserPresenter() {
mModel = getModel(UserModel.class);
}
@Override
public void getUser(String userName) {
//做一些判断
if (TextUtils.isEmpty(userName)) {
getView().showMsg("用户名不能为空");
return;
}
//显示loading
getView().showLoading("正在加载...");
Disposable disposable = mModel.getUser(userName, new HttpResultObserver<String>() {
@Override
protected void onResult(String s) {
//结果回调显示
getView().showUser(s);
getView().hideLoading();
}
@Override
protected void onFailure(Throwable e) {
//获取数据是失败回调处理
getView().showMsg(e.getMessage());
getView().hideLoading();
}
});
addDisposable(disposable);
}
}
- UserMode: 就是一个用户相关的模块,和用户相关的请求都可以写在这里
public class UserModel extends BaseModel {
public Disposable getUser(String userName, HttpResultObserver<String> observer) {
return getApiService().getUser(userName).subscribeWith(observer);
}
}
- ApiService: 接口管理
public interface ApiService {
@GET("users/{username}")
Single<String> getUser(@Path("username") String userName);
@GET("orgs/{org}")
Single<String> getOrg(@Path("org") String org);
}
对于有后台有固定返回格式的的数据,也可以统一处理。比如返回结果类似这样的:
{
code:200
msg:"success"
data:{}
}
那么可以这么处理,创建回调泛型类
public abstract class HttpResultObserver2<T> extends HttpResultObserver<Result<T>> {
@Override
public void onSuccess(Result<T> t) {
switch (t.code) {
case 200:
onSuccess(t.data);
break;
default: {
HttpResultException e = new HttpResultException(t.code, t.msg);
toast(e.getMsg());
onFailure(HttpError.RESULT_ERROR, e);
e.printStackTrace();
break;
}
}
}
@Override
public void onError(Throwable t) {
onFailure(error, (Exception) t);
}
/**
* 请求成功回调
*
* @param result 回调数据
*/
public abstract void onResult(T result);
/**
* 请求失败回调
*
* @param error,自定义异常
* @param e 失败异常信息
*/
public abstract void onFailure(HttpError error, @NonNull Exception e);
}
APIService:Single 泛型类型为 <Result<xxx>>
Single<Result<String>> uploadImage(...)
使用方式和上面介绍的一样。
总结
MVP 架构是死的,具体实现是活的;任何离开具体业务的代码都是不现实的,我的这部分代码只是一个参考,可以根据具体需求在我的这份代码上扩展,实现自己的需求。举些例子:要统一处理异常信息,可以继承 HttpResultObserver
统一处理异常;处理内存泄漏也可以有很多方式,RxLifecycle/Reference/LifeCycle
等;不一定要用 Single
,用 Flowable/Maybe
等都行,看具体需要,网络请求是一次性的,所以我用 Single
,处理也方便。总而言之,最好的不一定适合你,适合你的才是最好的。
网友评论