Xamarin Android 辅助功能服务泄漏内存

Xamarin Android Accessibility Service Leaks Memory

运行 从 RootInActiveWindow 遍历 AccessibilityNodeInfo 的无障碍服务导致内存泄漏。我已经在 Android 6.x 和 7.x 物理设备和模拟器上测试了这个。

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

在分析器中观察应用程序,您可以看到无障碍服务永远在内存中攀升。更具体地说,来自下面示例中的 GetWindowNodes 方法。

例子

元数据配置

<?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:

时,突出显示的区域 总是 增加

做错了什么?漏洞?不...

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

上使用你的代码

直到 GC 运行 至少进行一次次要收集后,才会收集这些次要分配,您始终可以在 OnAccessibilityEvent 的末尾放置一个 GC.Collect(0); 以立即清理增加您的 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;
        }
    }
    ~~~
}

Logcat:

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