5

我正在将 MvvmCross 与 MonoDroid 一起使用。

在视图模型中的计时器中,我调用的每一分钟RaisePropertyChanged("MinutesRemaining")-MinutesRemaining作为一个整数,以分钟为单位指定持续时间,直到当前条目结束(是的,这是在 UI 线程上调用的!)。

MinutesRemaining绑定到TextView使用 MvvmCross。

4.10.1从 Xamarin 更新之前,该应用程序将完全崩溃,并且没有错误消息打印到跟踪 - 它现在在调试时正确中断,并在调用PropertyChanged事件时给出以下错误:

MvxBind:Error:281.24 Problem seen during binding execution for binding Text for MinutesRemaining - problem ArgumentException: 'jobject' must not be IntPtr.Zero.
Parameter name: jobject
  at Android.Runtime.JNIEnv.CallVoidMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms) [0x00010] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.10.1-branch/9d03ce3e/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:499 
  at Android.Widget.TextView.set_TextFormatted (ICharSequence value) [0x00034] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.10.1-branch/9d03ce3e/source/monodroid/src/Mono.Android/platforms/android-14/src/generated/Android.Widget.TextView.cs:1814 
  at Android.Widget.TextView.set_Text (System.String value) [0x00013] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.10.1-branch/9d03ce3e/source/monodroid/src/Mono.Android/platforms/android-14/src/generated/Android.Widget.TextView.cs:1823 
  at Cirrious.MvvmCross.Binding.Droid.Target.MvxTextViewTextTargetBinding.SetValueImpl (System.Object target, System.Object toSet) [0x00000] in <filename unknown>:0 
  at Cirrious.MvvmCross.Binding.Bindings.Target.MvxConvertingTargetBinding.SetValue (System.Object value) [0x00000] in <filename unknown>:0 
  at Cirrious.MvvmCross.Binding.Bindings.MvxFullBinding.UpdateTargetFromSource (System.Object value) [0x00000] in <filename unknown>:0 

它第一次正确绑定 - 只有在随后的RaisePropertyChanged调用中才会发生这种情况。相同的代码也适用于 Windows 8 和 Windows Phone。

更新

在用于上述场景的适配器中使用 JavaFinalise 解决了该问题(可在此处找到:MVVMCross Binding Crashes Android Application)。我现在遇到的问题是相同的结果,但是适配器中的第一个视图绑定到父视图模型中的属性(而不是项目)。

用于绑定的代码如下:

public class SubjectFilterAdapter : MvxAdapter {
    private EntityListFragment<TEntity, TViewModel> _owner;

    public SubjectFilterAdapter(Context context, EntityListFragment<TEntity, TViewModel> owner) : base(context, (IMvxAndroidBindingContext)owner.BindingContext) {

        _owner = owner;
    }

    protected override View GetBindableView(View convertView, object dataContext, int templateId) {
        var view = base.GetBindableView(convertView, dataContext, templateId);

        if (templateId == ItemTemplateId && GetPosition(dataContext) == 0) {
            var set = _owner.CreateBindingSet<EntityListFragment<TEntity, TViewModel>, TViewModel>();

            set.Bind(view.FindViewById<TextView>(Resource.Id.SelectedScheduleText))
                .To(x => x.SelectedScheduleText).WithClearBindingKey("SelectedScheduleTextFilterBinding");

            set.Apply();
        }

        return view;
    }

    protected override void JavaFinalize() {
        if (this.BindingContext != null)
            this.BindingContext.ClearAllBindings();
        base.JavaFinalize();
    }
}

它可以正常工作(对于前几个更改),但之后会引发上述异常。使用 MvvmCross 3.0.14-beta3

谢谢!

4

2 回答 2

7

通过将 listitem/cell 绑定上下文与父上下文混合,您将进入一个相当高级的领域。

为了帮助尝试解释/调试您正在发生的事情,您需要了解所有父生命周期、listitem/cell 生命周期以及相应 MvvmCross 绑定上下文的生命周期。

在父生命周期级别,这通常是 AndroidActivityFragment. 为了简单起见,我将仅用Activity于此答案的其余部分。

Activity有几个关键的生命周期事件

  • OnCreate仅在Activity首次启动时调用一次
  • OnDestroy仅在Activity不再显示时才调用一次。

MvvmCross 拦截这些事件并且:

  • 在 内,OnCreate它将 a 设置ViewModelActivityDataContext用户代码——通常是在其中膨胀的 Xml 代码SetContentView——然后创建绑定。这些绑定存储BindingContextActivity
  • OnDestroy, MvvmCross 销毁其中的所有绑定BindingContext

