片段。分配了非零值(Android的peer对象GC-ED,但不是单声道),argments argments null niull。

发布于 2025-01-31 20:41:51 字数 4611 浏览 4 评论 0 原文

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文