Android网络请求还在用Retrofit吗,来试试使用Apollo Graphql 吧

背景

跳槽快两个月了,到了一家新公司,正准备开干,结果发现公司服务端用的是apollo-graphql来处理客户端网络请求,就要求客户端也用这个graphql,当时还挺奇怪的,毕竟之前android客户端用的都是retrofit+okhttp+rxjava这一套,我特么还吭哧吭哧的将它们封装了一套自认为挺不错的网络层架构

结果来到新公司发现就这么用不上了(其实一部分是用的上的,后面再说);现在干Android开发的,处理网络请求这块,不说百分百,也有个百分之七八十用的都是这一套吧,其它的基本也是OKHttp的封装库;尽管我的内心里有一万只草泥马在奔腾,但还是得迎难而上啊

不过有一说一,网上关于在Android上使用graphql的文章真的少的可怜,其实从这个库的star数量也能看出来
在这里插入图片描述
大家要是使用过Postman的话,不知有没有注意到它也开始支持graphql了
在这里插入图片描述
(透露下,其实apollo-android库网络层也是使用的OKHttp,可见OKHttp的强大啊)

回顾 RESTful

在理解graphql之前先阐述下RESTful,因为graphql产生的原因就是想替代RESTful

我们都知道服务端现在大部分使用的都是基于RESTful的设计风格和开发方式,当然客户端也是这样(Retrofit就是一个类似于RESTful的网络框架),那到底怎么理解RESTful呢?首先REST不是英语单词rest(休息)的意思,而是Representational State Transfer的简称,即表现层状态转移,其中REST指的是一组架构约束条件和原则,只要某个架构符合REST的约束条件和原则,那就可以称为RESTful架构,它是一种软件架构风格,而不是标准,也就是说不是强制的

看完这些拗口的术语你可能还是不太清楚,用知乎大神lvony的一句话概括

用URL定位资源,用HTTP动词(GET,POST,DELETE,PUT)描述操作

REST的主体是资源,比如网络上的一张图片,一段文字,一个视频,一些实际存在的东西,而URL就是用来指向这个资源的,比如

https://www.baidu.com/user

这个api一看就是用来对user资源的操作,这里使用名词来指定资源,没有涉及操作相关的声明,为啥呢?因为基本的操作有增删改查,如果你不用restful的话,就会产生四个接口:

https://www.baidu.com/add_user
https://www.baidu.com/update_user
https://www.baidu.com/delete_user
https://www.baidu.com/query_user

显然这种设计风格太冗余了,那让我们切换到restful会是什么样呢?对资源的操作无外乎增删改查,在restful中,每一个HTTP动词描述一种操作:

  • GET:对应查询操作
  • POST:对应增加操作
  • DELETE:对应删除操作
  • PUT:对应更新操作

这样我们只需要一个接口就行了

有的同学可能有疑问了,实际开发中很多查询操作都是使用post,而不是get,因为有些查询需要的参数特别多,如果使用get,接口会很难看,同时也不安全;那我这里就不是RESTful了吗?其实上面说过了,RESTful是一种软件架构风格,没有强制约束,也就是说只是建议开发者这样做,适当的变通没有问题

在知乎上还看到一种对RESTful的经典解释

看http url就知道要什么
看http method就知道干什么
看http status code就知道结果如何

前面两句和lvony说的一个意思,后面一句也很精髓,http状态很多,看到其中的一些状态码你就明白请求结果如何

  • 200 OK,请求成功。成功的含义取决于HTTP方法
  • 201 Created,该请求已成功,并因此创建了一个新的资源。这通常是在POST请求,或是某些PUT请求之后返回的响应。
  • 204,OK,资源删除成功
  • 304 Not Modified,资源没有更新,客户端可以使用缓存
  • 400 Bad Request,有两种情况:1.语义有误,当前请求无法被服务器理解;2.请求参数有误
  • 401 Unauthorized,当前请求需要用户验证
  • 403 Forbidden,服务器已经理解请求,但是拒绝执行它。与 401 响应不同的是,身份验证并不能提供任何帮助
  • 404 Not Found,请求失败,请求所希望得到的资源未被在服务器上发现
  • 414 URI Too Long,请求的URI 长度超过了服务器能够解释的长度,通常的情况包括:本应使用POST方法的表单提交变成了GET方法,导致查询字符串(Query String)过长
  • 500 Internal Server Error,服务器遇到了不知道如何处理的情况
  • 501 Not Implemented,此请求方法不被服务器支持且无法被处理。只有GET和HEAD是要求服务器支持的

