App 组件化/模块化之路——使用SDK的思路进行模块化设计接口
在不久之前分享一篇《App 组件化/模块化之路——如何封装网络请求框架》文章介绍了我在项目中封装网络请求框架的思路。开发一个 App 会涉及到很多网络请求 API ,例如登录注册接口、用户信息接口、业务列表请求接口等等。而本文介绍的是如何模块化设计这些接口,使得项目中更好地复用代码。当然这仅仅是一家之言,欢迎留言拍砖。
问题
网络请求中最常见的莫过于用户授权登录模块了。现在以此模块为例,大概有以下接口
- 登录 sign_in
- 注册 sign_up
- 找回密码 find_password
- 获取短信验证码 getvalidatecode
- 获取用户信息 user_info
- 修改用户信息 edit_user
- 绑定手机号 bind_phone
假设一个 App 中有这些接口,那么如何设计这些接口呢?按照我们之前设计的网络请求框架就是把每一个具体的 API (例如登录接口) 写一个 Request 类。
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 类。
如果你的 App 业务比较复杂,那么 Request 类数目就会暴增,这时候如何组织管理这些 Request 类就是一个问题了。
接口模块化
思路其实也简单。不错,为了让你的接口更好的复用,我们把整个模块相关的接口进行整体设计。对外统一接口和回调方法。我们来看代码。
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
里面又管理着 Presenter
和 View
接口,分别代表具体 API 请求方法和数据回调方法。其中在 View
接口中定义了几个通用的回调 onBegin
, onFinish
, onError
,分别代表请求开始、结束、出错等几种状态,其它方法就是具体 API 返回的数据回调了。
这个 Contract
接口设计思路是源于googlesamples/android-architecture 。这样的好处我认为就是很好的管理这个模块中的众多的接口和回调方法,而维护者一看就一目了然,非常清晰。
然后,实现一个 Contract
接口中的 View
接口。其实是空实现。
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
类,这个是用户信息的缓存类。只要用户登录过了,那么下次就是直接取缓存中的登录信息就可以了。
整体结构
预览以下整体的结构
与用户相关的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 里很多页面都会用到获取本地音乐或者视频的列表。同样地,有以下几个类。
微信关注我们,可以获取更多
App 组件化/模块化之路——使用SDK的思路进行模块化设计接口的更多相关文章
- App 组件化/模块化之路——构建开发架构思路
App 组件化/模块化开发架构思路 随着业务的发展 App 开发技术也越来越成熟,对开发者来说 App 代码量也迅速地增长到一个数量级.对于如何架构 App 已经每个开发者面临的实际问题.好的架构可以 ...
- App 组件化/模块化之路——如何封装网络请求框架
App 组件化/模块化之路——如何封装网络请求框架 在 App 开发中网络请求是每个开发者必备的开发库,也出现了许多优秀开源的网络请求库.例如 okhttp retrofit android-asyn ...
- App 组件化/模块化之路——Android 框架组件(Android Architecture Components)使用指南
面对越来越复杂的 App 需求,Google 官方发布了Android 框架组件库(Android Architecture Components ).为开发者更好的开发 App 提供了非常好的样本. ...
- 得到、微信、美团、爱奇艺APP组件化架构实践
一.背景 随着项目逐渐扩展,业务功能越来越多,代码量越来越多,开发人员数量也越来越多.此过程中,你是否有过以下烦恼? 项目模块多且复杂,编译一次要5分钟甚至10分钟?太慢不能忍? 改了一行代码 或只调 ...
- App 组件化/模块化之路——Repository 模式
什么是 Repository 模式 Repository 这个词直译过来仓库.仓储的意思.这个意思其实也能反应出 Repository 模式作用.App 开发中少不了对数据的操作,数据的来源可能有很多 ...
- JavaScript 组件化开发之路(一)
*:first-child{margin-top: 0 !important}.markdown-body>*:last-child{margin-bottom: 0 !important}.m ...
- Android 组件化/模块化之路——在展示层搭建MVP结构
Android 组件化/模块化之路——在展示层搭建MVP结构 什么是MVP Model–View–Presenter (MVP) 源于 Model–View–Controller (MVC) 的结构设 ...
- Android组件化、模块化、插件化
组件:指的是单一的功能组件,如地图组件(MapSDK).扫码组件(QRCode).支付组件(AnjukePay).路由组件(Router)等等: 模块:指的是独立的业务模块,如新房模块(NewHous ...
- Android的组件化和模块化
Android随着业务的增多,而且后续新的需求的增加,代码的修改会变得非常频繁 然后最近在看组件化和模块化 公司的业务没有那么大,所以这种方式我并没有采取 但是还是需要了解下他的使用机制 还有优缺点之 ...
随机推荐
- Mybatis第九篇【基于Maven在Idea下Mybatis逆向工程】
前言 在Intellij idea下,没有学习Maven的情况下使用Mybatis的逆向工程好像有点复杂,资料太少了-找到的资料好像也行不通- 于是学完Maven之后,我就再来更新Idea下使用Myb ...
- 自定义BaseServlet利用反射
比较完美一点的BaseServlet package com.yangwei.mvc.servlet; import java.io.IOException; import java.lang.ref ...
- JMeter基础之—录制脚本
Jmeter 是一个非常流行的性能测试工具,虽然与LoadRunner相比有很多不足,比如:它结果分析能力没有LoadRunner详细:很它的优点也有很多: l 开源,他是一款开源的免费软 ...
- Flex布局介绍
Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性 任何一个容器都可以指定为 Flex 布局. .box{ display: -web ...
- Oculus Store游戏下载默认路径修改方法
最近在测试一款VR游戏,所以在硬件设备上选择了HTC Vive和Oculus两款眼镜.相对而言,HTC安装比较人性化:支持自定义安装路径,而且可在界面更改应用程序下载位置,如图所示: 这下替我节省了不 ...
- Quartz源码——JobStore保存JonDetail和Trigger源码分析(一)
我都是分析的jobStore 方式为jdbc的SimpleTrigger!RAM的方式类似分析方式! {0} :表的前缀 ,如表qrtz_trigger ,{0}== qrtz_ {1}:quartz ...
- vue实例讲解之axios的使用
本篇来讲解一下axios插件的使用,axios是用来做数据交互的插件. 这篇将基于vue实例讲解之vue-router的使用这个项目的源码进行拓展. axios的使用步骤: 1.安装axios npm ...
- Run Away 模拟退火
Run Away Time Limit:5000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Sta ...
- Max Sum of Max-K-sub-sequence hdu3415
Max Sum of Max-K-sub-sequence Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K ...
- Python in/not in --- if not/if + for...[if]...构建List+ python的else子句
区分几个容易出错的地方: in 成员运算符 - 如果字符串中包含给定的字符返回 True >>>"H" in a True not in 成员运算符 - 如果字符 ...