作者: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的支持。

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)行在不同線程,時序模型如下圖。

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種組合方式,即HttpClient、UnityWebRequestImpl 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é)合使用
- 導(dǎo)入BestHttp 插件.
- 導(dǎo)入Retrofit for Unity.
- 導(dǎo)入在Integrations目錄下的
BestHttpImpl.unitypackage.
Alt text - 用BestHttpImpl實(shí)例作為RestAdapter中的SetHttpImpl()方法的返回值.
protected override HttpImplement SetHttpImpl()
{
return new BestHttpImpl();
}
與 UNITYWEBREQUEST的結(jié)合
要想使用 UnityWebRequest, unity 版本必須是5.4或更高.
- 導(dǎo)入Retrofit for Unity.
- 導(dǎo)入在Integrations目錄下的
UnityWebRequestImpl.unitypackage.
Alt text - 用UnityWebRequestImpl實(shí)例作為RestAdapter中的SetHttpImpl()方法的返回值.
protected override HttpImplement SetHttpImpl()
{
return new UnityWebRequestImpl();
}

