由于 Unity 中的 MapBox 导致内存泄漏

Memory leak because of MapBox in Unity

我在我的 Unity 项目中使用 MapBox SDK,我只是显示地图。我不使用其他服务,例如路线等。不知何故,我总是收到内存泄漏的错误消息。由于我的项目是 Ios 和 Android 应用程序,因此内存泄漏对我来说听起来不太好哈哈。

引用错误是这样的:

A Native Collection has not been disposed, resulting in a memory leak. Allocated from:
Unity.Collections.NativeArray`1:.ctor(Byte[], Allocator)
UnityEngine.Networking.UploadHandlerRaw:.ctor(Byte[])
Mapbox.Unity.Telemetry.<PostWWW>d__8:MoveNext() (at Assets\Mapbox\Unity\Telemetry\TelemetryEditor.cs:82)
Mapbox.Unity.Utilities.Routine:MoveNext() (at Assets\Mapbox\Unity\Utilities\Runnable.cs:130)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
UnityEngine.MonoBehaviour:StartCoroutineManaged2(IEnumerator)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
Mapbox.Unity.Utilities.Routine:.ctor(IEnumerator) (at Assets\Mapbox\Unity\Utilities\Runnable.cs:116)
Mapbox.Unity.Utilities.Runnable:Run(IEnumerator) (at Assets\Mapbox\Unity\Utilities\Runnable.cs:47)
Mapbox.Unity.Telemetry.TelemetryEditor:SendTurnstile() (at Assets\Mapbox\Unity\Telemetry\TelemetryEditor.cs:37)
Mapbox.Unity.MapboxAccess:ConfigureTelemetry() (at Assets\Mapbox\Unity\MapboxAccess.cs:196)
Mapbox.Unity.MapboxAccess:SetConfiguration(MapboxConfiguration, Boolean) (at Assets\Mapbox\Unity\MapboxAccess.cs:108)
Mapbox.Unity.MapboxAccess:LoadAccessToken() (at Assets\Mapbox\Unity\MapboxAccess.cs:161)
Mapbox.Unity.MapboxAccess:.ctor() (at Assets\Mapbox\Unity\MapboxAccess.cs:66)
Mapbox.Unity.MapboxAccess:get_Instance() (at Assets\Mapbox\Unity\MapboxAccess.cs:41)
Mapbox.Unity.Map.<SetupAccess>d__88:MoveNext() (at Assets\Mapbox\Unity\Map\AbstractMap.cs:609)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
UnityEngine.MonoBehaviour:StartCoroutineManaged(String, Object)
UnityEngine.MonoBehaviour:StartCoroutine(String, Object)
UnityEngine.MonoBehaviour:StartCoroutine(String)
Mapbox.Unity.Map.AbstractMap:MapOnStartRoutine(Boolean) (at Assets\Mapbox\Unity\Map\AbstractMap.cs:534)
Mapbox.Unity.Map.AbstractMap:Start() (at Assets\Mapbox\Unity\Map\AbstractMap.cs:500)

指向SDK自带的MapBox脚本。我不知道是否应该将它们包括在这里,因为我没有以任何方式触及它们,而且它们非常通用并且指向许多其他脚本。

有人知道吗?或者我可以忽略这一点,我的应用程序会正常运行吗?到目前为止,据我所知,我的测试没有任何问题。

谢谢你和我一起思考!

文本表明 Mapbox.Unity.Map.AbstractMap 使用了一些非托管资源。这在处理图像或绘图的任何 class 中无处不在。我个人不知道这个class,但有了这个名字一点也不奇怪。

非托管资源未被 GC 及时自动清理。虽然通常有终结器最终清理该资源,但通常没有等待的选项。在这种情况下,似乎甚至有一个例外可以确保您不会忘记 - 非常方便!

您的工作是通过调用 Mapbox.Unity.Map.AbstractMap.Dispose().

确保在删除最后一个引用之前清理非托管资源

我的一般建议是,每当您处理任何可处理的东西时:“创建。使用。处理。全部在同一段代码中,最好使用 using statement。”通常建议重新设计您的代码,以便您可以尽可能地遵循该模式。

有一个非常罕见的例外。在极少数情况下,您必须实施 Dispose pattern.

Dispose 模式有两个用例:

  • 您直接处理非托管资源。这基本上永远不会发生。 class 库已经涵盖了所有可能的情况。在这种情况下,您首先编写一个 Finalizer,然后为方便起见添加一个 Dispose 函数
  • 您有一个实现 IDisposeable 的任何类型的字段。在这种情况下,您自己的 class 必须实现 IDisposeable。唯一目的是将 Dispose() 调用中继到您的类型包含的任何 Disposeable 字段。这是 99% 的用例。

由于代码大部分是相同的,通常终结器是 Dispose(bool) 函数的一部分。 fianlizer 和 Dispose 之间的唯一区别是:

  • Finalizer 调用从不 中继到 Disposeable 字段。 Finalizer是每个Instance和GC之间的master。
  • Dispose 调用总是 中继到 Disposeable 字段。该中继实际上是您首先实施 IDisposeable 的唯一原因。

以下错误只会发生在编辑器中,不会影响目标平台,因为 Assets/Mapbox/Unity/Telemetry/TelemetryEditor.cs 脚本中存在 UNITY_EDITOR 宏。

如果您仍然想修复此错误,则必须在使用完 UnityWebRequest 对象后调用 Dispose,无论请求成功还是失败,只需替换 PostWWW方法如下:

        IEnumerator PostWWW(string url, string bodyJsonString)
        {
            byte[] bodyRaw = Encoding.UTF8.GetBytes(bodyJsonString);

#if UNITY_2017_1_OR_NEWER
            using (UnityWebRequest postRequest = new UnityWebRequest(url, "POST"))
            {
                postRequest.SetRequestHeader("Content-Type", "application/json");

                postRequest.downloadHandler = new DownloadHandlerBuffer();
                postRequest.uploadHandler = new UploadHandlerRaw(bodyRaw);

                yield return postRequest.SendWebRequest();

                while (!postRequest.isDone) { yield return null; }

                if (!postRequest.isNetworkError)
                {
#else
                var headers = new Dictionary<string, string>();
                headers.Add("Content-Type", "application/json");
                headers.Add("user-agent", GetUserAgent());
                var www = new WWW(url, bodyRaw, headers);
                yield return www;

                while (!www.isDone) { yield return null; }

                // www doesn't expose HTTP status code, relay on 'error' property
                if (!string.IsNullOrEmpty(www.error))
                {
#endif
                    PlayerPrefs.SetString(Constants.Path.TELEMETRY_TURNSTILE_LAST_TICKS_EDITOR_KEY, "0");
                }
                else
                {
                    PlayerPrefs.SetString(Constants.Path.TELEMETRY_TURNSTILE_LAST_TICKS_EDITOR_KEY, DateTime.Now.Ticks.ToString());
                }
            }
        }