如何優(yōu)雅的在Unity中發(fā)送REST API請求

作者:sp958857
在Unity中經(jīng)常使用的網(wǎng)絡(luò)請求模塊,有官方的UnityWebRequest模塊,BestHttp第三方插件等。他們的使用方式都大同小異,通過Url的拼接處理,提供各種方法來應(yīng)對REST請求的各種模式(Get/Post/Delete/Patch)與參數(shù),使用的代碼如下

//BestHttp
HTTPRequest request = new HTTPRequest(new Uri("http://server.com/path"),HTTPMethods.Post,OnRequestFinished);
    request.AddField("FieldName", "Field Value");
    request.Send();
//official UnityWebRequest module
 WWWForm form = new WWWForm();
        form.AddField("myField", "myData");
        using (UnityWebRequest www = UnityWebRequest.Post("http://www.my-server.com/myform", form))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError || www.isHttpError)
            {
                Debug.Log(www.error);
            }
            else
            {
                Debug.Log("Form upload complete!");
            }
        }

如果項(xiàng)目中的REST API請求的數(shù)量變大的情況下,就會出現(xiàn)幾鐘情況:

  • Http請求的代碼的代碼分散在工程中
  • 如果把請求代碼集中放置,這個類會非常巨大,將包含各種涉及底層請求模塊的代碼,如www.SendWebRequest()request.AddField("FieldName", "Field Value");
  • 如果涉及多線程請求處理,例如在子線程發(fā)送請求,在主線程更新UI,那么代碼將變得更加難以集中統(tǒng)一維護(hù)

在Android開發(fā)中,得到大量應(yīng)用和驗(yàn)證的網(wǎng)絡(luò)請求庫Retrofit,將REST API變成一個個的接口定義,在業(yè)務(wù)代碼中可以像調(diào)用方法一樣去請求API函數(shù),代碼將變得非常優(yōu)美和容易維護(hù)。
那么在Unity中有沒有這樣的庫呢,奈何目前是沒有的。.Net版本的Retrofit倒是有一些,例如refit、Retrofit.Net,可惜的是這些版本都是基于.Net 4.5開發(fā)的,并不適用與大多數(shù)基于.NET 3.5運(yùn)行時開發(fā)的Unity工程。
在這種情況下,只能自己寫一個適用于Unity .NET 3.5 工程的Retrofit模塊了。

Retrofit for Unity

實(shí)現(xiàn)過程中,盡量保持跟Java版本一致,得益于Java跟C#語言基本類似,很多實(shí)現(xiàn)基本沒有大礙,例如Java的注解對應(yīng)C#的特性。但是有一些語言特性是C#沒有的,例如Java的類動態(tài)代理,在C#是沒有的,就會在定義Client的REST接口的時候產(chǎn)生一些差別。
下面著重介紹下該模塊的使用方式。

簡介

Retrofit for Unity把Http API描述變成接口直接使用。下面是一個REST API的接口化定義

 public interface IHttpBinInterface
    {
        [Get("/get")]
        IObservable<HttpBinResponse> Get(
            [Query("query1")]string arg1,
            [Query("query2")]string arg2
            );
     }

如何使用

3個步驟:
-[1] 定義一個Interface:該接口用來管理你的 HTTP API。
-[2] 繼承 RestAdapter 并實(shí)現(xiàn)該接口: 在接口方法實(shí)現(xiàn)中只需要調(diào)用 SendRequest(args...) 即可。

public class HttpBinService:RestAdapter,IHttpBinInterface
    {
        private static HttpBinService _instance;

        public static HttpBinService Instance
        {
            get
            {
                if (_instance == null)
                {
                    var go = new GameObject("HttpBinService");
                    _instance = go.AddComponent<HttpBinService>();
                }
                return _instance;
            }
        }
  
        protected override HttpImplement SetHttpImpl()
        {
            var httpImpl = new HttpClientImpl();
            httpImpl.EnableDebug = true;
            return httpImpl;
        }

        protected override void SetRestAPI()
        {
            baseUrl = "http://httpbin.org";
            iRestInterface = typeof (IHttpBinInterface);
        }


        public IObservable<HttpBinResponse> Get(string arg1, string arg2)
        {
            return SendRequest<HttpBinResponse>(arg1,arg2) as IObservable<HttpBinResponse>;
        }
    }

