自定义retrofit框架(二)编写基本框架模型

回顾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 (必须)
  • 请求方法 (必须)(只讨论GETPOST)
  • 请求参数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() {
            @Override
            public void subscribe(ObservableEmitter emitter) throws Exception {
                final Call call = new OkHttpClient().newCall(request);
                Response response = call.execute();
                if (response.isSuccessful()) {
                    //解析结果
                    String data = response.body().string();
                    emitter.onNext(new Gson().fromJson(data, type));
                } else {
                    // TODO: 异常处理 先简单处理
                    emitter.onError(new Exception("http error"));
                }
                emitter.onComplete();
            }
        });
    }

# 运行测试用例
 @Test
    public void test2() {

        HttpProxy proxy = new HttpProxy();

        ApiService service = proxy.create(ApiService.class);
      
        //由于返回的是rxjava的Observable 因此可以进行rxjava的各种操作
        service.login("luqihua", "123456")
                .subscribe(new Consumer>() {
                    @Override
                    public void accept(HttpResult userInfoHttpResult) throws Exception {
                        System.out.println("onNext: " + userInfoHttpResult);
                    }
                });

    }


# 输出结果
onNext: HttpResult{code=0, message='success', data=UserInfo{username='luqihua', password='123456'}}


 
 

这一步进行了网络的请求和返回值类型的解析,从测试结果可以看出调用已经成功,结果在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式的请求模式,接下来就是对整个框架进行细节上的优化。这将在下一篇博文中进行讲解。

下一篇将对框架进行优化。

项目源码我封装成了一个library放在了github,有兴趣的朋友可以下载下来进一步优化

你可能感兴趣的:(自定义retrofit框架(二)编写基本框架模型)