初识 GraphQL

GraphQL中文官网:GraphQL
android版github地址:apollo-android
可视化查询器:graphiql

GraphQL 是 Facebook 于 2012 年在内部开发的数据查询语言,在 2015 年开源,旨在提供 RESTful 架构体系的替代方案
GraphQL可以看成是Graph+QL,其中QL是Query Language的简称,所以GraphQL的意思是可视化(图形化)查询语言,是一种描述客户端如何向服务端请求数据的API语法,类似于 RESTful API 规范
想想SQL(Structured Query Language) 是结构化查询语言的简称,你应该大概理解GraphQL是啥了,但是要注意,虽然GraphQL名字叫查询语言,但是跟数据库其实没啥关系

当数据从MySQL、NoSQL等数据库里面查出来,怎么给前端或者移动端呢?这时候GraphQL站出来了,它作用于数据接口,让客户端自由筛选获取服务端事先定好的数据,提高了api接口的灵活性,比如后端从数据查询出来A,B,C,D四个字段,使用GraphQL处理接口,这样客户端来获取数据,可以随意要哪几个字段,可以是A,可以是B,也可以是B,C等,不需要再跟服务端哥们打招呼了

再说说移动端使用GraphQL的原因吧:

了解了基于RSTful风格的网络请求,我们应该能知道某些情况下其实会产生一些弊端的,

  1. 一个请求对应一个资源,那么在一些网络请求比较多的页面就会带来比较大的负担,通常情况下应用的首页会承载比较多的内容,这样应用一打开,同时会有很多网络请求发送出去,在获取响应的时候就会带来比较复杂的处理,最明显的就是菊花(loading动画)什么时候该消失;
  2. 还有一个比较恶心的情况就是,一些api接口,在设计后它的参数和返回值就会确定,如果哪一天由于设计或者业务变动,需要修改接口字段或者返回值,这时候要是改动这个接口,新版本app是能用,但是老版本的app调用这个接口可能就会产生错误(总不能每次都强制用户升级吧),严重的直接app出现崩溃;不修改原接口,就得增加新的接口;
  3. 最烦的就是要维护接口文档了,在漫长的开发过程中,需要时刻根据接口变化去更新api文档,这个痛大家应该都懂。

当然了还有其它的缺点,这里就不细说了,而这些问题在apollo graphql中都不存在

  1. 在graphql里面,之前的多个请求这时候会变成一个请求,这样首页之前要同时调用四个接口,现在只需要调用一个接口就行了,再也不用担心菊花的问题了;
  2. 同时graphql里的字段和参数都可以自定义,之前是服务端定义好客户端传什么,返回什么给客户端;现在反过来了,客户端想传什么字段,需要哪些返回值由自己定义,这样api接口就十分灵活了,这样新版本想修改接口只需要增加字段就行了,不需要改动已存在的字段,再也不用担心版本兼容问题了;
  3. graphql还可以生成接口文档并支持导出,完美解决你的烦恼

重点:Graphql只是一种用于 API 的查询语言,相比于REST API,查询多个数据就需要多个URL不同,它可以做到用一个请求能准确的获取你所需要的所有数据,不多不少

使用 GraphQL

说了这么多,来看看怎么在Android项目里接入GraphQL吧

GraphQL有很多版本,如图
在这里插入图片描述

其中支持Android平台的库就是apollo-android,使用这个库要求gradle版本是5.1.1或者更高,一开始让我使用这个库的时候,我还担心它支持OKHttp吗?支持RxJava吗?后来正式接入的时候才发现是我多想了,这个库底层的网络模块使用的也是OKHttp,同时也支持RxJava,瞬间感觉:哟,小老弟,不错嘛

