专栏首页董亮亮的开发笔记Android组件化-经典MVP

Android组件化-经典MVP

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

(完)

本文分享自微信公众号 - 董亮亮的开发笔记(liangl_dong),作者:dongliangliang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Docker入门及自定义容器

    Linux Containers Linux容器 缩写LXC,其对进程进行隔离,而非一个完整的操作系统。Docker属于LXC的一种封装,提供简单易用的容器使用...

    用户2987604
  • 依赖注入

    讲真的,今年就业形势相当不好,对IT从业者的专业能力要求将变得更高。近来一边忙学业,一边学习Java基础和框架源码,为不久之后的招聘做准备。

    用户2987604
  • 算法 - PNPoly解决点和多边形问题

    计算点到多边形最短距离的基本原理是:依次计算点到多边形每条边的距离,然后筛选出最短距离。

    用户2987604
  • 用最简单的例子说明设计模式(三)之责任链、建造者、适配器、代理模式、享元模式

    六月的雨
  • 【设计模式】—— 访问者模式Visitor

      模式意图   对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同。此时,就是访问者模式的典型应用了。   应用场景   1 不同的子类...

    用户1154259
  • 使用saripaar对android输入控件进行快速验证

    saripaar是个android的第三方快速校验,使用注解快速添加验证规则。 public class LoginActivity extends Activ...

    kklldog
  • Spring中基于注解@AspectJ的AOP实现

    @AspectJ 作为通过 Java 5 注释注释的普通的 Java 类,它指的是声明aspects 的一种风格。通过在基于架构的 XML 配置文件中包含以下元...

    Jerry Wang
  • Java-匿名子类(匿名内部类)

    版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons)

    Fisherman渔夫
  • zephir-(9)类和对象2

    #zephir-(9)类和对象2# ? ##前言## 先在这里感谢各位zephir开源技术提供者 zephir全面使用对象编程,这就是为什么拓展的使用方式只能是...

    喵了个咪233
  • Condition控制线程通信:java三个线程循环打印ABC

    private Lock lock = new ReentrantLock(); private Condition c1 = lock.newConditi...

    用户1215919

扫码关注云+社区

领取腾讯云代金券