Mono Android: пользовательское представление Java Android JNI не вызывает конструкторы в формате XML - PullRequest
2 голосов
/ 13 февраля 2012

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

XML Layout

  <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 # Конструкторы класса "Bridge"

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 Class

  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?

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

1 Ответ

2 голосов
/ 14 февраля 2012

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

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

Однако Android Callable Wrappersspecial: они содержат объявления методов Java native, а конструкторы Android Callable Wrapper вызывают в среду выполнения Mono for 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 должно быть пропущено.Это позволяет всем работать (у вас не может быть двух разных типов с одним и тем же полностью определенным именем), но добавляет другой набор сложностей.

...