網絡請求只會用Retrofit?外國人已經在用Apollo Graphql了

 背景 

跳槽快兩個月了,到了一家新公司,正準備開幹,結果發現公司服務端用的是apollo-graphql來處理客戶端網絡請求,就要求客戶端也用這個graphql,當時還挺奇怪的,畢竟以前android客戶端用的都是retrofit+okhttp+rxjava這一套,我特麼還吭哧吭哧的將它們封裝了一套自認爲挺不錯的網絡層架構。結果來到新公司發現就這麼用不上了(其實一部分是用的上的,後面再說);如今幹Android開發的,處理網絡請求這塊,不說百分百,也有個百分之七八十用的都是這一套吧,其它的基本也是OKHttp的封裝庫;儘管個人心裏裏有一萬隻草泥馬在奔騰,但仍是得迎難而上啊。不過有一說一,網上關於在Android上使用graphql的文章真的少的可憐,其實從這個庫的star數量也能看出來。
阿里P6P7【安卓】進階資料分享+加薪跳槽必備面試題前端

你們要是使用過Postman的話,不知有沒有注意到它也開始支持graphql了。java

(透露下,其實apollo-android庫網絡層也是使用的OKHttp,可見OKHttp的強大啊)react

 回顧 RESTful   

在理解graphql以前先闡述下RESTful,由於graphql產生的緣由就是想替代RESTful。咱們都知道服務端如今大部分使用的都是基於RESTful的設計風格和開發方式,固然客戶端也是這樣(Retrofit就是一個相似於RESTful的網絡框架),那到底怎麼理解RESTful呢?首先REST不是英語單詞rest(休息)的意思,而是Representational State Transfer的簡稱,即表現層狀態轉移,其中REST指的是一組架構約束條件和原則,只要某個架構符合REST的約束條件和原則,那就能夠稱爲RESTful架構,它是一種軟件架構風格,而不是標準,也就是說不是強制的。看完這些拗口的術語你可能仍是不太清楚,用知乎大神lvony的一句話歸納。android

用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動詞描述一種操做:json

  • GET:對應查詢操做
  • POST:對應增長操做
  • DELETE:對應刪除操做
  • PUT:對應更新操做

這樣咱們只須要一個接口就好了。有的同窗可能有疑問了,實際開發中不少查詢操做都是使用post,而不是get,由於有些查詢須要的參數特別多,若是使用get,接口會很難看,同時也不安全;那我這裏就不是RESTful了嗎?其實上面說過了,RESTful是一種軟件架構風格,沒有強制約束,也就是說只是建議開發者這樣作,適當的變通沒有問題。在知乎上還看到一種對RESTful的經典解釋。後端

看http url就知道要什麼
看http method就知道幹什麼
看http status code就知道結果如何

前面兩句和lvony說的一個意思,後面一句也很精髓,http狀態不少,看到其中的一些狀態碼你就明白請求結果如何。api

  • 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 是 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文件

接下來build工程,在app/build/generated/source/apollo/classes/debug下面找到生成的幾個文件(經過官網的.graphql文件和schema.json文件生成的),如圖:image

這裏簡單看下.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類型,還有其它類型轉換,以下表:

轉換例子以下:

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緩存,文件上傳等,它能極大的簡化咱們的接口請求及讓接口定義更加的自由,開放。
阿里P6P7【安卓】進階資料分享+加薪跳槽必備面試題

相關文章
相關標籤/搜索