且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

MonoDroid:调用自定义视图的构造函数时出错 - TwoDScrollView

更新时间:2022-05-23 09:15:48

恭喜!你遇到了一个有漏洞的抽象.:-/

Congratulations! You've hit a leaky abstraction. :-/

问题是这样的:无论好坏,来自构造函数的虚方法调用都会调用派生程度最高的方法实现.C#在这方面与Java相同;考虑以下程序:

The problem is this: for better or worse, virtual method calls from constructors invoke the most derived method implementation. C# is the same as Java in this respect; consider the following program:

using System;

class Base {
    public Base ()
    {
        Console.WriteLine ("Base..ctor");
        M ();
    }

    public virtual void M ()
    {
        Console.WriteLine ("Base.M");
    }
}

class Derived : Base {

    public Derived ()
    {
        Console.WriteLine ("Derived..ctor");
    }

    public override void M ()
    {
        Console.WriteLine ("Derived.M");
    }
}

static class Demo {
    public static void Main ()
    {
        new Derived ();
    }
}

运行时,输出为:

Base..ctor
Derived.M
Derived..ctor

也就是说,在 Derived 构造函数执行之前调用 Derived.M() 方法.

That is, the Derived.M() method is invoked before the Derived constructor has executed.

在适用于 Android 的 Mono 中,事情变得更加……复杂.Android Callable Wrapper (ACW) 的构造函数由 Java 调用,负责创建 对等 C# 实例并将 Java 实例映射到 C# 实例.但是,如果从 Java 构造函数调用虚拟方法,则该方法将在 有 C# 实例调用该方法之前被分派!

In Mono for Android, things get more...complicated. The Android Callable Wrapper (ACW)'s constructor is invoked by Java and is responsible for creating the peer C# instance and mapping the Java instance to the C# instance. However, if a virtual method is invoked from the Java constructor, then the method will be dispatched before there is a C# instance to invoke the method upon!

让它沉入一点.

我不知道哪种方法正在为您的特定代码触发场景(您提供的代码片段工作正常),但我们确实有一个示例可以满足此场景:LogTextBox 覆盖 TextView.DefaultMovementMethod 属性,以及 TextView 构造函数调用 getDefaultMovementMethod() 方法.结果是 Android 尝试在 LogTextBox 实例存在之前调用 LogTextBox.DefaultMovementMethod.

I don't know which method is triggering the scenario for your specific code (the code fragment you provided works fine), but we do have a sample which hits this scenario: LogTextBox overrides the TextView.DefaultMovementMethod property, and the TextView constructor invokes the getDefaultMovementMethod() method. The result is that Android tries to invoke LogTextBox.DefaultMovementMethod before a LogTextBox instance even exists.

那么 Mono for Android 有什么作用呢?Mono for Android 创建了 ACW,因此知道应该将 getDefaultMovementMethod() 方法委托给哪个 C# type.它没有实例,因为还没有创建实例.所以 Mono for Android 创建了一个适当类型的实例......通过 (IntPtr, JniHandleOwnership) 构造函数,如果找不到这个构造函数,就会产生一个错误.

So what does Mono for Android do? Mono for Android created the ACW, and thus knows which C# type the getDefaultMovementMethod() method should be delegated to. What it doesn't have is an instance, because one hasn't been created. So Mono for Android creates an instance of the appropriate type...via the (IntPtr, JniHandleOwnership) constructor, and generates an error if this constructor cannot be found.

一旦(在这种情况下)TextView 构造函数完成执行,LogTextBox 的 ACW 构造函数将执行,此时 Mono for Android 将啊哈!我们已经为这个 Java 实例创建了一个 C# 实例",并且将然后在已经创建的实例上调用适当的构造函数.这意味着对于单个实例,将执行两个构造函数:(IntPtr, JniHandleOwnership) 构造函数,以及(稍后)(Context, IAttributeSet, int) 构造函数.

Once the (in this case) TextView constructor finishes executing, the LogTextBox's ACW constructor will execute, at which point Mono for Android will go "aha! we've already created a C# instance for this Java instance", and will then invoke the appropriate constructor on the already created instance. Meaning that for a single instance, two constructors will be executed: the (IntPtr, JniHandleOwnership) constructor, and (later) the (Context, IAttributeSet, int) constructor.