前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android组件化-经典MVP

Android组件化-经典MVP

作者头像
用户2987604
发布2020-06-15 16:05:19
9130
发布2020-06-15 16:05:19
举报

Android 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核心思想:

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。

public abstract class BaseModel {
}
网络交互 - okHttp+Retrofit+Rxjava

网络访问使用无话可说的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);}
数据库交互 - GreenDao

使用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);    }}
SharedPreferences

使用SP存储用户偏好设置或登录认证数据等碎片数据。

LoginModel

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);    }}

2.2 Presenter层的设计

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构造函数中初始化。

2.3 View层的设计

在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
BaseActivity
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
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

(完)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 董亮亮的开发笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、经典的MVP
    • 1.1 一句话MVP
      • 1.2 MVP图解
      • 二、Modulize使用MVP
        • 2.1 Model层的设计
          • 网络交互 - okHttp+Retrofit+Rxjava
          • 数据库交互 - GreenDao
          • SharedPreferences
          • LoginModel
        • 2.2 Presenter层的设计
          • 2.3 View层的设计
            • BaseActivity
            • BaseFragment
            • LoginActivity
        • 参考
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档