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# "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 клас

  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 Wrappers са специални: те съдържат декларации на метод 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