在Java版本的Retrofit中不需要這一步的原因就是使用了類的動態(tài)代理:
GitHubService service = retrofit.create(GitHubService.class);//GitHubService為Interface
由于語言特性的差別,C#沒有這個特性,且Unity中官方請求模塊UnityWebRequest和WWW都是基于協(xié)程實(shí)現(xiàn)的,而協(xié)程只能在MonoBehavior中使用,因此為了兼容官方模塊,RestAdapter作為通用基類適配器需要繼承MonoBehavior,業(yè)務(wù)請求服務(wù)直接繼承RestAdapter,并實(shí)現(xiàn)REST接口的方法。

-[3] 調(diào)用方法: HttpBinService將負(fù)責(zé)把你的方法調(diào)用變成向服務(wù)端發(fā)送的Http網(wǎng)絡(luò)請求。

 var ob = HttpBinService.Instance.Get("abc", "123");
     ob.SubscribeOn(Scheduler.ThreadPool)//send request in sub thread
          .ObserveOn(Scheduler.MainThread)//receive response in main thread
          .Subscribe(data =>
              {
                   // onSuccess
                   Debug.LogFormat("Received on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
              },
              error =>
              {
                  Debug.Log("Retrofit Error:" + error);
              });

在Java版本的Retrofit中借助RxJava來實(shí)現(xiàn)異步的請求。好在Unity版本中也有Reactive Extension的擴(kuò)展 UniRx,借助UniRx,可以快速實(shí)現(xiàn)Retrofit for Unity的異步請求。

API 聲明

在接口的方法中使用C# 特性來標(biāo)識一個請求將被怎么發(fā)送,特性可以被標(biāo)識在方法和方法參數(shù)上。

請求方式

每一個方法必須要有一個Http方法類型的特性來提供請求方式以及相對地址,該組件提供6種內(nèi)置的特性來標(biāo)識GET, POST, PUT, DELETE, PATCH and HEAD請求,相對地址在特性中標(biāo)識即可。

 [Get("/users")]

當(dāng)然,也可以直接在URL地址中使用查詢URL。

[GET("/users/list?sort=desc")]

URL動態(tài)組裝

URL可以通過中括號+變量名來動態(tài)組裝,如{any string},同時,用來動態(tài)替換URL的參數(shù)必須使用 [Path]特性來標(biāo)識,并且需要使用相同的string值。

[Get("/group/{id}/users")]
IObservable<<List<User>> GetList(
    [Path("id")] int groupId);

Query查詢語句也可以使用同樣的方式來組裝動態(tài)查詢URL。

[Get("/group/{id}/users")]
IObservable<<List<User>> GetList(
    [Path("id")] int groupId,
    [Query("sort")] string sort);

對于更復(fù)雜的查詢,建議使用QueryMap來實(shí)現(xiàn)。并且通過這種方式可以實(shí)現(xiàn)數(shù)量可變的查詢URL。

[Get("/group/{id}/users")]
IObservable<<List<User>>GroupList(
    [Path("id")] int groupId,
    [QueryMap] Dictionary<string, string> options);

帶BODY的請求

一個Body對象可以通過 [Body] 特性來完成帶Body的請求。

[POST("/users/new")]
IObservable<User> CreateUser(
    [Body] User user);

該object對象將使用RestAdapter中指定的轉(zhuǎn)換器Convert來把對象轉(zhuǎn)換成string并發(fā)往服務(wù)端,默認(rèn)情況下,RestAdapter將使用DefalutConverter(Implemented by Newtonsoft.json) 來完成轉(zhuǎn)換任務(wù)。

FORM ENCODED請求

方法也可以聲明為發(fā)送form-encoded 數(shù)據(jù)的請求。
當(dāng)有參數(shù)使用[Field]特性標(biāo)識時,[Field]中的字符將作為健,參數(shù)值將作為值來發(fā)送form-encoded數(shù)據(jù)。

[POST("/user/edit")]
IObservable<User> UpdateUser(
    [Field("first_name")] string first,
    [Field("last_name")] string last);

HEADER 的組裝

可以使用 [Headers] 特性來標(biāo)識靜態(tài)的Header數(shù)據(jù)

[Headers("Cache-Control: max-age=640000")]
[GET("/widget/list")]
IObservable<List<Widget>> WidgetList();
[Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})]
[GET("/users/{username}")]
IObservable<User> GetUser(
    [Path("username")] string username);

