android-layout - 单声道 Android : Java Android custom view JNI not calling constructors in xml layout

标签 android-layout java-native-interface xamarin.android

我们在 Android 上使用 Mono,并且希望使用一些用 Java Android 编写的自定义 View 子类。我们创建了一个 C#“桥”类来通过 JNI 公开 Java 类。当通过 C# 桥接类从 C# 调用时,我们公开的方法覆盖和自定义方法工作正常,但是当我们在 XML 布局中使用该类时(通过引用 View 的完全限定的 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)");
          ...
      }
  }

当我们停在程序中的断点处时,我们看到本地镜像 View 的类型为 Android.Widgets.ImageView,但保留值 {com.example. widget.ImageView@4057f5d8}。我们认为该类型也应该显示 Example.Widgets.ImageView,但我们无法弄清楚如何让 .NET 使用此类型而不是继承的 Android 类型( Android.Widgets.ImageView)。

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

提前致谢

最佳答案

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

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

但是,Android Callable Wrappers 很特殊:它们包含 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 实例出现在托管代码中时,它将用 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 生成。这允许事情工作(你不能有两种不同的类型具有相同的完全限定名称),但增加了一组不同的复杂性。

关于android-layout - 单声道 Android : Java Android custom view JNI not calling constructors in xml layout,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9264487/

相关文章:

java - 如何从 C++ 调用 Java 方法

java - 在包 'text color' 中找不到属性 'android' 的资源标识符 --- 构建时

android - Kotlin在与 Activity 不同的布局上更新textview的文本

Android Manifest.xml 文件错误 : Android

Java 和 JNI - 加载 dll 库仅适用于我的计算机

java - 在不同的核心上运行进程

java - 通过代码以 dp 为单位设置 View 的高度?

xamarin - 使用Environment访问Android特殊文件夹路径

xamarin.android - 通过 BroadcastReceiver 接收来电时将应用程序从后台带到前台

单色触摸 : Namespaces allowed