Android MVP+RxJava+Retrofit框架设计

Android MVP+RxJava+Retrofit框架设计


一、背景

MVP介绍:由于MVC模式功能划分不够明确,容易造成Activity、Fragment既有View的功能,又有controller的功能,所有的逻辑都放在了Activity、Fragment中,代码冗长,不便阅读。在这个基础上MVP架构做了一定的优化,Activity、Fragment、xml布局文件单纯的负责UI展示,Model层负责网络请求,Presenter负责处理网络请求后的数据。降低了耦合度,代码更加容易维护。

功能调用
UI更新
数据请求
数据
View
Presenter
Model

二、框架设计思路

1.创建一个lib_common的Libary模块,将框架封装在这个module中以供其他组件模块使用。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.修改app下的gradle文件导入lib_common

在这里插入图片描述

3.框架封装

View层的封装
BaseView.java将一些页面公用的UI操作写在接口中

public interface BaseView {
    /**
     * 显示dialog默认展示type 0
     *
     * @param message 消息内容
     */
    default void showDialog(String message) {
        showDialog(message, 0);
    }

    /**
     * 显示dialog
     *
     * @param message 消息内容
     * @param type    弹框类型
     */
    void showDialog(String message, int type);

    /**
     * 显示loading页面
     */
    default void showLoading(){
        showLoading(null);
    }

    /**
     * 显示loading页面
     *
     * @param message 加载信息
     */
    void showLoading(String message);

    /**
     * 隐藏loading页面
     */
    void hideLoading();

    /**
     * 正常展示页面
     */
    void showNormal();

    /**
     * 空页面
     */
    void showEmpty();

    /**
     * 出现错误默认展示type 0
     *
     * @param message
     */
    default void showError(String message) {
        showError(message, 0);
    }

    /**
     * 出现错误
     *
     * @param message
     * @param type
     */
    void showError(String message, int type);

    /**
     * 信息Toast提示
     *
     * @param message 提示信息
     */
    void showMsg(String message);

    /**
     * 跳转到登录界面
     */
    void gotoLoginActivity();
}

ILifeProcessor.java用于将Activity、Fragment生命周期中的一些操作进行规范、流程处理

public interface ILifeProcessor {

    /**
     * 初始化一些布局参数
     */
    void initUIParams();

    /**
     * 初始化状态栏
     */
    void initStatusBar();

    /**
     * 初始化获取Intent中的数据
     */
    void initIntent(Intent intent);

    /**
     * 数据恢复
     */
    void initSaveInstanceState(Bundle savedInstanceState);

    /**
     * 布局id
     * @return layout id
     */
    int generateIdLayout();

    /**
     * 布局view
     * @return layout view
     */
    View generateViewLayout();

    /**
     * 初始化Views
     */
    void initView();

    /**
     * 初始化Listener
     */
    void initListener();

    /**
     * 初始化数据
     */
    void initData();

    /**
     * 释放资源
     */
    void releaseCache();
}

BaseActivity.java

public abstract class BaseActivity<V extends BaseView, T extends BasePresenter<V>> extends AppCompatActivity implements View.OnClickListener, ILifeProcessor, BaseView {

    public T mPresenter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initUIParams();
        initIntent(getIntent());
        initSaveInstanceState(savedInstanceState);
        if (generateIdLayout() > 0) {
            setContentView(generateIdLayout());
        } else if (generateViewLayout() != null) {
            setContentView(generateViewLayout());
        }
        mPresenter = createPresenter();
        mPresenter.attachView((V) this, this);
        initView();
        initListener();
        initData();
    }

    @Override
    protected void onResume() {
        super.onResume();
        initStatusBar();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseCache();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }

    protected abstract T createPresenter();

    @Override
    public void initUIParams() {

    }

    @Override
    public void initIntent(Intent intent) {

    }

    @Override
    public void initSaveInstanceState(Bundle savedInstanceState) {

    }

    @Override
    public View generateViewLayout() {
        return null;
    }

    @Override
    public void releaseCache() {

    }
}

BaseFragment.java 数据懒加载

public abstract class BaseFragment<V extends BaseView, T extends BasePresenter<V>> extends Fragment implements View.OnClickListener, BaseView {

