.net - Xamarin Android 辅助服务泄漏内存

标签 .net xamarin mono xamarin.android

运行从 RootInActiveWindow 遍历 AccessibilityNodeInfo 的辅助服务会导致内存泄漏。我已在物理设备和模拟器中的 Android 6.x 和 7.x 上对此进行了测试。

查看此问题的最简单方法是打开辅助功能服务并访问不断触发 WindowContentChanged 事件的网页(例如,转到 https://time.is )。

在分析器中查看应用程序,您可以看到辅助服务在内存中永远爬升。更具体地说,来自下面示例中的 GetWindowNodes 方法。

  • 我在这里做错了什么还是 Xamarin Android 中的错误?
  • 有什么办法可以解决这个问题吗?

示例

元数据配置

<?xml version="1.0" encoding="utf-8" ?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagReportViewIds"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"/>

服务

[Service(Permission = "android.permission.BIND_ACCESSIBILITY_SERVICE", Label = "memtest")]
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
public class AccService : AccessibilityService
{
    private const string SystemUiPackage = "com.android.systemui";

    public override void OnAccessibilityEvent(AccessibilityEvent e)
    {
        var root = RootInActiveWindow;
        if(string.IsNullOrWhiteSpace(e.PackageName) || e.PackageName == SystemUiPackage ||
            root?.PackageName != e.PackageName)
        {
            return;
        }

        switch (e.EventType)
        {
            case EventTypes.WindowContentChanged:
            case EventTypes.WindowStateChanged:
                var nodes = GetWindowNodes(root, e, null);
                break;
        }
    }

    public override void OnInterrupt()
    {

    }

    /// <summary>
    /// Get a flat list of all nodes in this window.
    /// </summary>
    private List<AccessibilityNodeInfo> GetWindowNodes(AccessibilityNodeInfo n,
                                                       AccessibilityEvent e,
                                                       List<AccessibilityNodeInfo> nodes)
    {
        if (nodes == null)
        {
            nodes = new List<AccessibilityNodeInfo>();
        }

        if (n != null)
        {
            if (n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false))
            {
                nodes.Add(n);
            }

            for (var i = 0; i < n.ChildCount; i++)
            {
                GetWindowNodes(n.GetChild(i), e, nodes);
            }
        }

        return nodes;
    }
}

分析器结果

突出显示的区域总是随着每次调用OnAccessibilityEvent而增加:

profiler results 1 profiler results 2 profiler results 3 profiler results 4

最佳答案

做错了什么吗?漏洞?不……

执行快照,刷新浏览器页面,执行另一个快照,启动并重复。您不应该看到任何内存泄漏。 (至少我在 Xamarin.Android v7.1.0.35 上没有使用您的代码。

在 GC 运行至少一次次要回收之前,不会收集这些次要分配,您始终可以放置 GC.Collect(0);在你的 OnAccessibilityEvent 的末尾立即清理您的List<AccessibilityNodeInfo>分配,但 GC 将在未来某个时候运行,当它需要内存进行额外分配时...

In the absence of an explicit collection via GC.Collect() collections are on demand, based upon heap allocations. This is not a reference counting system; objects will not be collected as soon as there are no outstanding references, or when a scope has exited. The GC will run when the minor heap has run out of memory for new allocations. If there are no allocations, it will not run.

回复:https://developer.xamarin.com/guides/android/advanced_topics/garbage_collection/

更新:

我一直在发布版本 ( Xamirin.Android 7.1.0.35 ) 中运行此修改后的代码,并在 tweeting.net 上打开浏览器。和过滤logcat。

已接听 20,000 次电话并计数到 OnAccessibilityEvent方法我还没有看到任何内存问题...

尝试并比较结果...

public class AccService : AccessibilityService
{
    const string SystemUiPackage = "com.android.systemui";
    const string TAG = "MEMTEST";
    long originalMemory;
    long lastMemory;
    long currentMemory;
    long stabilizedMemory;
    long nodeCount;
    long stabilizeCount;
    long cycleCount;

    public AccService(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer) : base(javaReference, transfer)
    {
        originalMemory = GC.GetTotalMemory(false);
    }

    public AccService()
    {
        originalMemory = GC.GetTotalMemory(false);
    }

    public override void OnAccessibilityEvent(AccessibilityEvent e)
    {
        var root = RootInActiveWindow;
        if (string.IsNullOrWhiteSpace(e.PackageName) || e.PackageName == SystemUiPackage ||
            root?.PackageName != e.PackageName)
        {
            return;
        }

        switch (e.EventType)
        {
            case EventTypes.WindowContentChanged:
            case EventTypes.WindowStateChanged:
                var nodes = GetWindowNodes(root, e, null);

                nodeCount = nodes.Count;
                foreach (var item in nodes)
                {
                    item.Dispose();
                }
                nodes = null;
                currentMemory = GC.GetTotalMemory(true);
                if (stabilizeCount < 20)
                {
                    stabilizeCount++;
                    stabilizedMemory = currentMemory;
                }
                cycleCount++;
                Log.Info(TAG, $"{(currentMemory == lastMemory ? "Stable " : (currentMemory > lastMemory ? "Growing" : "Shrink "))} / C{currentMemory} vs. S{stabilizedMemory} / Change: {currentMemory - lastMemory} / {lastMemory - stabilizedMemory}  / {cycleCount}:{nodeCount}");
                if (currentMemory > stabilizedMemory * 2)
                    Log.Error(TAG, $"Runaway memory : {currentMemory} vs. {stabilizedMemory}");
                lastMemory = currentMemory;

                break;
        }
    }
    ~~~
}

日志猫:

Info (5080) / MEMTEST: Stable  / C4316936 vs. S4316888 / Change: 0 / 48  / 21015:6

关于.net - Xamarin Android 辅助服务泄漏内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42321065/

相关文章:

c# - 对齐报告中的文本

MonoTouch 与可移植库项目无法构建

c# - ListView 过滤器.. 图像不遵循过滤器

c# - 更好地处理丢失的 .NET Framework (0xc0000135) 崩溃?

.net - 您能否轻松地将 WPF 应用程序转换为 WPF XBAP 应用程序?

c# - 递归层次父子

mono - 在带有 MonoDevelop 的 OSX 上,从终端/命令行运行 NUnit 测试

xamarin.ios - 在软件键盘上绑定(bind) 'GO' 键

c# - 在 Xamarin Mac 中阻止鼠标气泡

c# - Xamarin.Forms 微调器 TimePicker