kyuskoj 发表于 2022-11-23 10:02

UnityWebRequest 源码分析

一、概念
UnityWebRequest提供了一个用于编写HTTP请求和处理HTTP响应的模块化系统。UnityWebRequest系统的主要目标是允许Unity游戏与Web浏览器后端进行交互。它还支持高需求的功能,如分块的HTTP请求,流式POST / PUT操作以及对HTTP标头和动词的完全控制。

二、常用操作
1、 从HTTP服务器检索文本或二进制数据
要从标准HTTP或HTTPS Web服务器检索简单数据(如文本数据或二进制数据),请使用该UnityWebRequest.GET调用。该函数将单个字符串作为参数,字符串指定从中检索数据的URL。

例子:
public class Example : MonoBehaviour {
    void Start() {
      StartCoroutine(GetText());
    }

    IEnumerator GetText() {
      UnityWebRequest www = UnityWebRequest.Get("403 Forbidden");
      yield return www.SendWebRequest();

      if(www.isNetworkError || www.isHttpError) {
            Debug.Log(www.error);
      }
      else {
            // Show results as text
            Debug.Log(www.downloadHandler.text);

            // Or retrieve results as binary data
            byte[] results = www.downloadHandler.data;
      }
    }
}
2、 从HTTP服务器检索纹理
要从远程服务器检索纹理文件,可以使用UnityWebRequest.Texture.此功能与UnityWebRequest.GET下载和存储纹理效果非常相似,但经过优化。该函数将一个字符串作为参数。该字符串指定您希望从中下载图像文件以用作纹理的URL。
例子:
public class Example : MonoBehaviour {
    void Start() {
      StartCoroutine(GetTexture());
    }

    IEnumerator GetTexture() {
      UnityWebRequest www = UnityWebRequestTexture.GetTexture("http://www.xxxx.com/image.png");
      yield return www.SendWebRequest();

      if(www.isNetworkError || www.isHttpError) {
            Debug.Log(www.error);
      }
      else {
            Texture myTexture = ((DownloadHandlerTexture)www.downloadHandler).texture;
      }
    }
}3、 从HTTP服务器下载AssetBundle
要从远程服务器下载AssetBundle,可以使用UnityWebRequest.GetAssetBundle。此函数将数据流式传输到内部缓冲区,该缓冲区解码并解压缩工作线程上的AssetBundle数据。
函数的参数有几种形式。以最简单的形式,它只需要从中下载AssetBundle的URL。您可以选择提供校验和来验证下载数据的完整性。
例子:
public class Example : MonoBehaviour {
    void Start() {
      StartCoroutine(GetAssetBundle());
    }

    IEnumerator GetAssetBundle() {
      UnityWebRequest www = UnityWebRequest.GetAssetBundle("http://www.xxxxx.com/xxx.bundle");
      yield return www.SendWebRequest();

      if(www.isNetworkError || www.isHttpError) {
            Debug.Log(www.error);
      }
      else {
            AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(www);
      }
    }
}
4、 将表单发送到HTTP服务器
例子:
public class Example : public MonoBehaviour {
    void Start() {
      StartCoroutine(Upload());
    }