    protected View mRootView;
    public Context mContext;
    /**
     * 当前fragment是否是可见状态
     */
    protected boolean isVisible;
    protected boolean isPrepared;
    /**
     * 是否已加载过数据
     */
    protected boolean isLoad = false;
    public T mPresenter;


    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (getUserVisibleHint()) {
            isVisible = true;
            lazyLoad();
        } else {
            isVisible = false;
            onInvisible();
        }
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getActivity();
        mPresenter = createPresenter();
        mPresenter.attachView((V) this, mContext);
        setHasOptionsMenu(true);
    }

    protected abstract T createPresenter();


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        if (mRootView == null) {
            mRootView = initView(inflater, container);
        }
        initListener();
        return mRootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isPrepared = true;
        lazyLoad();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
        releaseCache();
    }

    protected void lazyLoad() {
        if (!isPrepared || !isVisible) {
            return;
        }
        onVisible();
        if (!isLoad) {
            initData();
            isLoad = true;
        }
    }

    /**
     * 当fragment切换变成可见状态时可以在这个方法中进行一些你想要进行的操作
     * 该方法每次切换都会调用切记进行数据加载操作,数据加载操作最好放在initData()方法中进行
     */
    protected void onVisible() {

    }

    /**
     * fragment切换变成不可见状态时可以在这个方法中进行一些你想要进行的操作
     */
    protected void onInvisible() {

    }

    /**
     * 布局导入及控件初始化
     *
     * @param inflater
     * @param container
     * @return
     */
    public abstract View initView(LayoutInflater inflater, ViewGroup container);

    /**
     * 初始化控件监听事件
     */
    public abstract void initListener();

    /**
     * 初始化数据
     */
    public abstract void initData();

    public void releaseCache() {

    }

    @Override
    public void showMsg(String message) {

    }

    @Override
    public void showDialog(String message) {

    }

    @Override
    public void hideLoading() {

    }

    @Override
    public void showLoading() {
        showLoading(null);
    }

    @Override
    public void showLoading(String message) {

    }

    @Override
    public void showNormal() {

    }

    @Override
    public void showEmpty() {

    }

    @Override
    public void showDialog(String message, int type) {

    }

    @Override
    public void gotoLoginActivity() {

    }
}

Presenter的封装

BasePresenter.java

public class BasePresenter<V extends BaseView> {

    protected WeakReference<V> mView;
    protected WeakReference<Context> mContext;

    private CompositeDisposable compositeDisposable;

    /**
     * disposable管理,防止RxJava引起内存泄漏
     *
     * @param disposable
     */
    protected void addDisposable(Disposable disposable) {
        if (compositeDisposable == null) {
            compositeDisposable = new CompositeDisposable();
        }
        compositeDisposable.add(disposable);
    }

    /**
     * view,context绑定
     *
     * @param view
     * @param context
     */
    public void attachView(V view, Context context) {
        this.mView = new WeakReference<V>(view);
        this.mContext = new WeakReference<Context>(context);
    }

    /**
     * view,context,compositeDisposable解绑
     */
    public void detachView() {
        if (this.mView != null) {
            this.mView.clear();
        }
        if (this.mContext != null) {
            this.mContext.clear();
        }
        if (this.compositeDisposable != null) {
            this.compositeDisposable.clear();
        }
    }
}

Model层网络框架的封装

后台返回的数据都有统一的格式,除了具体的data外其他都是一样的,所以data采用泛型,灵活改变

public class BaseResponse<T> {
    /**
     * 返回码 200成功
     */
    private int code;
    /**
     * 消息
     */
    private String msg;
    /**
     * 数据
     */
    private T data;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
    
    @Override
    public String toString() {
        return "BaseResponse{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

  BaseObserver的作用是对数据进行统一处理,比如说数据请求发生401,404,500错误,我们不可能每次在创建Observer的时候重复再写一遍对这些数据的处理,浪费时间还不美观,而且日后修改起来比较麻烦。
  熟悉RxJava的同学知道,当我们开启一个异步任务时,通常需要在Activity/Fragment销毁时,及时关闭异步任务,否则就会有内存泄漏的。在BasePresenter中涉及到Disposable,一般的做法是订阅成功后,拿到Disposable对象,在Activity/Fragment销毁时,调用Disposable对象的dispose()方法,将异步任务中断,也就是中断RxJava的管道,以此,BaseObserver的父类必须继承自实现了dispose接口的类,这里选择ResourceObserver

public abstract class BaseObserver<T> extends ResourceObserver<BaseResponse<T>> {

    private BaseView mView;
    private Context mContext;

    public BaseObserver(BaseView baseView, Context context) {
        this.mView = baseView;
        this.mContext = context;
    }

    @Override
    public void onNext(BaseResponse<T> tBaseResponse) {
        if (tBaseResponse.getCode() != Constant.HttpCode.HTTP_CODE_SUCCESS) {
            onError(new Throwable(tBaseResponse.getMsg()));
        } else {
            success(tBaseResponse.getData());
        }
    }

    @Override
    public void onError(Throwable e) {
        String errorMsg = e.getMessage();
        if (e instanceof UnknownHostException) {
            errorMsg = mContext.getResources().getString(R.string.http_un_know_host_exception);
        } else if (e instanceof SocketTimeoutException) {
            errorMsg = mContext.getResources().getString(R.string.http_socket_time_out_exception);
        } else if (e instanceof HttpException) {
            HttpException httpException = (HttpException) e;
            if (httpException.code() == Constant.HttpCode.HTTP_CODE_SERVER_ERROR) {
                errorMsg = mContext.getResources().getString(R.string.http_server_error);
            } else if (httpException.code() == Constant.HttpCode.HTTP_CODE_WITHOUT_LOGIN) {
                errorMsg = mContext.getResources().getString(R.string.http_without_login);
                mView.hideLoading();
                mView.showMsg(errorMsg);
                failer(errorMsg);
                mView.gotoLoginActivity();
                return;
            }
        } else if (e instanceof ParseException || e instanceof JSONException) {
            errorMsg = mContext.getResources().getString(R.string.http_json_parse_error);
        }
        mView.hideLoading();
        mView.showMsg(errorMsg);
        failer(errorMsg);
    }

    @Override
    public void onComplete() {

    }

    /**
     * 请求成功后在该方法中对数据进行处理
     * @param t
     */
    public abstract void success(T t);

    /**
     * 出错后可以在该方法中进行一些额外的操作
     * @param msg
     */
    public void failer(String msg) {

    }
}

常量类Constant.java

public class Constant {
    public static class HttpCode {
        /**
         * 请求成功
         */
        public static final int HTTP_CODE_SUCCESS = 200;
        /**
         * 未登录
         */
        public static final int HTTP_CODE_WITHOUT_LOGIN = 401;
        /**
         * 服务器错误
         */
        public static final int HTTP_CODE_SERVER_ERROR = 500;
    }
}

Strings.xml添加以下字符串

    <string name="http_un_know_host_exception">网络不可用</string>
    <string name="http_socket_time_out_exception">请求网络超时</string>
    <string name="http_server_error">服务器处理请求出错</string>
    <string name="http_without_login">登陆失效</string>
    <string name="http_login_over_time">登陆超时</string>
    <string name="http_json_parse_error">数据解析错误</string>

  为了在后期防止后台修改数据名称导致我们数据错误,从而要修改多处代码,该框架在获取数据的同时需要进行数据的转化

这是登录请求返回的数据

public class LoginResponse {

    private Long id;
    private String phone;
    private String nickname;
    private Integer gender;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }
}

LoginItem.java

public class LoginItem extends BaseItem {

