Xamarin Android 辅助功能服务泄漏内存
Xamarin Android Accessibility Service Leaks Memory
运行 从 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
:
时,突出显示的区域 总是 增加
做错了什么?漏洞?不...
执行快照,刷新浏览器页面,执行另一个快照,提出并重复。您应该看不到任何内存泄漏。 (至少我没有在 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
运行 从 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
:
做错了什么?漏洞?不...
执行快照,刷新浏览器页面,执行另一个快照,提出并重复。您应该看不到任何内存泄漏。 (至少我没有在 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