    IEnumerator Upload() {
      WWWForm form = new WWWForm();
      form.AddField("myField", "myData");

      UnityWebRequest www = UnityWebRequest.Post("403 Forbidden", form);
      yield return www.SendWebRequest();

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

5、 将原始数据上传到HTTP服务器
例子:
public class Example : MonoBehaviour {
    void Start() {
      StartCoroutine(Upload());
    }

    IEnumerator Upload() {
      byte[] myData = System.Text.Encoding.UTF8.GetBytes("This is some test data");
      UnityWebRequest www = UnityWebRequest.Put("http://www.xxxx.com/upload", myData);
      yield return www.SendWebRequest();

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

三、高级操作
现在我们应用底层API创建对象,可以提供更大的灵活性。

1、创建UnityWebRequest

UnityWebRequest可以像任何其他对象一样实例化。有两个构造函数可用:
标准的无参数构造函数创建一个新的UnityWebRequest,其中所有设置为空白或默认。目标网址未设置,未设置自定义标题,并且重定向限制设置为32。
第二个构造函数接受一个字符串参数。它将UnityWebRequest的目标URL分配给字符串参数的值,否则与无参数构造函数相同。
多个其他属性可用于设置,跟踪状态和检查结果或UnityWebRequest。
UnityWebRequest wr = new UnityWebRequest(); // Completely blank
UnityWebRequest wr2 = new UnityWebRequest("http://www.xxxx.com"); // Target URL is set

// the following two are required to web requests to work
wr.url = "403 Forbidden";
wr.method = UnityWebRequest.kHttpVerbGET;   // can be set to any custom method, common constants privided

wr.useHttpContinue = false;
wr.chunkedTransfer = false;
wr.redirectLimit = 0;// disable redirects
wr.timeout = 60;       // don't make this small, web requests do take some time

2、创建UploadHandlers
(1)UploadHandlerRaw:这个类在构造时接受数据缓冲区。该缓冲区在内部复制到本机代码存储器中,然后UnityWebRequest在远程服务器准备好接受正文

    byte[] payload = new byte;
    // ... fill payload with data ...

    UnityWebRequest wr = new UnityWebRequest("http://www.xxxxx.com/data-upload");
    UploadHandler uploader = new UploadHandlerRaw(payload);

    // Sends header: "Content-Type: custom/content-type";
    uploader.contentType = "custom/content-type";

    wr.uploadHandler = uploader;
(2)UploadHandlerFile: 它从给定的文件中读取数据,并将原始字节作为请求体发送到服务器

public class Example : MonoBehaviour
{
    void Start()
    {
      StartCoroutine(UploadFileData());
    }

    IEnumerator UploadFileData()
    {
      using (var uwr = new UnityWebRequest("https://xxxx.com/upload", UnityWebRequest.kHttpVerbPUT))
      {
            uwr.uploadHandler = new UploadHandlerFile("/path/to/file");
            yield return uwr.SendWebRequest();
            if (uwr.result != UnityWebRequest.Result.Success)
                Debug.LogError(uwr.error);
            else
            {
                // file data successfully sent
            }
      }
    }
}
3、创建DownloadHandlers

(1)DownloadHandlerBuffer 用于简单的数据存储。
public class Example : MonoBehaviour {
    void Start() {
      StartCoroutine(GetText());
    }

    IEnumerator GetText() {
      UnityWebRequest www = new UnityWebRequest("http://www.xxx.com");
      www.downloadHandler = new DownloadHandlerBuffer();
      yield return www.SendWebRequest();

      if(www.isNetworkError || www.isHttpError) {
            Debug.Log(www.error);
      }
      else {
            // Show results as text
            Debug.Log(www.downloadHandler.text);

            // Or retrieve results as binary data
            byte[] results = www.downloadHandler.data;
      }
    }
}(2)DownloadHandlerFile 用于下载文件并将其保存到内存较少的磁盘。
public class Example : MonoBehaviour {

    void Start () {
      StartCoroutine(DownloadFile());
    }

    IEnumerator DownloadFile() {
      var uwr = new UnityWebRequest("https://unity3d.com/", UnityWebRequest.kHttpVerbGET);
      string path = Path.Combine(Application.persistentDataPath, "unity3d.html");
      uwr.downloadHandler = new DownloadHandlerFile(path);
      yield return uwr.SendWebRequest();
      if (uwr.isNetworkError || uwr.isHttpError)
            Debug.LogError(uwr.error);
      else
            Debug.Log("File successfully downloaded and saved to " + path);
    }
}(3)DownloadHandlerTexture 用于下载图像。


public class Example : MonoBehaviour {
    UnityEngine.UI.Image _img;

    void Start () {
      _img = GetComponent<UnityEngine.UI.Image>();
      Download("http://www.xxxx.com/myimage.png");
    }

    public void Download(string url) {
      StartCoroutine(LoadFromWeb(url));
    }

    IEnumerator LoadFromWeb(string url)
    {
      UnityWebRequest wr = new UnityWebRequest(url);
      DownloadHandlerTexture texDl = new DownloadHandlerTexture(true);
      wr.downloadHandler = texDl;
      yield return wr.SendWebRequest();
      if(!(wr.isNetworkError || wr.isHttpError)) {
            Texture2D t = texDl.texture;
            Sprite s = Sprite.Create(t, new Rect(0, 0, t.width, t.height),
                                     Vector2.zero, 1f);
            _img.sprite = s;
      }
    }
}(4)DownloadHandlerAssetBundle 用于提取AssetBundles。
public class Example : MonoBehaviour {
    void Start() {
      StartCoroutine(GetAssetBundle());
    }

    IEnumerator GetAssetBundle() {
      UnityWebRequest www = new UnityWebRequest("http://www.xxxx.com");
      DownloadHandlerAssetBundle handler = new DownloadHandlerAssetBundle(www.url, uint.MaxValue);
      www.downloadHandler = handler;
      yield return www.SendWebRequest();

      if(www.isNetworkError || www.isHttpError) {
            Debug.Log(www.error);
      }
      else {
            // Extracts AssetBundle
            AssetBundle bundle = handler.assetBundle;
      }
    }
}(5)DownloadHandlerAudioClip 用于下载音频文件。

public class Example : MonoBehaviour {

    void Start () {
      StartCoroutine(GetAudioClip());
    }

    IEnumerator GetAudioClip() {
      using (var uwr = UnityWebRequestMultimedia.GetAudioClip("http://xxxxx.com/xxx.ogg", AudioType.OGGVORBIS)) {
            yield return uwr.SendWebRequest();
            if (uwr.isNetworkError || uwr.isHttpError) {
                Debug.LogError(uwr.error);
                yield break;
            }

            AudioClip clip = DownloadHandlerAudioClip.GetContent(uwr);
            // use audio clip
      }
    }   
}(6)DownloadHandlerMovieTexture 用于下载视频文件。
public class Example : MonoBehaviour {

    void Start () {
      StartCoroutine(GetAudioClip());
    }

    IEnumerator GetAudioClip() {
      using (var uwr = UnityWebRequestMultimedia.GetMovieTexture("http://xxxx.com/xxxxx.ogg")) {
            yield return uwr.SendWebRequest();
            if (uwr.isNetworkError || uwr.isHttpError) {
                Debug.LogError(uwr.error);
                yield break;
            }

            MovieTexture movie = DownloadHandlerMovieTexture.GetContent(uwr);
            // use movie texture
      }
    }   
}(7)DownloadHandlerScript是一个特殊的类。
对于需要完全控制下载数据处理的用户,Unity提供DownloadHandlerScript该类。
默认情况下,这个类的实例什么都不做。但是,如果您从中派生自己的类DownloadHandlerScript,则可能会覆盖某些函数,并在数据从网络到达时使用它们来接收回调。

注意:实际下载发生在工作线程上,但所有DownloadHandlerScrpt回调都在主线程上运行。避免在这些回调期间执行计算量大的操作。
protected void ReceiveContentLength(long contentLength);
这个函数在收到Content-Length头时被调用。请注意,如果您的服务器在处理UnityWebRequest的过程中发送一个或多个重定向响应,则可能会多次发生此回调。
protected void OnContentComplete();
当UnityWebRequest从服务器完全下载所有数据并将所有接收到的数据转发给ReceiveData回调时,将调用此函数。
protected bool ReceiveData(byte[] data, long dataLength);
该数据从远程服务器到达后调用,每帧调用一次。该data参数包含从远程服务器接收到的原始字节,并dataLength指示数据数组中新数据的长度。

当不使用预先分配的数据缓冲区时,系统每次调用此回调时都会创建一个新的字节数组,并且dataLength始终等于data.Length。使用预先分配的数据缓冲区时,数据缓冲区将被重用,并且dataLength必须用于查找更新的字节数。

该函数需要返回值为true或false。如果您返回false,系统将立即中止UnityWebRequest。如果返回true,则处理正常继续。
public class Example : DownloadHandlerScript {

    // Standard scripted download handler - allocates memory on each ReceiveData callback

    public Example(): base() {
    }

    // Pre-allocated scripted download handler
    // reuses the supplied byte array to deliver data.
    // Eliminates memory allocation.

    public Example(byte[] buffer): base(buffer) {
    }

    // Required by DownloadHandler base class. Called when you address the 'bytes' property.

    protected override byte[] GetData() { return null; }

    // Called once per frame when data has been received from the network.

    protected override bool ReceiveData(byte[] data, int dataLength) {
      if(data == null || data.Length < 1) {
            Debug.Log("Example :: ReceiveData - received a null/empty buffer");
            return false;
      }

      Debug.Log(string.Format("Example :: ReceiveData - received {0} bytes", dataLength));
      return true;
    }

    // Called when all data has been received from the server and delivered via ReceiveData.

    protected override void CompleteContent() {
      Debug.Log("Example :: CompleteContent - DOWNLOAD COMPLETE!");
    }

    // Called when a Content-Length header is received from the server.

    protected override void ReceiveContentLength(int contentLength) {
      Debug.Log(string.Format("Example :: ReceiveContentLength - length {0}", contentLength));
    }
}

四、以上UnityWebRequest相关的类和功能就差不多了,我画了几张图做个总结:
UnityWebRequest继承关系如下


这里UnityWebRequestBase是一个宏定义,会根据不同平台而不同,比如安卓平台就是 UnityWebRequestAndroid类,IOS平台就是UnityWebRequestiPhone类。这两个类都是通过原生网络接口进行调用。
当然如果定义了UNITYWEBREQUEST_USE_CURL宏就宏统一使用curl进行处理。

UnityWebRequestDefaultBase是一个中间层,没有太多的作用。

UnityWebRequestProto类:大部分的逻辑接口都在这里处理,这个基类持有了Downloadhandler 和 UpLoadHandler类的对象。

DownloadHandler继承关系如下:


UpLoadHandler继承关系如下:



通过上面三幅图我们可以看出,UnityWebRequest通过继承、接口类、组合的方式实现了代码的多功能性。使代码具有了更高的内聚性、可读性和可扩展性。

本篇内容就先写这么多,如果喜欢这篇文章,请点赞+关注,后面还有更多精彩内容。
页: [1]
查看完整版本: UnityWebRequest 源码分析