Thanks to visit codestin.com
Credit goes to github.com

Skip to content
L.L DONG edited this page Jun 27, 2019 · 1 revision

一、经典的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图解

一图胜千言:

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

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,本人用在项目开发中问题不大。

Clone this wiki locally