注意:Headers沒有同健覆蓋功能,即使Headers的健相同也會一起發(fā)送出去。
如果需要動態(tài)組裝Header,可以在參數(shù)中用[Header]來標(biāo)識這是一個動態(tài)參數(shù)化的Header組裝。

[GET("/user")]
IObservable<User> GetUser(
    [Header("Authorization")] string authorization)

Retrofit 配置

RestApater 類負(fù)責(zé)將API接口轉(zhuǎn)換成可以調(diào)用方法的對象。默認(rèn)情況下Retrofit會自動配置好供你調(diào)用,當(dāng)然所有的配置都是可以自定義的,這是為了方便擴(kuò)展以及功能細(xì)分。

序列化和反序列化配置

Retrofit默認(rèn)使用 Newtonsoft.json 庫來完成序列化和反序列化。
如果API接口返回的結(jié)構(gòu)不能被默認(rèn)配置的解析庫解析,或者你想要用別的庫來完成轉(zhuǎn)換工作,那么這個時候,你只需要創(chuàng)建一個類并實(shí)現(xiàn) Converter 接口,并在Adapter的Setup()中指定這個實(shí)例來完成轉(zhuǎn)換的工作即可。

結(jié)合Rx的異步網(wǎng)絡(luò)請求實(shí)踐

 var observable = RxRestBestHttpClient.Instance.RxGetList("square", "retrofit");//聲明可觀察對象
     observable
            .SubscribeOn(Scheduler.ThreadPool) //將網(wǎng)絡(luò)請求切換到預(yù)定義的線程池中的某個線程執(zhí)行
            .ObserveOnMainThread()  //請求完成時,在主線程呈現(xiàn)數(shù)據(jù)
            .Subscribe(
                data =>
                {
                    Debug.LogFormat("Receive on threadId:{0}", Thread.CurrentThread.ManagedThreadId);
                    Debug.Log("Retrofit Success:" + data[0].testField);
                },
                error =>
                {
                    Debug.LogError("Retrofit Error:" + error);
                });

定義可觀察對象后observable之后,通過observable.Subcribe(Action<T> onNext, Action<Exception> onError)的調(diào)用后,網(wǎng)絡(luò)請求才會發(fā)送出去,默認(rèn)情況下,如果沒有調(diào)用SubscribeOn()ObserveOn(),請求線程跟回調(diào)線程都是在主線程完成。

Scheduler+Linq多線程請求與切換

使用Scheduler完成多線程請求與回調(diào)切換

在上面的例子種可以看到IObserable在被訂閱前使用了2個方法:SubscribeOn(Scheduler.ThreadPool)ObserveOnMainThread(),實(shí)際上他們是使用了IObserable<T>的擴(kuò)展方法SubscribeOnand ObserveOn,

1/SubscribeOn 告訴Rx可觀察對象的產(chǎn)生運(yùn)行在哪個線程。
2/ObserveOn告訴Rx數(shù)據(jù)流的觀察處理運(yùn)行在哪個線程。

所以如果需要做一些耗時操作,例如網(wǎng)絡(luò)請求和文件讀寫都可以通過SubscribeOn來將他們放到異步線程去處理。然后通過ObserveOn來在主線程處理回調(diào)的數(shù)據(jù)。中間不需要關(guān)心線程安全的問題,Rx已經(jīng)在底層幫我們處理好了。

注意:如果SubcribeOn調(diào)用之后,不調(diào)用ObserveOn,那么觀察也會在運(yùn)行在之前切換到的子線程。并不會自動切換回主線程處理觀察數(shù)據(jù)。

使用Linq擴(kuò)展操作符簡化開發(fā)

Rx除了支持LINQ的原生操作符,并且拓展了許多很實(shí)用的操作符,并且可以支持自定義操作符,操作符的數(shù)量在源源不斷的添加中。具體請參考文檔,這里列舉幾個業(yè)務(wù)模塊中經(jīng)常使用到場景。

線程串聯(lián)

在業(yè)務(wù)開發(fā)中,遇到需要先請求X,通過X的響應(yīng)數(shù)據(jù),再請求Y,通過Y的響應(yīng)數(shù)據(jù),最后請求Z,時序模型如下圖。
可以通過Linq語法這樣寫,Rx已經(jīng)內(nèi)部實(shí)現(xiàn)對Linq的支持。


Alt text
var quert = from x in ObserableWWW.Get("http://github.com");
            from y in ObserableWWW.Get(x)
            from z in ObserableWWW.Get(y)
            select new {x,y,z}
