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

回顾retrofit基本用法 (结合rxjava)

//定义结果解析类
public class HttpResult<T> {
    int code;
    String message;
    T data;
}

//定义接口
public interface ApiService {
    @FormUrlEncoded
    @POST("login")
    Observable<HttpResult<UserInfo>> 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<HttpResult<UserInfo>>() {
                    @Override
                    public void accept(HttpResult<UserInfo> 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<String,String> params 键值对(非必须)
  • 返回值类型 (必须) (通常在进行一个http请求时,返回结果是和服务端约定好的可以预知的。一般是json字符串转bean对象)

retrofit的封装中,这四个信息全部都定义在了接口方法中(使用注解),通过retrofit.create(ApiService.class) 得到了可执行的实例对象。结合上一篇博文,可以猜测, retrofit.create(ApiService.class)方法采用了动态代理的方式生成了对象。

基本模型搭建

//首先定义一个最基本的ApiService,返回值结果是Observable<T>
public interface ApiService {
    Observable<HttpResult<UserInfo>> login(String username,String password);
}


//定义一个HttpProxy   对应框架中的retrofit类,可以通过动态代理生成接口实例对象
public class HttpProxy {
    public <T> T create(Class<T> 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<String, String> params = getParams(method, args);

        //解析返回值类型
        final Type resultType = getResultType(method);

        //发送请求并返回结果
        return parseResult(url, md, params, resultType);
    }

   /**
     * 获取http请求参数
     *
     * @param method
     * @param args
     * @return
     */
    private Map<String, String> 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<String, String> 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<HttpResult<UserInfo>> 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<String, String> 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<HttpResult<UserInfo>> login(@Field("username") String username, @Field("password") String password);

}


# getParams()方法补充代码
 /**
     * 获取http请求参数
     *
     * @param method
     * @param args
     * @return
     */
    private Map<String, String> getParams(Method method, Object[] args) {
        //该操作用于返回方法参数注解,由于方法参数可以有多个,而每个参数又可以有多个注解,所以返回的是一个二维数组。
        Annotation[][] annotationSS = method.getParameterAnnotations();

        //定义一个map用于接收入参key=value
        Map<String, String> 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> 这样的结果,在网络请求成功时可以自动将结果转换成功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<T> get(); 这个方法需要进一步拿到T类型进行json转换
         */
        if (resultType instanceof ParameterizedType) {
            /**
             * 1.如果定义返回结果是Observable<T>类型,只有一个泛型,因此这个type[]长度是1,
             *2.如果定义的是形如Map<String,String>类型,有2个泛型, 则返回的Type[]数组长度为2
             */
            Type[] types = ((ParameterizedType) resultType).getActualTypeArguments();
            //得到Observable<T>中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<com.lu.http.UserInfo>
调用成功

从输出结果可以看到,我们拿到了返回值结果的完整类型,包括泛型信息,这将在进行json转换的时候作为第二个参数type。

进行http请求和返回值结果的gson转换

上面几步我们已经获取了一个网络请求所需要的基本信息,接下来结合okhttp正式发起请求了(okhttp和rxjava的使用不熟悉的自行百度了解一下)

/**
     * 发送网络请求并将结果解析成方法定义的返回值类型
     * @param url 请求地址
     * @param method 请求方法
     * @param params 请求参数
     * @param type  结果解析类型
     * @return
     */
    public Object parseResult(String url, String method, Map<String, String> 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<T>
        return Observable.create(new ObservableOnSubscribe<Object>() {
            @Override
            public void subscribe(ObservableEmitter<Object> 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<T> 因此可以进行rxjava的各种操作
        service.login("luqihua", "123456")
                .subscribe(new Consumer<HttpResult<UserInfo>>() {
                    @Override
                    public void accept(HttpResult<UserInfo> 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<HttpResult<UserInfo>> login(@Field("username") String username, @Field("password") String password);


    //添加一个方法用于根据groupId获取用户列表
    @POST("http://localhost:8080/api/getUserList")
    Observable<List<UserInfo>> 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<HttpResult<UserInfo>>() {
//                    @Override
//                    public void accept(HttpResult<UserInfo> userInfoHttpResult) throws Exception {
//                        System.out.println("onNext: " + userInfoHttpResult);
//                    }
//                });

        //调用第二个接口请求
        service.getUserList("1")
                .subscribe(new Consumer<List<UserInfo>>() {
                    @Override
                    public void accept(List<UserInfo> 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,有兴趣的朋友可以下载下来进一步优化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李头阅读 15,072评论 4 39
  • 安卓开发领域中,很多重要的问题都有很好的开源解决方案,例如Square公司提供网络请求 OkHttp , Retr...
    aaron688阅读 1,913评论 1 20
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,062评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 本篇稍微记录下Git使用的一些心得。对Git的使用,应该是从搭建自己的博客开始的。当时看到开源中国推荐的一篇基于码...
    G小调的Qing歌阅读 222评论 0 1