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也是在這套框架上實現的,各位小夥伴如果對這個框架有什麼可以改進或疑惑的地方可以在下方留言,看到消息我會及時回覆,說不定還能交個朋友呢😃。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章