第一步:这里我新建一个工程,第一步要做的事就是在项目根目录下的build.gradle里添加插件依赖

    dependencies {
        classpath 'com.apollographql.apollo:apollo-gradle-plugin:1.2.1'
    }

第二步:接下来在app module的build.gradle里添加插件依赖和库依赖,记住插件依赖一定要在Android插件后面

apply plugin: 'com.apollographql.android'

dependencies {
    implementation 'com.apollographql.apollo:apollo-runtime:1.2.1'
}

第三步:如果要使用RxJava(肯定要使用咯),还需要添加apollo-rx2-support

dependencies {
    implementation ('com.apollographql.apollo:apollo-rx2-support:1.2.1')
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
}

rxjava这个库不需要添加,apollo-rx2-support库已经依赖了

第四步:因为apollo插件会根据.graphql文件和schema.json文件生成java代码,而代码生成器会使用到jetbrains annotations,所以还需要添加依赖

dependencies {
  compileOnly 'org.jetbrains:annotations:13.0'
  testCompileOnly 'org.jetbrains:annotations:13.0'//可选
}

接下来就是添加上面提到的.graphql文件和schema.json文件了,这两个配置文件实际开发中是由后端开发提供给你的,并且这两个文件要求一一对应

  • 在app module下的src/main下创建一个与你的java文件夹相同级别的文件夹,我把它命名为graphql,虽然官方说随便命名,但实际会有一些错误,所以还是用这个名字吧,然后再新建一个类似与com/mango/apollo的三层目录
  • 添加一个schema.json文件到刚才的目录下,这个名字不要改,定死了就这个;后端在部署成功后,会自动生成schema.json文件,是用来描述你的 GraphQL API、所有字段和输入参数等信息的文件,这个文件有点恐怖,即使很简单的一个接口,也能有几十k
  • 添加一个以.graphql为后缀的文件,名字你可以自己命名,它是一个GraphQL 查询(query)文件,这些查询会被 Apollo codegen 用来生成返回数据的数据结构,最重要的是你也可以在里面自定义查询方法,包括新建.graphql文件

.graphql文件和schema.json文件如果你没有,可以去官网上下载

接下来build工程,在app/build/generated/source/apollo/classes/debug下面找到生成的几个文件(通过官网的.graphql文件和schema.json文件生成的),如图
在这里插入图片描述
这里简单看下.graphql文件里面的内容

query FeedQuery($type: FeedType!, $limit: Int!) {
  feedEntries: feed(type: $type, limit: $limit) {
    id
    repository {
      ...RepositoryFragment
    }
    postedBy {
      login
    }
  }
}

fragment RepositoryFragment on Repository {
  name
  full_name
  owner {
    login
  }
}

先看第一句

query FeedQuery($type: FeedType!, $limit: Int!) 
  • query: 表示操作类型是query,也就是查询的意思;还有其它的类型比如mutation(表示增删改的意思) 或 subscription,描述你打算做什么类型的操作。操作类型是必需的,除非你使用查询简写语法
  • FeedQuery: 表示操作名称,是你的操作的有意义和明确的名称
  • $ type: FeedType!: 使用$符号定义变量,变量名称是type,变量类型是FeedType,加一个!意思非空

然后看第二句:

feedEntries: feed(type: $type, limit: $limit)
  • feedEntries:是feed的别名,最终返回体名就是feedEntries
  • feed:如果没有定义别名,那么返回体名就是feed
  • type: $ type:接受的参数,参数名是type,值是上面定义的变量$type

继续看里面的结构

{
    id
    repository {
      ...RepositoryFragment
    }
    postedBy {
      login
    }
  }
  • id,repository,postedBy:这几个都是字段,至于最终返回的数据中字段对应的类型是啥由服务端决定,比如id这个字段,返回的可能是string,也可能是int
  • repository字段指向一个Object类型,实际类型是RepositoryFragment,它的定义如下
    fragment RepositoryFragment on Repository {
      name
      full_name
      owner {
        login
      }
    }
    
    使用fragment修饰,当你的返回数据中字段比较多时,就可以使用fragment来组织一组字段,便于复用

