tl; dr:
看来,Android有时会垃圾收集 framework peer对象,在我们的情况下, bundle
。当捆绑包在.NET代码中使用时,这将导致NullReferenceException,而该捆绑包在.NET代码中不符合垃圾收集资格。
这与声称Xamarin机械确保Java无法在.NET侧无法GC的对象的有用文章相矛盾。
这是在我们从类中的公共字段重新分配 Bundle
存储中的 Bundle
bunddle 存储到只读的自动属性。我想知道富有富裕者生成的自动培训是否会干扰Xamarin参考跟踪机械?
我们有一个很小且简单的Xamarin Android应用程序(非形式)。
主要活动具有 viewPager
,其中通过 android> android.support.v13.app.fragmentpageradapter
显示相同片段的实例, labelfragment
。
适配器总是将参数
分配给其在其 getItem
中创建的所有片段。
我们最近收到的崩溃报告清楚地表明,labelfragment时有时会发现其 this.arguments
为 null
null ,并在其中使用NullReferenceExcept崩溃。 OnCreate
。
通过大量的记录(请参阅编辑历史记录有关完整的故事;并不重要)参数在创建新片段时 fragmentPagerAdapter.getItem()
public override Android.App.Fragment GetItem(int position)
{
PageDescriptor info = m_Tabs[position];
Android.Util.Log.Debug(LOG_TAG, "GetItem({0}), creating t: {1}, a: {2}", position, info.TypeOfFragment.Name, info.FragmentArguments != null);
var f = (Android.App.Fragment)Activator.CreateInstance(info.TypeOfFragment);
f.Arguments = info.FragmentArguments;
Android.Util.Log.Debug(LOG_TAG, "GetItem({0}), created t: {1}, a: {2}", position, f.GetType().Name, f.Arguments != null);
return f;
}
在崩溃报告中产生以下logcat:
D LabelsAdapter: GetItem(5), creating t: LabelFragment, a: True
D LabelsAdapter: GetItem(5), created t: LabelFragment, a: False
也就是说,,我们将验证的非纳尔捆绑包分配给 f.arguments
,然后在那之后, f.Arguments
is null
。
这个问题不容易再现。它每天在严重使用的应用程序中每天发生几次,但确实会定期发生。
我们对此感到困惑,不知道该怎么办。
我们在做什么公然做错了吗?
这是Android中的已知问题吗?
这是Xamarin中的已知问题吗?
我们如何处理它?
我们还发现,在所有崩溃的情况下,始终在下面引用的所有垃圾收集。
下一个呼叫 getItem()
之后,GC演示了问题。该呼叫不必在GC运行之后或同时进行,只需在之后(甚至在几分钟后)。
时间戳编辑为简洁。
10:46:18 I ame.oursmallap: Explicit concurrent copying GC freed 73676(5134KB) AllocSpace objects, 4(84KB) LOS objects, 49% free, 7529KB/14MB, paused 98us total 51.603ms
10:46:18 W System : A resource failed to call close.
10:46:18 I chatty : uid=10146(com.companyname.oursmallapp) FinalizerDaemon identical 39 lines
10:46:18 W System : A resource failed to call close.
10:46:18 W System : A resource failed to call close.
10:46:18 I chatty : uid=10146(com.companyname.oursmallapp) FinalizerDaemon identical 6 lines
10:46:18 W System : A resource failed to call close.
10:47:19 D LabelsAdapter: GetItem(5), creating t: LabelFragment, a: True
10:47:19 D LabelsAdapter: GetItem(5), created t: LabelFragment, a: False
编辑:
看来我们对GC LogCat的预感是正确的。
我们已经能够通过随机调用在我们存储的 Bundle
实例上随机调用
来重现问题(是的片段实例)。
Fragment的参数
属性不是一个愚蠢的游戏:
public unsafe Bundle Arguments
{
[Register("getArguments", "()Landroid/os/Bundle;", "GetGetArgumentsHandler")] get => Java.Lang.Object.GetObject<Bundle>(Fragment._members.InstanceMethods.InvokeNonvirtualObjectMethod("getArguments.()Landroid/os/Bundle;", (IJavaPeerable) this, (JniArgumentValue*) null).Handle, JniHandleOwnership.TransferLocalRef);
[Register("setArguments", "(Landroid/os/Bundle;)V", "GetSetArguments_Landroid_os_Bundle_Handler")] set
{
JniArgumentValue* parameters = stackalloc JniArgumentValue[1];
parameters[0] = new JniArgumentValue(value == null ? IntPtr.Zero : value.Handle);
Fragment._members.InstanceMethods.InvokeVirtualVoidMethod("setArguments.(Landroid/os/Bundle;)V", (IJavaPeerable) this, parameters);
}
}
它将返回 null
当.NET包装器和Java对象之间链接打破时,当时是Java时物体是收集垃圾的。
然后,问题是,Java垃圾收集一个在.NET侧仍然有引用的对象? (我们将它们存储在.NET列表中。)GC文章声称是不可能的。
现在我考虑了这一点,唯一的变化(之后开始发生)是我们重构了一个将捆绑包存储到仅阅读自动属性中的字段。我想知道自动属性的实现是否与Xamarin参考保护机制不兼容?
TL;DR:
It would appear that Android sometimes garbage-collects the Java side of framework peer objects, in our case the Bundle
. This results in a NullReferenceException when the Bundle is later used in the .NET code, where it is not eligible for garbage collection.
This contradicts the helpful article that claims that the Xamarin machinery makes sure Java cannot GC an object that is not GC-able on the .NET side.
This started after we refactored the Bundle
storage from a public field in a class into a read-only auto property. I wonder if compliler-generated auto-properties interfere with Xamarin reference tracking machinery?
We have a very small and simple Xamarin Android app (non-Forms).
The main activity has a ViewPager
where it displays instances of the same fragment, LabelFragment
, via an Android.Support.V13.App.FragmentPagerAdapter
.
The adapter always assigns Arguments
to all fragments it creates in its GetItem
.
We have been receiving crash reports recently that clearly show that every now and then, a LabelFragment finds its this.Arguments
to be null
and crashes with a NullReferenceException in its OnCreate
.
Through extensive logging (see the editing history for the full story; not really important), we were able to pinpoint that the fragment arguments are lost in the FragmentPagerAdapter.GetItem()
when creating a new fragment:
public override Android.App.Fragment GetItem(int position)
{
PageDescriptor info = m_Tabs[position];
Android.Util.Log.Debug(LOG_TAG, "GetItem({0}), creating t: {1}, a: {2}", position, info.TypeOfFragment.Name, info.FragmentArguments != null);
var f = (Android.App.Fragment)Activator.CreateInstance(info.TypeOfFragment);
f.Arguments = info.FragmentArguments;
Android.Util.Log.Debug(LOG_TAG, "GetItem({0}), created t: {1}, a: {2}", position, f.GetType().Name, f.Arguments != null);
return f;
}
which produced the following logcat in the crash report:
D LabelsAdapter: GetItem(5), creating t: LabelFragment, a: True
D LabelsAdapter: GetItem(5), created t: LabelFragment, a: False
That is, we assign a proven non-null Bundle to f.Arguments
, and right after that, f.Arguments
is null
.
The problem is not easily reproducible. It happens a couple of times a day in an app that is under heavy use, but it does happen regularly.
We are baffled by this and don't know what to do.
Are we doing something blatantly wrong?
Is this a known issue in Android?
Is this a known issue in Xamarin?
How do we work around it?
We also find it suspicious that in all cases of the crash, there is always a specific logcat of garbage collection quoted below.
The very next call to GetItem()
after that GC demonstrates the problem. The call does not have to take place immediately after or concurrently with the GC run, just some time after (even minutes after).
Timestamps edited for brevity.
10:46:18 I ame.oursmallap: Explicit concurrent copying GC freed 73676(5134KB) AllocSpace objects, 4(84KB) LOS objects, 49% free, 7529KB/14MB, paused 98us total 51.603ms
10:46:18 W System : A resource failed to call close.
10:46:18 I chatty : uid=10146(com.companyname.oursmallapp) FinalizerDaemon identical 39 lines
10:46:18 W System : A resource failed to call close.
10:46:18 W System : A resource failed to call close.
10:46:18 I chatty : uid=10146(com.companyname.oursmallapp) FinalizerDaemon identical 6 lines
10:46:18 W System : A resource failed to call close.
10:47:19 D LabelsAdapter: GetItem(5), creating t: LabelFragment, a: True
10:47:19 D LabelsAdapter: GetItem(5), created t: LabelFragment, a: False
EDIT:
It would appear our hunch about the GC logcat was correct.
We have been able to reproduce the problem by randomly calling Dispose
on our stored Bundle
instances that we assign to Fragments (yes, we create the bundles in advance, and only much later the Fragment instances).
The fragment's Arguments
property is not a dumb get-setter:
public unsafe Bundle Arguments
{
[Register("getArguments", "()Landroid/os/Bundle;", "GetGetArgumentsHandler")] get => Java.Lang.Object.GetObject<Bundle>(Fragment._members.InstanceMethods.InvokeNonvirtualObjectMethod("getArguments.()Landroid/os/Bundle;", (IJavaPeerable) this, (JniArgumentValue*) null).Handle, JniHandleOwnership.TransferLocalRef);
[Register("setArguments", "(Landroid/os/Bundle;)V", "GetSetArguments_Landroid_os_Bundle_Handler")] set
{
JniArgumentValue* parameters = stackalloc JniArgumentValue[1];
parameters[0] = new JniArgumentValue(value == null ? IntPtr.Zero : value.Handle);
Fragment._members.InstanceMethods.InvokeVirtualVoidMethod("setArguments.(Landroid/os/Bundle;)V", (IJavaPeerable) this, parameters);
}
}
It will return null
when the link is broken between the .NET wrapper and the Java object, which is when the Java object is garbage collected.
The question then is, how could Java garbage-collect an object that still has references on the .NET side? (We are storing them in a .NET list.) The GC article claims that it is not possible.
Now that I think about it, the only change (after which this started happening) was that we refactored a field in which the Bundle was stored into a read-only auto property. I wonder if the implementation of auto properties is not compatible with the Xamarin reference-preservation mechanisms?
发布评论