Bug 52616 - Accessibility Service Leaks Memory
Summary: Accessibility Service Leaks Memory
Status: RESOLVED INVALID
Alias: None
Product: Android
Classification: Xamarin
Component: Mono runtime / AOT Compiler ()
Version: 7.0 (C8)
Hardware: All All
: --- normal
Target Milestone: ---
Assignee: Andi McClure
URL:
Depends on:
Blocks:
 
Reported: 2017-02-18 21:47 UTC by kyle.spearrin
Modified: 2017-02-21 00:58 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
project to reproduce issue (30.56 KB, application/x-zip-compressed)
2017-02-18 21:47 UTC, kyle.spearrin
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED INVALID

Description kyle.spearrin 2017-02-18 21:47:26 UTC
Created attachment 19877 [details]
project to reproduce issue

Running an accessibility service that traverses AccessibilityNodeInfo from the RootInActiveWindow causes memory leaks. I've tested this on Android 6.x and 7.x on physical devices and in the emulator.

The easiest way to see this issue is to turn on the accessibility service and visiting a webpage that keeps triggering the WindowContentChanged event (for example, go to https://time.is).

Watching the app in the profiler you can see the accessibility service climb in memory forever. More specifically coming from the GetWindowNodes method in the attached example.

Profiler results. The highlighted areas are *always* increasing with every call to the OnAccessibilityEvent:

http://imgur.com/JAnDVIv.png
http://imgur.com/baeD8h5.png


Example code:


    <?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;
        }
    }
Comment 1 kyle.spearrin 2017-02-18 23:05:13 UTC
Some other captures from the profiler that are of interest:

http://imgur.com/iV4lDjf.png
http://imgur.com/RoRKtjw.png
Comment 2 kyle.spearrin 2017-02-19 22:45:43 UTC
Note that I can see some GCing happening when snapshotting and refreshing the pages, moving to new windows, etc. However, it never seems to recover all the way. Every GC results in a memory state that is higher than the last GC. I have tried calling GC.Collect(0) after each event as a test but it doesn't seem to make any difference event by event. If I leave the window open for several hours on that website (even with refreshes and doing other things on the device) the memory just climbs forever and it eventually crashes the app.
Comment 3 kyle.spearrin 2017-02-21 00:58:59 UTC
Turns out the problem was that AccessibilityNodeInfo objects need to be disposed of manually. This has been resolved.