我们在 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/