App 組件化/模塊化之路——使用SDK的思路進行模塊化設計接口

在不久以前分享一篇《App 組件化/模塊化之路——如何封裝網絡請求框架》文章介紹了我在項目中封裝網絡請求框架的思路。開發一個 App 會涉及到不少網絡請求 API ,例如登陸註冊接口、用戶信息接口、業務列表請求接口等等。而本文介紹的是如何模塊化設計這些接口,使得項目中更好地複用代碼。固然這僅僅是一家之言,歡迎留言拍磚。java

問題

網絡請求中最多見的莫過於用戶受權登陸模塊了。如今以此模塊爲例,大概有如下接口android

  • 登陸 sign_in
  • 註冊 sign_up
  • 找回密碼 find_password
  • 獲取短信驗證碼 getvalidatecode
  • 獲取用戶信息 user_info
  • 修改用戶信息 edit_user
  • 綁定手機號 bind_phone

假設一個 App 中有這些接口,那麼如何設計這些接口呢?按照咱們以前設計的網絡請求框架就是把每個具體的 API (例如登陸接口) 寫一個 Request 類。git

public class SignInRequest extends BaseTextRequest<SignInResult> {

    public SimpleTextRequest(Context context, Map<String, String> params) {
        super(context);
        addParams(params);
    }

    @Override
    public String getUrl() {
        return "https://api.angrycode.net/signin";
    }

    @Override
    public HttpMethod getHttpMethod() {
        return HttpMethod.POST;
    }

    @Override
    protected SignInResult onRequestFinish(String result) {
        return SignInResult.parse(result);
    }

    @Override
    protected SignInResult onRequestError(int code, String message) {
        return new SignInResult(code,message);
    }
}

 

相似的註冊接口對應一個 SignUpRequest 類,因而這樣有多少個接口就又多少個 Request 類。github

若是你的 App 業務比較複雜,那麼 Request 類數目就會暴增,這時候如何組織管理這些 Request 類就是一個問題了。api

接口模塊化

思路其實也簡單。不錯,爲了讓你的接口更好的複用,咱們把整個模塊相關的接口進行總體設計。對外統一接口和回調方法。咱們來看代碼。緩存

AuthContract

/**
 * Created by wecodexyz@gmail.com on 2017/10/14 下午5:57.
 * GitHub - https://github.com/wecodexyz
 * Description:
 */

public interface AuthContract {
   
    interface Presenter {
        /**
         * 登陸:/api/1.0/user/sign/in
         *
         * @param account  手機或郵箱
         * @param password 登陸密碼
         * @param type     類型:0-普通登陸、1-郵箱登陸、2-手機登陸
         */
        void signIn(String account, String password, @SignInType int type);

        /**
         * 註冊:/api/1.0/user/sign/up
         
         * @param nick_name 用戶暱稱 optional
         * @param signature 用戶簽名 optional
         */
        void signUp(String account, String password, @SignInType int type, String code, String nick_name, String signature);
        /**
         * 登出:/api/1.0/user/sign/out
         */
        void signOut();
        /**
         * 修改密碼:/api/1.0/user/password/update
         *
         * @param old_password 原密碼
         * @param new_password 新密碼
         */
        void updatePassword(String old_password, String new_password);
        /**
         * 手機綁定:/api/1.0/user/phone/bind
         */
        void bindPhone(String phone, String code, String password);
        /**
         * 手機解綁:/api/1.0/user/phone/unbind
         */
        void unbindPhone(String phone, String code);
        /**
         * 獲取我的資料:/api/1.0/user/profile
         */
        void profile();
        
    }

    interface View {
        /**
         * 註冊結果
         *
         * @param signInResult
         */
        void onSignUpFinish(SignInResult signInResult);
        /**
         * 登陸結果
         *
         * @param signInResult
         */
        void onSignInFinish(SignInResult signInResult);
        /**
         * 手機綁定結果
         *
         * @param result
         */
        void onBindPhoneFinish(APIResult result);
        /**
         * 獲取我的資料
         *
         * @param result
         */
        void onRequestProfileFinish(ProfileResult result);
        /**
         * 獲取我的資料
         *
         * @param result 更新結果
         */
        void onUpdateProfileFinish(APIResult result);
        /**
         * 出錯回調
         *
         * @param code
         * @param msg
         */
        void onError(int code, String msg);
      
