Xamarin.Android所提供的接口基本上是对原生接口的再封装,但Xamarin团队在封装时采用了C#风格,这会导致某些API的用法无法完全和原生方法对照起来。除此之外,由于Xamarin.Android并不是将C#直接翻译成JVM语言,而是引入了Mono运行时,通过ACW对Java的对象进行访问,因此某些操作也会与原生方式迥异,理解这些差异点,会让我们在开发时更加游刃有余。 Xamarin.Android的体系结构

知识点1:如何实例化Runnable接口

在原生开发中,Runnable接口的使用几乎可以说是不可避免的,因为它太常用了。在Xamarin.Android中,Runnable对应的API定义是IRunnable,一般而言,Xamarin.Android都会在接口定义的前面加个大写的I,这是Java语法规范中没有要求的。这可能会让我们有些不适应。但事实证明,这么做也是有好处的,至少我知道了不少API原来是接口,而不是类;毕竟很多时候,我们都是拿来就用,是接口还是类型,并不会产生实质性的影响,因此也就不会专门去查询是接口还是类;所以,如果在Xamarin.Android下找不到与原生对应的的API,试试在前面加个I,也许那是个接口也不一定。

对于Runnable接口这个例子,如果使用Java语法的话,我们一般是用匿名类来实现它。

  Runnable run = new Runnable() {
            @Override
            public void run() {
              //do what you want
            }
        };

但在C#中情况就有所不同了,因为C#是不支持匿名类型的。但是我们总不能每一次都去实现一个IRunnable接口的类型,那光是起名字,就很累人了。 还好,Xamarin.Android官方很周到地提供了一个Runnable类型,注意:这是个类型哦,不是接口哦。这个类型有一个接收Action委托的构造器,这样我们就可以把我们想要运行的代码包装成Action委托,利用lamda表达式就能够达成比Java的匿名类型更便捷的效果了;

IRunnable run = new Runnable(() =>{
    //do what  you want
});

知识点2:如何实例化IThreadFatory接口

在想要实例化一个线程池对象时,我发现我所使用的的ThreadPoolExecutor构造器的最后一个参数原来是个接口。

一个ThreadPoolExecutor构造器的定义

而这一次Xamarin.Android官方可没有那么温馨地为这个接口提供一个已经定义好的类型,所以我决定自己动手,丰衣足食。但是但我试图实现一个IThreadFactory类型时,出幺蛾子了。自动实现接口出来的效果是这样的。

    class MyThreadFactory : IThreadFactory {
        public IntPtr Handle => throw new NotImplementedException();

        public void Dispose() {
            throw new NotImplementedException();
        }

        public Thread NewThread(IRunnable r) {
            throw new NotImplementedException();
        }
    }

我不是只需要实现NewThread方法就行了吗?另外两个是什么鬼? 查看一下IThreadFactory的定义: IThreadFactory的定义

比原生定义多了一个IJavaObject接口,再看看这个是什么:

IJavaObject的定义

原来IJavaObject正是ACW机制的关键,实现了对原生对象的托管,而且第一句话就开宗明义地说:“NEVER IMPLEMENT THIS INTERFACE YOURSELF”(不要自行实现该接口)。好吧,那我具体应该怎么做呢?当我想打开文中给出的文档链接查看更多信息时,却发现这个链接已经失效了。 对这个问题,我迷茫了很长时间,最后是在Stackflower上的某个回答中意外地发现了问题的答案。解决方法很简单,就是在IThreadFactory接口的同时,继承Java.Lang.Object类。

class MyThreadFactory : Java.Lang.Object, IThreadFactory {
        public Thread NewThread(IRunnable r) {
            
        }
    }

注意:这里的Object要使用全名Java.Lang.Object,否则会和C#自身的Object基类型发生冲突;然后,我们就只需要实现接口要求的方法就可以了。其它类似的接口实现也是如此这般。

知识点3:如何将某些类型转换成需要的接口对象

我发现有些Xamarin.Android对象不能够像往常一样转换成我需要的接口对象,比如下面的代码在运行时就会抛出InvalidCastException 异常:

IWindowManager windowManager = (IWindowManager)context.GetSystemService(Context.WindowService);

这个时候,我们需要换个姿势来进行类型转换,这个姿势就是正确的:

windowManager = context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();

JavaCast是一个扩展方法,其定义如下: JavaCast<TResult>的定义

这么做的原因如下:

大概意思就是说,这是一个黑科技,用来应对正常类型转换无法应付的情况。根据文档的描述,应该只有在向接口转换时才会出现这样的问题。所以,像从子类向父类的转换一般都是可以成功的。比如:

ViewGroup viewGroup = (subview.Parent as ViewGroup);
ViewGroup viewGroup = (ViewGroup)subview.Parent;

知识点4:如何获取对象的class

再Java开发中使用反射时,我们经常会需要获取类的class对象,原生的做法很简单:

Class c = MyClass.class;

但这个语法在C#中是不受支持的,我们需要使用一个专门的方法来实现这个功能;

    Java.Lang.Class c = Java.Lang.Class.FromType(typeof(MyClass));

当然执行该方法的前提是,目标是Java.Lang.Object的子类,不能是纯C#对象;

先讲这么多,后续还有遇到再与大家分享。

参考文献

Xamarin.Android的高级的概念和内部机制 IJavaObject的官方文档 JavaCast的官方文档 Java.Lang.Class.FromType Method