第五步:初始化ApolloClient

以前我们都是使用Retrofit+OKHttp,现在变成了Apollo+OKHttp

        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .build();
        ApolloClient apolloClient = ApolloClient.builder()
                .serverUrl(BASE_URL)
                .okHttpClient(okHttpClient)
                .build();

第六步:发起请求

使用apollo构建请求和使用okhttp有点类似,还记得在okhttp里如何发起一个请求吗?是不是构建一个Request,然后实例化一个Call,最后发起请求;在apollo里也是这样

//Limit 和 Type是我们graphQL 查询中的动态参数,在.graphql文件里定义的
FeedQuery feedQuery = FeedQuery
                        .builder()
                        .limit(10)
                        .type(FeedType.HOT)
                        .build();
ApolloQueryCall<FeedQuery.Data> queryCall = apolloClient.query(feedQuery);
queryCall.enqueue(new ApolloCall.Callback<FeedQuery.Data>() {
    @Override
    public void onResponse(@NotNull Response<FeedQuery.Data> response) {
        FeedQuery.Data data = response.data();
        List<FeedQuery.FeedEntry> feedEntries = data.feedEntries();
        for (FeedQuery.FeedEntry feedEntry : feedEntries) {
            Log.i("MainActivity",""+feedEntry.toString());
        }

    }

    @Override
    public void onFailure(@NotNull ApolloException e) {
        Log.i("MainActivity","onFailure " + e.getMessage());
    }
});

响应中的FeedQuery.FeedEntry内容如下

feedEntries={
            id = 105,
            repository = Repository {
                    fragments = Fragments {
                        repositoryFragment = RepositoryFragment {
                            name = pairhub,
                            full_name = pairhub / pairhub,
                            owner = Owner {
                                login = pairhub
                            }
                        }
                    }
            },
            postedBy = PostedBy {
                login = gustavlrsn
            }
        }

第七步:结合RxJava使用如下:

FeedQuery feedQuery = FeedQuery
                        .builder()
                        .limit(10)
                        .type(FeedType.HOT)
                        .build();
        Rx2Apollo.from(apolloClient.query(feedQuery))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<Response<FeedQuery.Data>>(){

                    @Override
                    public void onNext(Response<FeedQuery.Data> dataResponse) {
                        FeedQuery.Data data = dataResponse.data();
                        List<FeedQuery.FeedEntry> feedEntries = data.feedEntries();
                        for (FeedQuery.FeedEntry feedEntry : feedEntries) {
                            Log.i("MainActivity","feedEntry:"+feedEntry.toString());
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("MainActivity","onError " + e.getMessage());
                    }

                    @Override
                    public void onComplete() {

                    }
                });

主要是将ApolloCall(ApolloQueryCall的父类)转成Observable类型,还有其它类型转换,如下表

Apollo typeRxJava2 type
ApolloCallObservable<Response>
ApolloSubscriptionCallObservable<Response>
ApolloQueryWatcherObservable<Response>
ApolloStoreOperationSingle
ApolloPrefetchCompletable

转换例子如下:

ApolloCall<FeedQuery.Data> apolloCall = apolloClient.query(query);
Observable<Response<FeedQuery.Data>> observable = Rx2Apollo.from(apolloCall);

ApolloPrefetch<FeedQuery.Data> apolloPrefetch = apolloClient.prefetch(query);
Completable completable = Rx2Apollo.from(apolloPrefetch);

也可以转成Disposable 类型

Disposable disposable = Rx2Apollo.from(query).subscribe();
disposable.dispose();

总结

关于graphql的使用还有很多,比如支持数据库缓存,http缓存,文件上传等,它能极大的简化我们的接口请求及让接口定义更加的自由,开放;在后续如果有时间,再出几篇结合实际开发经历使用graphql的文章。

参考文章:
HTTP 响应代码
GraphQL的核心概念(译)

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页