回顾retrofit基本用法 (结合rxjava)
//定义结果解析类
public class HttpResult {
int code;
String message;
T data;
}
//定义接口
public interface ApiService {
@FormUrlEncoded
@POST("login")
Observable> login(@Field("username") String username
, @Field("password") String password);
}
//创建retroft对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/api/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient())
.build();
//创建接口实例
ApiService service = retrofit.create(ApiService.class);
//执行接口方法
service.login("luqihua","123456")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer>() {
@Override
public void accept(HttpResult userInfoHttpResult) throws Exception {
System.out.println("accept: " + userInfoHttpResult);
}
});
#输出结果
accept: HttpResult{code=0, message='success', data=UserInfo{username='luqihua', password='123456'}}
## 这里直接结合了rxjava转换和json解析,这是我们最常用的方式,后续自己封装也使用这2个转换。
一个最基本的网络请求应该包含下面四个部分
- url (必须)
- 请求方法 (必须)(只讨论GET和POST)
- 请求参数Map
params 键值对(非必须) - 返回值类型 (必须) (通常在进行一个http请求时,返回结果是和服务端约定好的可以预知的。一般是json字符串转bean对象)
retrofit的封装中,这四个信息全部都定义在了接口方法中(使用注解),通过retrofit.create(ApiService.class) 得到了可执行的实例对象。结合上一篇博文,可以猜测, retrofit.create(ApiService.class)方法采用了动态代理的方式生成了对象。
基本模型搭建
//首先定义一个最基本的ApiService,返回值结果是Observable
public interface ApiService {
Observable> login(String username,String password);
}
//定义一个HttpProxy 对应框架中的retrofit类,可以通过动态代理生成接口实例对象
public class HttpProxy {
public T create(Class c) {
return (T) Proxy.newProxyInstance(
c.getClassLoader()
, new Class[]{c}
, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return parseRequest(proxy, method, args);
}
}
);
}
public Object parseRequest(Object proxy, Method method, Object[] args) {
// TODO: 获取url和method
//解析请求参数
Map params = getParams(method, args);
//解析返回值类型
final Type resultType = getResultType(method);
//发送请求并返回结果
return parseResult(url, md, params, resultType);
}
/**
* 获取http请求参数
*
* @param method
* @param args
* @return
*/
private Map getParams(Method method, Object[] args) {
// TODO: 2017/12/1
return null;
}
/**
* 获取结果解析类型
*
* @param method
* @return
*/
private Type getResultType(Method method) {
// TODO: 2017/12/1
return null;
}
/**
* @param url 请求地址
* @param method 请求方法
* @param params 请求参数
* @param type 返回值类型
* @return
*/
public Object parseResult(String url, String method, Map params, final Type type) {
// TODO: 2017/12/1
return null;
}
}
### 测试
@Test
public void test2() {
HttpProxy proxy = new HttpProxy();
ApiService service = proxy.create(ApiService.class);
service.login("luqihua");
System.out.println("调用成功");
}
### 输出
调用成功
在parseRequest()方法中,我加入了四个TODO待办事项,接下来将一步一步补充这三处代码
获取url和method
第一步先要在login方法上加入url和method并在代理方法中解析
# 定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//作用在方法上
public @interface POST {
String value() default "";
}
# 在ApiService上使用注解
public interface ApiService {
@POST("http://localhost:8080/api/login") //这是我的接口 大家可以换成自己的测试接口
Observable> login(String username, String password);
}
# 在parseRequest方法中获取url
public Object parseRequest(Object proxy, Method method, Object[] args) {
//解析url 和method
String url = "";//请求url
String md= "POST";//请求方法,默认给POST
if (method.isAnnotationPresent(POST.class)) {
url = method.getAnnotation(POST.class).value();
//打印看下
System.out.println("请求地址: "+url);
md = "POST";
}
//解析请求参数
Map params = getParams(method, args);
//解析返回值类型
final Type resultType = getResultType(method);
//发送请求并返回结果
return parseResult(url, md, params, resultType);
}
# 运行测试用例
@Test
public void test2() {
HttpProxy proxy = new HttpProxy();
ApiService service = proxy.create(ApiService.class);
service.login("luqihua", "123456");
System.out.println("调用成功");
}
# 输出
请求地址: http://localhost:8080/api/login
调用成功
获取输入参数
动态代理方法的入参都封装在了 invoke(Object proxy, Method method, Object[] args) 方法的第三个参数 Object[] args中。从http请求的入参观察,每一个入参应该是key=value形式的键值对,但是仅仅通过上述ApiService的参数传递只能得到value而得不到key。怎么引入key呢?联想retrofit的接口方法很容易想到那就是使用注解来引入key。
#定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Field {
String value() default "";
}
# 改造ApiService
public interface ApiService {
@POST("http://localhost:8080/api/login")
Observable> login(@Field("username") String username, @Field("password") String password);
}
# getParams()方法补充代码
/**
* 获取http请求参数
*
* @param method
* @param args
* @return
*/
private Map getParams(Method method, Object[] args) {
//该操作用于返回方法参数注解,由于方法参数可以有多个,而每个参数又可以有多个注解,所以返回的是一个二维数组。
Annotation[][] annotationSS = method.getParameterAnnotations();
//定义一个map用于接收入参key=value
Map params = new HashMap<>();
int len = annotationSS.length;
for (int i = 0; i < len; i++) {
Annotation[] annotations = annotationSS[i];
if (annotations.length != 1) {
//这里我们限制每个参数只能有一个注解,这是为了保证key和value的一一对应
throw new RuntimeException("每个参数只能有一个注解");
}
Annotation att = annotations[0];
if (att instanceof com.lu.http.Field) {
//将第i个参数的注解值作为key 参数值作为value放入map中
params.put(((Field) att).value(), (String) args[i]);
}
}
System.out.println("参数:"+params);
return params;
}
# 运行测试用例
@Test
public void test2() {
HttpProxy proxy = new HttpProxy();
ApiService service = proxy.create(ApiService.class);
service.login("luqihua");
System.out.println("调用成功");
}
# 输出
参数:{password=123456, username=luqihua}
调用成功
代码都注释的很详细。可以看出,通过上述处理,获取到了参数入参键值对,这将作为http请求的入参。
获取返回值类型
在retroft基本用法中,返回结果是形如 Observable
这样的结果,在网络请求成功时可以自动将结果转换成功T对应的bean对象,gson转换的常用常用代码为 new Gson().fromJson(String json, Type type)。json是网络请求的返回字符串,显然我们还需要知道type,也就是T的类型才能进行正确的json解析。这里涉及到Java类体系中Type的知识,可以看我另一篇博客的介绍 Type类介绍
/**
* 获取结果解析类型
*
* @param method
* @return
*/
private Type getResultType(Method method) {
//解析返回值,
Type resultType = method.getGenericReturnType();
/**
* 1.如果方法定义的参数返回值不带泛型参数,则这个type是返回值类型的Class对象
* 例: String get();这个方法返回的Type是 String.class类
*
* 2.如果方法返回值带泛型参数,则返回Type属于 ParameterizedType 如果希望json解析的对象是泛型类型,需要进一步拿到泛型的Type
* 例: Observable get(); 这个方法需要进一步拿到T类型进行json转换
*/
if (resultType instanceof ParameterizedType) {
/**
* 1.如果定义返回结果是Observable类型,只有一个泛型,因此这个type[]长度是1,
*2.如果定义的是形如Map类型,有2个泛型, 则返回的Type[]数组长度为2
*/
Type[] types = ((ParameterizedType) resultType).getActualTypeArguments();
//得到Observable中T的类型
resultType = types[0];
}
System.out.println("返回结果类型: " + resultType);
return resultType;
}
# 运行测试用例
@Test
public void test2() {
HttpProxy proxy = new HttpProxy();
ApiService service = proxy.create(ApiService.class);
service.login("luqihua", "123456");
System.out.println("调用成功");
}
# 输出结果
返回结果类型: com.lu.http.HttpResult
调用成功
从输出结果可以看到,我们拿到了返回值结果的完整类型,包括泛型信息,这将在进行json转换的时候作为第二个参数type。
进行http请求和返回值结果的gson转换
上面几步我们已经获取了一个网络请求所需要的基本信息,接下来结合okhttp正式发起请求了(okhttp和rxjava的使用不熟悉的自行百度了解一下)
/**
* 发送网络请求并将结果解析成方法定义的返回值类型
* @param url 请求地址
* @param method 请求方法
* @param params 请求参数
* @param type 结果解析类型
* @return
*/
public Object parseResult(String url, String method, Map params, final Type type) {
//添加请求参数体
FormBody.Builder bodyBuilder = new FormBody.Builder();
for (String key : params.keySet()) {
bodyBuilder.add(key, params.get(key));
}
//构建请求
final Request request = new Request.Builder()
.url(url)
.method(method, bodyBuilder.build())
.build();
//parseRequest的返回值从这一步开始变成我们需要的Observable
return Observable.create(new ObservableOnSubscribe
这一步进行了网络的请求和返回值类型的解析,从测试结果可以看出调用已经成功,结果在RxJava的回调中解析成了我们需要的类型。这样我们的HttpProxy(对应retrofit类)基本编写完成。
在接口中增加另一个请求进行进一步测试。
//在apiservice中添加新的方法
public interface ApiService {
@POST("http://localhost:8080/api/login")
Observable> login(@Field("username") String username, @Field("password") String password);
//添加一个方法用于根据groupId获取用户列表
@POST("http://localhost:8080/api/getUserList")
Observable> getUserList(@Field("groupId") String groupId);
}
# 运行测试用例
@Test
public void test2() {
HttpProxy proxy = new HttpProxy();
ApiService service = proxy.create(ApiService.class);
// service.login("luqihua", "123456")
// .subscribe(new Consumer>() {
// @Override
// public void accept(HttpResult userInfoHttpResult) throws Exception {
// System.out.println("onNext: " + userInfoHttpResult);
// }
// });
//调用第二个接口请求
service.getUserList("1")
.subscribe(new Consumer>() {
@Override
public void accept(List userInfos) throws Exception {
System.out.println(userInfos);
}
});
}
# 输出结果
[UserInfo{username='username-0', password='password-0'}, UserInfo{username='username-1', password='password-1'}, UserInfo{username='username-2', password='password-2'}, UserInfo{username='username-3', password='password-3'}, UserInfo{username='username-4', password='password-4'}]
到这一步 整个框架基本已经完成,而且具备了retrofit式的请求模式,接下来就是对整个框架进行细节上的优化。这将在下一篇博文中进行讲解。