在MonoDroid中设置TextView的文本时,'jobject'不应该为IntPtr.Zero。

5

我正在使用MvvmCross和MonoDroid。

在视图模型中的计时器中,每隔一分钟我都会调用RaisePropertyChanged("MinutesRemaining"),其中MinutesRemaining是一个整数,指定当前条目结束前的持续时间(是的,这是在UI线程上调用的!)。

MinutesRemaining被绑定到一个TextView,使用MvvmCross。

在Xamarin的4.10.1更新之前,应用程序会完全崩溃,没有任何错误消息打印到跟踪记录中 - 现在在调试时正确地中断,并在调用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

谢谢!


可能和这个有关?http://stackoverflow.com/questions/19659458/mvvmcross-binding-crashes-android-application - Jamie
这个问题是在列表中发生的吗?还是在其他控件中?您是否在最新的代码中看到了这个问题 - 例如3.0.14-beta3二进制文件?(这不包括来自链接问题的JavaFinalize,但包括围绕列表的其他更改) - Stuart
JavaFinalize已经修复了其中一个问题 - 另一个问题还没有解决(在适配器中绑定到文本视图时,使用视图模型而不是该项的数据上下文)。我已经更新到beta3二进制文件,但问题仍然存在:( - Jamie
2个回答

7
通过混合listitem/cell绑定上下文与父级上下文,您正在进入一个相当高级的领域。
为了帮助解释/调试发生的事情,您需要了解一些有关父级生命周期、listitem/cell生命周期以及相应的MvvmCross绑定上下文的知识。
在父级生命周期层面上,这通常是一个Android Activity或Fragment。为了简单起见,本答案中将仅使用Activity。
此Activity具有几个关键的生命周期事件:
- OnCreate仅在首次启动Activity时调用一次。 - OnDestroy仅在Activity不再显示时调用一次。
MvvmCross拦截这些事件并:
- 在OnCreate中,它将ViewModel设置为Activity的DataContext。用户代码 - 通常是在SetContentView中膨胀的Xml代码 - 然后创建绑定。这些绑定存储在Activity的BindingContext中。 - 在OnDestroy中,MvvmCross销毁该BindingContext中的所有绑定。
在我们感兴趣的用户界面中,Activity拥有一个ListView,而该ListView为其设置了一个Adapter。在此场景中,ListView及其Adapter的DataContext与其父级相同。
在ListView的生命周期内,列表可能需要显示大量的项目。任何时候显示的项目都可能会更改 - 这既是由于用户触摸操作,也是由于视图模型更改。为了显示这些项目,ListView会请求Adapter提供View。对于它显示的每个项目,Adapter都会提供一个View,这些View可能会被重复使用(使用convertView参数)。然而,有时这些View也不会被重复使用 - 在这种情况下,即使在Java/Dalvik View已被删除并且Java完成后,View对象有时也可能在C#中继续存在。
MvvmCross截取了其MvxAdapter中的GetView调用。对于每个调用,它不仅返回一个View,而且还返回一个MvxListItemView。这是一个带有添加BindingContext的View,这使得MvvmCross用户可以将每个MvxListItemView绑定到其列表项DataContext。
当MvxListItemView被重用时,MvvmCross很容易地改变它的DataContext。当MvxListItemView未被重用时——当它从UI中移除然后JavaFinalize时——MvvmCross会拦截OnDetachedFromWindow事件,并使用它来将DataContext切换为null。它在OnDetachedFromWindow而不是JavaFinalize上执行此操作,因为保证Window调用将在UI线程上进行,并且因为它感觉(对我来说)是更清洁的位置。
请注意,最近几个版本中的某些行为已经发生了微妙的变化,但是以上描述适用于v3.0.14。
有了这个背景,你现在似乎正在尝试创建一个绑定,将ListItemView的内容放在Activity的BindingContext中。这意味着绑定并不真正了解ListItemView的生命周期,因此即使ListItemView已经从屏幕上删除并(可能)完成了,绑定仍然会保持活动状态。
为了解决这个问题...
  • 我认为最简单的方法是更改列表项的DataContext。如果您的ListItemView绑定是一个简单的普通绑定 - 如果它包含MinutesRemaining属性 - 那么您不应该遇到这些生命周期错误。
  • 您可以尝试@Jamie答案中找到的高级绑定(https://dev59.com/Y3nZa4cB1Zd3GeqPmSAL#20031690) - 但是,我认为这个答案仍然没有完全正确地处理它 - 因为我认为:
    • 它没有正确处理列表项视图被删除/完成的情况 - 如果第一个列表项在当前代码下滚动到屏幕外,那么我认为您仍然可能会看到问题。要使用基于Adapter的代码,则我认为Adapter需要以某种方式从列表项视图中获取回调,在该视图从UI中删除或完成时进行回调。
    • 该答案中的JavaFinalize有点淘气,因为它在AdapterFinalize期间清除父Activity的BindingContext。这可能是可以的,但实际上并不应该必要 - Activity自己的OnDestroy应该处理它。

谢谢你详细的回答,Stuart! :) - 我会考虑添加对listitemview的额外处理。 - Jamie

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();
        }
    }

我认为还不够...我认为您还需要处理列表项视图被简单地从列表中删除的情况。此外,在适配器完成时清除绑定上下文并不是一个好做法。当我完成今天的旅程后,我会详细回答这个问题。 - Stuart
谢谢 Stuart,非常感谢 - 我会取消标记它作为答案 :) - Jamie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接