query.Subcribe(x=>Debug.Log(x),ex=>Debug.LogException(ex));
線程合并

在業(yè)務(wù)開發(fā)中,遇到需要先請求A和B,需要A和B的請求都返回后進(jìn)行下一步,任意一個請求出錯下一步都無法執(zhí)行,并且只有2個網(wǎng)絡(luò)請求都返回結(jié)果之后才進(jìn)行下一步的處理,而且A和B兩個網(wǎng)絡(luò)請求沒有關(guān)聯(lián),運(yùn)行在不同線程,時序模型如下圖。


Alt text
var query = Observable.Zip(
            ObserableWWW.Get("http://github.com")),
            ObserableWWW.Get("http://google.com")),
            (github,google) => new {goole,bing});
            
query.Subcribe(x=>Debug.Log(x),ex=>Debug.LogException(ex));

這里只需要通過一個擴(kuò)展操作符Zip來實(shí)現(xiàn)即可,完成了線程等待和線程合并的功能。

更多操作符

Rx提供了許多操作符來簡化程序員的工作,上面介紹的只是冰山一角,更多的操作符在官網(wǎng)有介紹,并且大部分UniRx都可以支持,如果在遇到一個業(yè)務(wù)場景的時候,不知道選擇哪個操作符可以實(shí)現(xiàn)我們的功能,那么可以參考A Decision Tree of Observable Operators,來輔助選擇操作符

與不同Http模塊的結(jié)合

Retrofit只是一種代碼風(fēng)格的設(shè)計,可以美化代碼及方便管理API。它并不會真正的發(fā)送任何一個Http網(wǎng)絡(luò)請求。這些工作都交給底層的網(wǎng)絡(luò)請求庫來實(shí)現(xiàn),這也就意味著Retrofit for Unity 可以跟各種各樣的網(wǎng)絡(luò)請求插件或者是Unity官方的 UnityWebRequest模塊來結(jié)合使用。默認(rèn)情況下,該組件提供3種組合方式,即HttpClientUnityWebRequestImpl and BestHttpImpl,默認(rèn)情況下使用HttpClient作為底層網(wǎng)絡(luò)請求模塊,因?yàn)樗且粋€基于System.Net的非常簡潔的RESTful API的網(wǎng)絡(luò)請求模塊,并且完全開源。如果你需要跟其他網(wǎng)絡(luò)插件結(jié)合使用,那么只需要實(shí)現(xiàn)HttpImplement接口,并在Adapter的Setup()中指定一個實(shí)例即可。

與 BESTHTTP的結(jié)合使用

  1. 導(dǎo)入BestHttp 插件.
  2. 導(dǎo)入Retrofit for Unity.
  3. 導(dǎo)入在Integrations目錄下的BestHttpImpl.unitypackage .
    Alt text
  4. 用BestHttpImpl實(shí)例作為RestAdapter中的SetHttpImpl()方法的返回值.
 protected override HttpImplement SetHttpImpl()
        {
            return new BestHttpImpl();
        }

與 UNITYWEBREQUEST的結(jié)合

要想使用 UnityWebRequest, unity 版本必須是5.4或更高.

  1. 導(dǎo)入Retrofit for Unity.
  2. 導(dǎo)入在Integrations目錄下的UnityWebRequestImpl.unitypackage.
    Alt text
  3. 用UnityWebRequestImpl實(shí)例作為RestAdapter中的SetHttpImpl()方法的返回值.
 protected override HttpImplement SetHttpImpl()
        {
            return new UnityWebRequestImpl();
        }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,057評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評論 19 139
  • 穿著帶著彼岸花圖案的藏青色裙子,遇見了曼陀羅花,也遇見了你我那么無言的結(jié)局…
    奈何橋邊道奈何閱讀 248評論 0 0
  • 記得高三的時候,班上轉(zhuǎn)來了一個補(bǔ)習(xí)生。班主任將他領(lǐng)進(jìn)教師時,他低著頭,有些靦腆。班主任向我們介紹完他后,他終于...
    冷昕閱讀 375評論 0 1
  • 站在2016年的尾巴上,曾經(jīng)花了一個下午的時間,在咖啡廳里寫下自己的愿望清單,幾乎是想到了什么就寫上。然后又花了一...
    欣欣向榮的小天地閱讀 452評論 2 3

友情鏈接更多精彩內(nèi)容