Mono Android: пользовательское представление Java Android JNI не вызывает конструкторы в макете xml

Мы используем Mono для Android и хотим использовать несколько пользовательских подклассов представления, которые мы написали на Java Android. Мы создали класс «моста» C# для предоставления класса Java через JNI. Предоставленные нами переопределения методов и пользовательские методы работают нормально при вызове из C# через мостовой класс C#, но когда мы используем класс в макете XML (путем ссылки на полное пространство имен java для представления), кажется, что он никогда не вызывает конструктор через C#, поэтому тип C# не устанавливается должным образом.

XML-макет

  <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
                  android:layout_height="fill_parent" >
        <com.example.widget.ImageView:id="@+id/custom_view" /> 
  </merge>

C# конструкторы класса "мост"

namespace Example.Widgets {
  [Register ("com/example/widget/ImageView", DoNotGenerateAcw=true)]
  public class ImageView : global::Android.Widget.ImageView {
    private new static IntPtr class_ref = JNIEnv.FindClass("com/example/widget/ImageView");
    private static IntPtr id_ctor_Landroid_content_Context_;
    private static IntPtr id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_;
    private static IntPtr id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_I;
    ...

    protected override IntPtr ThresholdClass {
        get {
            return ImageView.class_ref;
        }
    }

    protected override Type ThresholdType {
        get {
            return typeof(ImageView);
        }
    }

    protected ImageView (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {
    }

    // --V-- this should get called by the android layout inflater, but it never does (nor do any of the other public constructors here)
    [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;)V", "")]
    public ImageView (Context context, IAttributeSet attrs) : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
    {
        Log.Debug("ImageView","Calling C# constructor (Landroid/content/Context;Landroid/util/AttributeSet;)V");
        if (base.Handle != IntPtr.Zero)
        {
            return;
        }
        if (base.GetType () != typeof(ImageView))
        {
            base.SetHandle (JNIEnv.CreateInstance (base.GetType (), "(Landroid/content/Context;Landroid/util/AttributeSet;)V", new JValue[]
            {
                new JValue (JNIEnv.ToJniHandle (context)),
                new JValue (JNIEnv.ToJniHandle (attrs))
            }), JniHandleOwnership.TransferLocalRef);
            return;
        }
        if (ImageView.id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_ == IntPtr.Zero)
        {
            ImageView.id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_ = JNIEnv.GetMethodID (ImageView.class_ref, "<init>", "(Landroid/content/Context;Landroid/util/AttributeSet;)V");
        }
        base.SetHandle (JNIEnv.NewObject (ImageView.class_ref, ImageView.id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_, new JValue[]
        {
            new JValue (JNIEnv.ToJniHandle (context)),
            new JValue (JNIEnv.ToJniHandle (attrs))
        }), JniHandleOwnership.TransferLocalRef);
    }



    [Register (".ctor", "(Landroid/content/Context;)V", "")]
    public ImageView (Context context) : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer) {
      ...
    }


    [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", "")]
    public ImageView (Context context, IAttributeSet attrs, int defStyle) : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)    {
      ...
    }

    ...

Java-класс

  package com.example.widget;
  public class ImageView extends android.widget.ImageView {

      //--V-- This constructor DOES get called here (in java)
      public ImageView(Context context, AttributeSet attrs) {
          super(context, attrs);
          Log.d(TAG, "Called Java constructor (context, attrs)");
          ...
      }
  }

Когда мы останавливаемся в точке останова в программе, мы видим, что локальное представление нашего изображения имеет тип Android.Widgets.ImageView, но содержит значение {com.example.widget.ImageView @4057f5d8}. Мы думаем, что этот тип также должен отображать Example.Widgets.ImageView, но мы не можем понять, как заставить .NET использовать этот тип вместо унаследованного типа Android ( Android.Widgets.ImageView).

Любые идеи о том, как заставить .NET правильно вызывать конструкторы, когда представления, предоставляемые через JNI, используются в макете XML?

заранее спасибо


person Andy R    schedule 13.02.2012    source источник
comment
Интересно, это ошибка, вызванная тем, что ваше пользовательское представление называется ImageView, как его родительский класс. Можете ли вы попробовать изменить имя и посмотреть, столкнетесь ли вы с той же проблемой?   -  person jpobst    schedule 14.02.2012


Ответы (1)


Проблема в том, что я не полностью задокументировал/объяснил, что делает RegisterAttribute.DoNotGenerateAcw, за что я должен извиниться.

В частности, проблема заключается в следующем: Android Layout XML может ссылаться только на типы Java. Обычно это не проблема, поскольку Android Callable Wrappers создаются при сборке. время, предоставляя типы Java для каждого типа C#, который является подклассом Java.Lang.Object.

Однако Android Callable Wrapper особенные: они содержат объявления методов Java native, а конструкторы Android Callable Wrapper вызывают среду выполнения Mono для Android для создания соответствующего класса C#. (См. пример по приведенному выше URL-адресу и обратите внимание, что тело конструктора вызывает mono.android.TypeManager.Activate().)

Ваш пример, однако, полностью обходит все это, потому что XML вашего макета не ссылается на Android Callable Wrapper, вместо этого он ссылается на ваш тип Java, а ваш тип Java не имеет конструктора, который вызывает mono.android.TypeManager.Activate().

В результате происходит в точности то, что вы сказали: создается экземпляр вашего типа Java, а также потому, что не существует "проводниковой связи" для связывания экземпляра Java с (будущим) созданным экземпляром C# ( вызов mono.android.TypeManager.Activate()), экземпляр C# не создается, что вы и видите.

Все это звучит совершенно разумно для меня, но я тот парень, который написал это, поэтому я предвзят.

Итак, что вы хотите, чтобы произошло? Если вам действительно нужен написанный от руки Java ImageView, как вы это сделали, то есть только один разумный конструктор C#, который нужно вызвать: конструктор (IntPtr, JniHandleOwnership). Не хватает только сопоставления между типом Java и типом C#, которое можно выполнить «где-то» во время запуска приложения через TypeManager.RegisterType():

Android.Runtime.TypeManager("com/example/widget/ImageView",
        typeof(Example.Widgets.ImageView));

Если у вас есть это сопоставление, то когда экземпляр com.example.widget.ImageView появляется в управляемом коде, он будет заключен в экземпляр Example.Widgets.ImageView с помощью конструктора (IntPtr, JniHandleOwnership).

Если вместо этого вы хотите, чтобы ваш конструктор C# (Context context, IAttributeSet attrs, int defStyle) вызывался, вы должны обойти свой тип оболочки и просто придерживаться кода C#:

namespace Example.Widgets {
    public class ImageView : global::Android.Widget.ImageView {
        ...

Таким образом, RegisterAttribute.DoNotGenerateAcw является «особым»: это означает, что вы «используете псевдоним» для существующего типа Java и что «обычное» создание Android Callable Wrapper следует пропустить. Это позволяет вещам работать (у вас не может быть двух разных типов с одним и тем же полным именем), но добавляет другой набор осложнений.

person jonp    schedule 13.02.2012
comment
Ах, отлично - TypeManager.RegisterType в статическом конструкторе исправил это. Спасибо! - person Andy R; 14.02.2012