    private long id;
    private String phone;
    private String nickname;
    private int gender;
    
    /**
     *当LoginResponse中的数据变量名称修改时,只需要在该方法中修改即可,而且,  
     *一些为NULL的情况,你可以在这里做处理.(比如当服务器返回的gender名称修改  
     *为sex时,只需要在LoginResponse和LoginItem的构造方法中修改一下就可以  
     *了,如果不加这么一层,在所有实现了该接口的地方以及使用gender数据的地方  
     *都要进行修改)
     */
    public LoginItem(LoginResponse loginResponse) {
        //ItemType的功能以后在讲到RecyclerView的适配器的时候会讲到
        this.ItemType = Constant.ItemType.ITEM_LOGIN_RESPONSE;
        this.id = loginResponse.getId() == null ? 0L : loginResponse.getId();
        this.phone = loginResponse.getPhone() == null ? "" : loginResponse.getPhone();
        this.nickname = loginResponse.getNickname() == null ? "" : loginResponse.getNickname();
        this.gender = loginResponse.getGender() == null ? 0 : loginResponse.getGender();

    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }
}

在RxJava中有这类型转换的操作符,因此在这里写一个基础类,对数据进行统一处理

BaseFunction.java

public abstract class BaseFunction<T, R extends BaseItem> implements Function<BaseResponse<T>, R> {

    private Context mContext;
    private BaseView mView;

    public BaseFunction(BaseView view, Context context) {
        this.mView = view;
        this.mContext = context;
    }
    
    @Override
    public R apply(BaseResponse<T> tResponseBean) throws Exception {
        if (tResponseBean.getCode()== Constant.HttpCode.HTTP_CODE_SUCCESS){
            return change(tResponseBean.getData());
        }else {
            throw new Exception(tResponseBean.getMsg());
        }
    }

    /**
     * 在该方法中对数据请求获取的数据进行转化
     * @param data
     * @return
     */
    public abstract R change(T data);
}

对Retrofit进行封装
Api.java

public interface Api {

    /**
     * 账号密码登录
     *
     * @param phone
     * @param password
     * @return
     */
    @POST(Constant.Url.LOGIN_BG_PASSWORD)
    Observable<BaseResponse<LoginResponse>> loginByPassword(@Query("phone") String phone, @Query("password") String password);

}

ApiService.java

public class ApiService {

    private volatile static ApiService instance = null;
    private Api api;
    private Retrofit retrofit;
    private static Context mContext;

    /**
     *在Application的onCreate()方法中初始化
     */
    public static void init(Context context) {
        mContext = context.getApplicationContext();
    }

    ApiService(){
        retrofit = new Retrofit.Builder()
                .baseUrl(Constant.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        api = retrofit.create(Api.class);
    }

    public static ApiService getInstance() {
        if (instance == null) {
            synchronized (ApiService.class) {
                if (instance == null) {
                    instance = new ApiService();
                }
            }
        }
        return instance;
    }

    public Api getChristianityApi() {
        return api;
    }
}

以上是我在自己空闲时间喜欢在自己作品上使用的一套框架,在接下来我会坚持每周写一篇文章关于一个我最近想写的一个写日记App的开发实例分享,该App也是在这套框架上实现的,各位小伙伴如果对这个框架有什么可以改进或疑惑的地方可以在下方留言,看到消息我会及时回复,说不定还能交个朋友呢😃。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值