在不久以前分享一篇《App 組件化/模塊化之路——如何封裝網絡請求框架》文章介紹了我在項目中封裝網絡請求框架的思路。開發一個 App 會涉及到不少網絡請求 API ,例如登陸註冊接口、用戶信息接口、業務列表請求接口等等。而本文介紹的是如何模塊化設計這些接口,使得項目中更好地複用代碼。固然這僅僅是一家之言,歡迎留言拍磚。java
網絡請求中最多見的莫過於用戶受權登陸模塊了。如今以此模塊爲例,大概有如下接口android
假設一個 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
思路其實也簡單。不錯,爲了讓你的接口更好的複用,咱們把整個模塊相關的接口進行總體設計。對外統一接口和回調方法。咱們來看代碼。緩存
/** * 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
裏面又管理着 Presenter
和 View
接口,分別表明具體 API 請求方法和數據回調方法。其中在 View
接口中定義了幾個通用的回調 onBegin
, onFinish
, onError
,分別表明請求開始、結束、出錯等幾種狀態,其它方法就是具體 API 返回的數據回調了。微信
這個 Contract
接口設計思路是源於googlesamples/android-architecture 。這樣的好處我認爲就是很好的管理這個模塊中的衆多的接口和回調方法,而維護者一看就一目瞭然,很是清晰。網絡
而後,實現一個 Contract
接口中的 View
接口。實際上是空實現。app
/** * 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
接口了。這個就是咱們這個模塊化接口的核心類了。
/** * 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 實現接口還有如下幾個方法
具體的 API 實現中,我這裏就使用了以前網絡框架中的代碼 SignInRequest。
在 AuthManager
中還有一個 AuthDBHelper
類,這個是用戶信息的緩存類。只要用戶登陸過了,那麼下次就是直接取緩存中的登陸信息就能夠了。
預覽如下總體的結構
與用戶相關的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 裏不少頁面都會用到獲取本地音樂或者視頻的列表。一樣地,有如下幾個類。
微信關注咱們,能夠獲取更多