          void onFinish();
      
          void onBegin();

    }

}

 

首先,根據 API 設計 Contract 接口,在這裏定義接口請求方法和回調方法。例如咱們這個登陸模塊,就能夠定義一個 AuthContract 協議接口,在這個 Contract裏面又管理着 PresenterView 接口,分別表明具體 API 請求方法和數據回調方法。其中在 View 接口中定義了幾個通用的回調 onBegin, onFinish, onError,分別表明請求開始、結束、出錯等幾種狀態,其它方法就是具體 API 返回的數據回調了。微信

這個 Contract 接口設計思路是源於googlesamples/android-architecture 。這樣的好處我認爲就是很好的管理這個模塊中的衆多的接口和回調方法,而維護者一看就一目瞭然,很是清晰。網絡

而後,實現一個 Contract 接口中的 View 接口。實際上是空實現。app

AuthCallback

/**
 * Created by wecodexyz@gmail.com on 2017/10/14 下午6:53.
 * GitHub - https://github.com/wecodexyz
 * Description: 受權登陸以及用戶相關接口回調類
 */

public class AuthCallback implements AuthContract.View {
    @Override
    public void onSignUpFinish(SignInResult signInResult) {

    }

    @Override
    public void onSignInFinish(SignInResult signInResult) {

    }

    @Override
    public void onSignOutFinish(APIResult result) {

    }

    @Override
    public void onUpdatePasswordFinish(APIResult result) {

    }

    @Override
    public void onBindPhoneFinish(APIResult result) {

    }

    @Override
    public void onUnbindPhoneFinish(APIResult result) {

    }

    @Override
    public void onRequestProfileFinish(ProfileResult result) {

    }

    @Override
    public void onUpdateProfileFinish(APIResult result) {

    }

    @Override
    public void onError(int code, String msg) {

    }
      
      @Override
      public void onFinish(){
        
    }
      
      @Override
      public void onBegin(){
        
    }
}

 

爲何要提供一個空實現的類呢?其實爲了方便使用。想一想你使用過的 WebViewChrome 的接口回調。框架

最後,咱們實現 Contract 中的 Presenter 接口了。這個就是咱們這個模塊化接口的核心類了。

AuthManager

/**
 * Created by wecodexyz@gmail.com on 2017/10/14 下午6:55.
 * GitHub - https://github.com/wecodexyz
 * Description:
 */

public class AuthManager implements AuthContract.Presenter {

    private Context mContext;

    private List<AuthCallback> mAuthCallbacks;

    private SignInResult mSignInResult;

    private AuthDBHelper mAuthDBHelper;

    private AuthManager() {
    }

    private static class Holder {
        private static final AuthManager INSTANCE = new AuthManager();
    }

    public static AuthManager get() {
        return Holder.INSTANCE;
    }

    /**
     * 在Application中進行初始化
     *
     * @param context application context
     */
    public void init(Context context) {
        mContext = context.getApplicationContext();
        //獲取本地登陸信息
        mAuthDBHelper = new AuthDBHelper(mContext);

        mSignInResult = mAuthDBHelper.loadSignInFromCache();
    }

    /**
     * 是否已登陸受權
     *
     * @return
     */
    public boolean isAuth() {
        return mSignInResult != null && mSignInResult.isStatus();
    }
    public void registerCallback(AuthCallback authCallback) {
        if (mAuthCallbacks == null) {
            mAuthCallbacks = new ArrayList<>();
        }
        mAuthCallbacks.add(authCallback);
    }

    public void unregisterCallback(AuthCallback authCallback) {
        mAuthCallbacks.remove(authCallback);
    }