在我们感兴趣的用户界面中,Activity拥有一个ListView,并且它ListView有一个Adapter集合。在这种情况下,DataContextfor theListView和 itsAdapter与它的父级相同。

在列表的生命周期内ListView可能需要显示很多项目。任何时候显示的项目都可能发生变化——无论是因为用户触摸操作还是因为视图模型的变化。要显示这些项目,ListView请使用Adapterfor Views。对于每个项目,它显示Adapter供应品 a View,并且这些Views 可以重复使用(使用convertView参数)。然而,有时,这些s 也没有被重用——在这种情况下,即使在 Java/Dalvik已被删除并且 Java 最终确定View之后,视图对象有时也可能在 C#中继续存在。View

MvvmCross 拦截GetViewMvxAdapter. 对于每个调用,它不仅返回 a View,而且还返回 and MvxListItemView。这是一个View附加的BindingContext- 这允许 MvvmCross 用户将每个绑定MvxListItemView到它的列表项DataContext

当 aMvxListItemView被重用时,MvvmCross 很容易改变它的DataContext.

当 aMvxListItemView未被重用时 - 当它从 UI 中移除然后JavaFinalized - MvvmCross 拦截OnDetachedFromWindow事件并使用它来切换DataContextnull. 它执行此操作,OnDetachedFromWindow而不是JavaFinalize保证在 UI 线程上进行 Window 调用,并且(对我而言)感觉像是一个更干净的地方来执行此操作。

请注意,在最近的版本中,其中一些行为发生了微妙的变化——但上面的描述对于 v3.0.14 是正确的


有了这个背景,您目前正在尝试做的ListItemViewBindingContextActivity.

这意味着绑定实际上并没有很好地理解 的生命周期ListItemView- 所以即使在ListItemView从屏幕上删除并且(可能)最终确定之后,绑定也可以说是活着的。

要解决这个问题...

  • 我认为最简单的方法是更改​​列表项的 DataContext。如果您的 ListItemView 绑定是一个简单的普通绑定 - 如果它包含 MinutesRemaining 属性 - 那么您不应该遇到这些生命周期错误。
  • 您可以尝试在@Jamie 的答案(https://stackoverflow.com/a/20031690/373321)中找到的高级绑定 - 但是,我认为这个答案仍然不太正确 - 正如我认为的那样:
    • 它不能正确处理 listitemview 被删除/最终确定的情况 - 如果第一个列表项在当前代码下滚动到屏幕外,那么我相信你仍然可以看到问题。要使用基于适配器的代码,我认为适配器需要以某种方式从 listitemview 获取回调,以防该视图从 UI 中删除或最终确定。
    • 那个JavaFinalize答案是“有点顽皮”,因为它ActivityFinalize. Adapter这可能是可以的,但它不应该真的是必要的 -Activity自己的OnDestroy应该处理。
于 2013-11-17T21:08:55.633 回答
0

通过将上述适配器代码更新为:

    public class SubjectFilterAdapter : MvxAdapter {
        private EntityListFragment<TEntity, TViewModel> _owner;

        private MvxFluentBindingDescriptionSet<EntityListFragment<TEntity, TViewModel>, TViewModel> _scheduleBindingSet; 

        public SubjectFilterAdapter(Context context, EntityListFragment<TEntity, TViewModel> owner)
            : base(context, (IMvxAndroidBindingContext)owner.BindingContext) {

            _owner = owner;
        }

        protected override View GetBindableView(View convertView, object dataContext, int templateId) {
            var view = base.GetBindableView(convertView, dataContext, templateId);

            if (templateId == ItemTemplateId && GetPosition(dataContext) == 0) {
                if (_scheduleBindingSet != null) {
                    _owner.BindingContext.ClearBindings("SelectedScheduleTextFilterBinding");

                    _scheduleBindingSet = null;
                }

                _scheduleBindingSet = _owner.CreateBindingSet<EntityListFragment<TEntity, TViewModel>, TViewModel>();

                _scheduleBindingSet.Bind(view.FindViewById<TextView>(Resource.Id.SelectedScheduleText))
                    .To(x => x.SelectedScheduleText).WithClearBindingKey("SelectedScheduleTextFilterBinding");

                _scheduleBindingSet.Apply();
            }

            return view;
        }

        protected override void JavaFinalize() {
            if (this.BindingContext != null)
                this.BindingContext.ClearAllBindings();
            base.JavaFinalize();
        }
    }
于 2013-11-17T14:15:12.627 回答