在Unity的UGUI中使用网络图片的4种方式及性能对比
有什么不明白的地方,扫描右方二维码加我微信交流。
github中有Demo,点击跳转。
UGUI使用网络图片有以下几种方式:
- WWW(下载必须协程)
- UnityWebRequest(下载必须协程)
- UnityWebRequestTexture(下载必须协程)
- HttpClient(下载可同步线程,也可多线程)
前3种加载图片的方式没有什么好说的,中规中矩的UnityAPI,Unity的官方文档里介绍的很详细。如果只是简单的下载一个头像,一个icon,使用这些API就够了。
HttpClient是C#的API,可在异步线程中使用,适用于更复杂的场景。例如排行榜,下载100个人甚至1000个人的头像数据,使用异步线程,效率将会大大提升。异步线程可提升效率,但注意线程安全,注意在异步线程中不要使用UnityAPI。
思路如下:
- 使用网络请求下载图片数据;
- 加载图片数据为Texture2D对象;
- 创建Sprite对象;
- 替换Image组件中的Sprite。
WWW加载方式如下:
IEnumerator GetTexFromWWW()
{
Debug.Log("1");
//WWW在后续的版本中已被弃用,不建议再继续使用
//可以查看WWW的内部实际上是UnityWebRequest
var imgUrlWWW = new WWW(_imgUrl);
yield return imgUrlWWW;
Debug.Log("2");
if (imgUrlWWW.error != null)
{
Debug.Log("error\t" + imgUrlWWW.error);
yield return null;
}
var sp = Sprite.Create(imgUrlWWW.texture, new Rect(0, 0, imgUrlWWW.texture.width, imgUrlWWW.texture.height), Vector2.zero);
imageWWW.sprite = sp;
imageWWW.SetNativeSize();
Debug.Log("3");
}
UnityWebRequest加载方式如下:
IEnumerator GetTexFromUnityWebRequest()
{
Debug.Log("1");
using var request = UnityWebRequest.Get(_imgUrl);
yield return request.SendWebRequest();
Debug.Log("2");
if (request.result != UnityWebRequest.Result.Success)
{
Debug.Log(request.result);
Debug.Log(request.error);
}
else
{
Debug.Log("3");
var texture = new Texture2D(200, 200);
texture.LoadImage(request.downloadHandler.data);
var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
imageUnityWebRequest.sprite = sprite;
imageUnityWebRequest.SetNativeSize();
Resources.UnloadUnusedAssets();
Debug.Log("4");
}
}
UnityWebRequestTexture加载方式如下:
IEnumerator GetTexFromUnityWebRequestTexture()
{
Debug.Log("1");
using var request = UnityWebRequestTexture.GetTexture(_imgUrl);
yield return request.SendWebRequest();
Debug.Log("2");
if (request.result != UnityWebRequest.Result.Success)
{
Debug.Log(request.result);
Debug.Log(request.error);
}
else
{
Debug.Log("3");
var texture = DownloadHandlerTexture.GetContent(request);
var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
imageUnityWebRequestTexture.sprite = sprite;
imageUnityWebRequestTexture.SetNativeSize();
Debug.Log("4");
}
}
HttpClient加载方式如下:
private async void GetTexFromHttpClient()
{
var clientTex = new HttpClient {Timeout = TimeSpan.FromSeconds(5)};
HttpResponseMessage responseTex = null;
try
{
await Task.Run(async () =>
{
Debug.Log("1");
responseTex = await clientTex.GetAsync(_imgUrl);
Debug.Log("2");
responseTex.EnsureSuccessStatusCode(); //用来抛异常的
var responseBody = await responseTex.Content.ReadAsByteArrayAsync();
Debug.Log(responseBody);
Debug.Log("3");
//此步骤为在异步线程中调用Unity主线程,因为UnityEngine的大部份API只能在Unity主线程中调用
//可下载gitDemo,在Demo中查看MainThread源码
MainThread.RunTask(() =>
{
if (responseTex.IsSuccessStatusCode)
{
Debug.Log("4");
var tex = new Texture2D(100, 100);
tex.LoadImage(responseBody);
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height),
new Vector2(0.5f, 0.5f));
imageHttpClient.sprite = sprite;
imageHttpClient.SetNativeSize();
}
});
});
}
catch (Exception e)
{
Debug.LogError(e.Message);
}
finally
{
clientTex.Dispose();
responseTex?.Dispose();
}
}
下载速度对比

经过测试,主线程中使用协程的下载速度远高于异步线程。
但这是在理想状况下,主线程当前压力较小,基本没有任务在运行。但当有大量的网络请求、I/O操作或者复杂数据计算时,是否依然是这样的结果呢?
我在Update方法里加了以下代码:
var a = 1;
for (var i = 0; i < 1000; i++)
{
++a;
PlayerPrefs.SetInt("test", a);
PlayerPrefs.Save();
}
进行1000次的数据写入,结果如下:

主线程压力较大时,结果是:
协程的速度明显慢了5431ms,91.4%。
异步线程的运行速度慢了2312ms,24.1%。
结论:理想情况下,主线程使用协程下载速度比异步线程快;复杂情况下,异步线程要好一点。
什么时候使用异步线程下载
在Update里执行1000次的Save方法,这种操作几乎没有。但游戏越做越大,功能越来越复杂,游戏运行时的实际情况比我们想像的要复杂的多。当主线程有压力、或者有明显卡顿时就可以考虑使用异步线程来分担主线程压力。