    public void clearCallbacks() {
        if (mAuthCallbacks == null) {
            return;
        }
        mAuthCallbacks.clear();
    }
    @Override
    public void signIn(String account, String password, @AuthContract.SignInType int type) {
        HashMap<String, String> params = new HashMap<>();
        if (!TextUtils.isEmpty(account)) {
            params.put("account", account);
        }
        if (!TextUtils.isEmpty(password)) {
            params.put("password", password);
        }
        params.put("type", String.valueOf(type));

        SignInRequest request = new SignInRequest(mContext);
        request.addParams(params);
        request.request()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                  .doOnSubscribe(new Consumer<Subscription>() {
                    @Override
                    public void accept(@NonNull Subscription subscription) throws Exception {
                        for (AuthCallback callback : mCallbacks) {
                            callback.onBegin();
                        }
                    }
                })
                .doFinally(new Action() {
                    @Override
                    public void run() throws Exception {
                        for (AuthCallback callback : mCallbacks) {
                            callback.onFinish();
                        }
                    }
                })
                .doAfterNext(new Consumer<SignInResult>() {
                    @Override
                    public void accept(@NonNull SignInResult signInResult) throws Exception {
                        mAuthDBHelper.cacheSignIn(signInResult);
                    }
                })
                .subscribe(new Consumer<SignInResult>() {
                    @Override
                    public void accept(@NonNull SignInResult signInResult) throws Exception {
                        if (signInResult.isStatus()) {
                            mSignInResult = signInResult;
                            UserInfo.fromSigninResult(mSignInResult);
                        }
                        for (AuthCallback callback : mAuthCallbacks) {
                            callback.onSignInFinish(signInResult);
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        LogUtils.e("sign in error -> " + throwable);
                        callbackError(110, "sign in error");
                    }
                });
    }
    //接口太多這裏只列舉signup接口,其中接口相似...

}

 

AuthManager 這個類設計單例模式。除了具體 API 實現接口還有如下幾個方法

  • init 全局初始化方法。主要是爲了保存 Application 上下文,由於接口請求會使用到。
  • registerCallback 註冊回調。哪裏使用,就哪裏註冊
  • unregisterCallback 取消註冊回調。與上面方法對應使用,避免頁面內存泄露
  • clearCallbacks 清除全部回調。

具體的 API 實現中,我這裏就使用了以前網絡框架中的代碼 SignInRequest。

AuthManager 中還有一個 AuthDBHelper 類,這個是用戶信息的緩存類。只要用戶登陸過了,那麼下次就是直接取緩存中的登陸信息就能夠了。

總體結構

預覽如下總體的結構

Auth-arch

與用戶相關的API都放在此模塊中進行管理,而其它模塊進行使用就很方便了。

首先,在Application中進行初始化

@Override
    public void onCreate() {
        super.onCreate();
        AuthManager.get().init(this);
    }

 

這個用法是否是與其它第三方 SDK 的使用相似呢?能夠感覺一下,其實這個也是以前提到的 SDK 設計思路。

而後在須要調用接口的頁面中,如LoginFragment

AuthCallback mAuthCallback = new AuthCallback() {
        @Override
        public void onError(int code, String msg) {
            //請求出錯
        }
      
          @Override
          public void onBegin(){
            //請求開始
        }
    
          @Override
          public void onFinish(){
            //請求結束
        }
      
        @Override
        public void onSignInFinish(SignInResult signInResult) {
            super.onSignInFinish(signInResult);
            if (signInResult.isStatus()) {
                //登陸成功
            }
        }
    };
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        AuthManager.get().registerCallback(mAuthCallback);
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        AuthManager.get().unregisterCallback(mAuthCallback);
    }

 

這樣用起來是否是很方便呢?

目前在項中中除了 API 能夠這樣設計以外,還有其它一個功能只要各個模塊都有可能常常使用到的均可以使用這樣的思路。

例如,個人 App 裏不少頁面都會用到獲取本地音樂或者視頻的列表。一樣地,有如下幾個類。

media-arch

微信關注咱們,能夠獲取更多

相關文章
相關標籤/搜索