Mono Android:Java Android 自定义视图 JNI 不调用 xml 布局中的构造函数

发布于 2025-01-05 02:42:36 字数 4159 浏览 0 评论 0原文

我们在 Android 上使用 Mono,并且想要使用一些用 Java Android 编写的自定义视图子类。我们创建了一个 C#“桥”类来通过 JNI 公开 Java 类。当通过 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)。

当在 XML 布局中使用通过 JNI 公开的视图时,有什么想法可以让 .NET 正确调用构造函数吗?

提前致谢

We're using Mono for Android, and we want to use a few custom view subclasses that we have written in Java Android. We created a C# "bridge" class to expose the Java class through JNI. The method overrides and custom methods we exposed are working fine when called from C# through the C# bridge class, but when we use the class in an XML layout (by referencing the fully qualified java namespace for the view), it never seems to call the constructor through C#, so the C# type does not get set properly.

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" class constructors

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)");
          ...
      }
  }

When we stop at a breakpoint in the program, we see that the local for our image view is of type Android.Widgets.ImageView, but holds the value {com.example.widget.ImageView@4057f5d8}. We're thinking that the type should show Example.Widgets.ImageView as well, but we can't figure out how to get .NET to use this type instead of the inherited Android type (Android.Widgets.ImageView).

Any ideas how to get .NET to call constructors properly when views exposed through JNI are used in XML layout?

Thanks in advance

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

指尖凝香 2025-01-12 02:42:36

问题是我还没有完全记录/解释 RegisterAttribute.DoNotGenerateAcw 的作用,为此我必须道歉。

具体来说,问题是这样的:Android Layout XML只能引用Java类型。通常这不是问题,因为 Android Callable Wrappers 是在构建时生成的时间,为每个 Java.Lang.Object 子类的 C# 类型提供 Java 类型。

但是,Android Callable Wrapper 很特殊:它们包含 Java 原生方法声明,并且 Android Callable Wrapper 构造函数调用 Mono for Android 运行时来创建相关的 C# 类。 (请参阅上面网址中的示例,并注意构造函数主体调用 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 实例出现在托管代码中,它将使用 (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 生成。这允许事情工作(你不能有两种不同的类型具有相同的完全限定名称),但增加了一组不同的复杂性。

The problem is that I haven't fully documented/explained what RegisterAttribute.DoNotGenerateAcw does, for which I must apologize.

Specifically, the problem is this: Android Layout XML can only refer to Java types. Normally this isn't a problem, as Android Callable Wrappers are generated at build-time, providing Java types for every C# type which subclasses Java.Lang.Object.

However, Android Callable Wrappers are special: they contain Java native method declarations, and Android Callable Wrapper constructors call into the Mono for Android runtime to create the relevant C# class. (See the example at the above url, and notice that the constructor body calls mono.android.TypeManager.Activate().)

Your example, however, completely bypasses all of that, because your layout XML isn't referencing an Android Callable Wrapper, it's instead referencing your Java type, and your Java type doesn't have a constructor that calls mono.android.TypeManager.Activate().

The result is that happens is exactly what you told it to happen: Your Java type is instantiated, and because there is no "plumbing" to associate the Java instance with a (to be) created C# instance (the mono.android.TypeManager.Activate() call), no C# instance is created, which is the behavior you're seeing.

All of which sounds entirely sane to me, but I'm the guy who wrote it, so I'm biased.

Thus, what do you want to have happen? If you really want a hand-written Java ImageView, as you've done, then there's only one reasonable C# constructor to have invoked: the (IntPtr, JniHandleOwnership) constructor. All that's missing is the mapping between the Java type and the C# type, which you can do "somewhere" during app startup via TypeManager.RegisterType():

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

If you have that mapping in place, then when the com.example.widget.ImageView instance is surfaced in managed code, it will be wrapped with an Example.Widgets.ImageView instance, using the (IntPtr, JniHandleOwnership) constructor.

If, instead, you want to have your C# (Context context, IAttributeSet attrs, int defStyle) constructor invoked, you should bypass your wrapper type and just stick to C# code:

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

In summary, RegisterAttribute.DoNotGenerateAcw is "special": it means that you're "aliasing" an existing Java type, and that the "normal" Android Callable Wrapper generation should be skipped. This allows things to work (you can't have two different types with the same fully qualified name), but adds a different set of complications.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文