【Xamain 跨平台机制原理剖析】

BeatriceQid 8年前

来自: http://www.cnblogs.com/micro-chen/p/5173344.html

【看了请推荐,推荐满100后,将发补丁地址】

Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原生态Java开发Android的越来越多,人工费用成本降低。

上面说的3条,都跟钱相关,不占技术边,看起来跟本文的标题严重不符。但是,细细看来,如果这个产品的圈子打不开,再牛的技术,也是枉然。因为技术是在不断推进的,

性能问题,技术问题,实现问题,等等都可以随着时间的推动去解决,但是,Xamarin公司貌似没打算把价格降低。越发的稳定。死鬼死鬼.......。所以,我们需要破解,我们需要破解,我们需要破解(重要的事情说3遍),但是破解并非容易,这个平台软件,几乎是我见过的CLI语言最难破解的东东,因为里面钉子太多,剃不干净。(我曾经尝试去破解,发现需要耗费太多的时间,而且不稳定)。。但是,嗯,道高一尺魔高一丈。找补丁,到谷歌..........现在可以明确的告诉大家,最新版的Xamarin有了破解补丁。谷歌去吧。

上面说的是题外话,希望这个生态可以在这片土壤继续壮大。我们开始进入我们的标题,不要说我是标题党。呵呵。

我们就讨论Xamarin的Android开发机制吧,IOS的,本身就是在MAC下面编译的,是AOT,不是JIT.........AOT机制,本身把代码转换成了Native代码应用。JIT是基于虚拟机语言运行的中间代码。Java和.Net 都是JIT 。。。。都有类似运行的虚拟机。Java->JVM  .....C#->CLR->JIT->Native Code......

首先,这个跨平台的思路是基于很久以前的的Mono项目,关于Mono项目,自己百度脑补。Mono是对微乳.Net的跨平台的实现。基于C语言的实现,可以实现CLR在不同的平台系统下的运行。当然,针对的就是Linux系统。Mono的诞生,让.net可以在Linux下跑了。

好,上面仅仅是开端,我们再来看下Android。这个项目是Google收购的一家Droid公司的产品。就是用开源的Linux系统,做的二次开发。本质依然是Linux内核。

谷歌收购这个项目后,加大了对这个项目的投入。最终得以让这个项目在移动设备上运行起来。。(资本+科技=进步)

看Android项目的结构图:

  • Applications 应用程序层(由Java编写并且在Dalvk虚拟机来运行)
  • Framework 应用框架层 (由Java编写)
  • Libraries And Android Runtime 各种库和Android 运行环境
  • Linux Kernel 操作系统层

从最上层的应用层,如 短信 电话 视频 微信 QQ等应用 到先的支持库,都是用Java实现的。

再往下就是一些对硬件功能包装的库lib层,大多是一些开源的Linux应用库,跟这个层平级的Android的虚拟机Dalvik。

最终是Linux的内核,系统的内核的作用,就是操作系统跟硬件进行交互。(当然,硬件需要对应的驱动被系统所能识别)

以上是安卓的结构和运行概述。当你打开微信的时候,就是从应用层-》框架层-》虚拟机到内核的一个来回调用交互。

----------------------------------Xamarin是怎么运行的?----------------------------------

我们上面说过,Mono项目,实现.net语言在Linux下运行,Android是Linux项目,那只要Android中有Mono,那么.net 就能在安卓里跑!!!!

于是,从Mono开发过的一些人,过度到了Xamarin公司。Xamarin公司就是基于此。

那么Xamarin打包的安卓应用是怎么跑的? 看下面的图:

这是啥?

哦,老伯伯的皮影。

额,被老外学会了。。。。。

然后, 无语了...................

没错,这个Xamarin其实就是皮影戏(高科技的皮影)!!!!!!!!!!!!!!!!!!!!!

(写到这里,不仅为国人悲哀,自己家的东西都被当垃圾丢弃,外国佬却总能从里面找出点什么。。。比如:炸药 司南 罗盘 等等,我们自己抛弃了自己,我们的眼里脑子里只有钱钱房房.................拿出点理想吧  my guys!!!)

说它是高科技的皮影,并非空穴来风。我们看看官方给出的结构解释:

原文:

大概意思是:Xamarin安卓项目是基于Mono运行时的,Android原生应用基于安卓运行时,也就是那个Dalvik虚拟机。。。当运行Xamarin创建的安卓应用的时候,

对应的安卓虚拟机上也会有对应包装过的对象的实例被分配。那个MCW 代表的是对原生Android的Lib的绑定,ACW是基于调用映射的API后返回响应到Mono。

怎么绑定,又怎么反馈呢?看官方怎么说的。。。

下文:

地址:http://developer.xamarin.com/guides/android/under_the_hood/architecture/

英文不好的同学,自己打开网址找翻译工具吧。

大概意思是:MCW 的绑定机制还有ACW的核心就是 JNI  

JNI是什么鬼?科普一下:JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。

好,既然提供交互入口JNI,那么我们的代码在经过monodroid处理后,就可以与Android这个java项目交互了。

那么C#语言是不是被处理转换成了JNI的应用了?是的。看图:

看完上图,你应该知道了,它在编译的时候,将C#代码文件,转化成了基于对Java原生文件的映射。注意:不是直接转化成了Java代码!是调用注册。

生产的java文件中并不提供运行实现,而是提供了在Android运行时的匹配映射!!!!!!!!!!!!!!!

真正的执行代码依然在classes.dex文件中。但并不是我们的C#编译的业务代码哦~~~业务代码一会儿会说的哦(这个文件是二进制的可被安卓虚拟机执行的文件,跟语言无关)。

Java原生的开发会把业务代码压缩进此文件。这个文件也是安卓虚拟机执行对应的应用时候,能找到入口,起始点。但是,Mono项目的业务代码没在这个里面,记住,它是皮影戏。这个是皮影。真正的操作者,幕后凶手,在后台!!!

上法宝:

Xamarin生成的apk包中,也是这样。

lib目录提供了运行时需要的lib 库,如Mono运行时。

注意下面的那俩文件,就是皮影戏的杆儿,映射工具。

真正的执行代码在????

在 lib 中或者在assemblies中!!

为啥是或者?因为企业版的Xamarin,支持使用NDK将应用打包成Native应用!!!

有的人纠结那个calsses.dex文件,作为入口,说Android的应用不是非线性耦合的模块应用吗?是的,但是,这非线性针对的是系统,或者是程序块内部,而不是系统跟程序应用之间的描述。应用插入到系统上,那个自描述文件 AndroidManifest.xml 提供了程序的入口描述。

而calsses.dex被安卓虚拟机 Dalvik 执行后,会给这个程序分配对应的程序运行域,

(摘自:腾飞(Jesse))关于Dalvk虚拟机与Java运行环境的区别

  1. Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。
  2. Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
  3. 不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式
  4. Dex文件格式可以减少整体文件尺寸,提高I/o操作的类查找速度。
  5. 是为了在运行过程中进一步提高性能,对dex文件的进一步优化。
  6. 所有的Android应用的线程都对应一个Linux线程,虚拟机因而可以更多的依赖操作系统的线程调度和管理机制
  7. 有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化,库的加载,预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的数据提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域。

大家注意第2点和第7点有助于我们理解Xamarin.Android的工作机制

好,到此,你就知道了吧,那个classes.dex 就是给Zygote提供孵化器应用的............Zygote(受精卵。很形象。应用像是受精卵,通过他暖化出来了个小鸡应用)

KO!!!!!!!到此,你也就基本知道,这玩意儿是怎么运行的了吧。。。。没错,记住,皮影戏!!!!!!!!!!!!!!!

-----------------------------垃圾回收----------------------------------------

关于这个话题,其实没必要过分讨论,因为既然是皮影戏,那么肯定两边交互的时候 肯定不如原生,如果你纠结这个,那么请学好Java去吧。

然后就是硬件的问题,从09年到现在,内存啊 CPU什么的再移动终端上换了好几代了。从单核到双核到四核到八核。。。。。硬件的提升简直是要亲命的。

花好几个月优化的项目,可能新一代的的硬件直接藐视了以前的项目性能问题。。。(当然,不能不关注性能。在一些游戏 视频等对硬件渲染要求较高的时候,就能体现出性能的致命性!!)

好了看到这里,你也没必要继续往下看了。下面的是基于Xamarin的内存管理。

因为是皮影戏,两边其实是互相持有对方的信息的。耍皮影的根据皮影反馈的信息,进行下一步如何操作,皮影接到命令,就会在屏幕上显示对应的动作。。。

摘抄:http://www.cnblogs.com/wintersun/archive/2013/02/28/2937270.html

1 . 性能问题。 例如垃圾回收,Mono for Android 声称支持垃圾回收,但也有需要注意的一些严重限制。"GC不完整视图的进程,可能无法运行在内存不足时,由于GC不知道内存不足。"因为这通常需要手动,每当创建一个activity运行垃圾回收或销毁,以释放未使用的内存。否则,可能导致内存不足的异常。 我自己也不止一次碰到了这个问题,不得不使用替代方法来解决问题。 

关于内存管理:

许多Mono for Android被分配对象为包装Java对象做为它们的代表。 这时会发生什么:每次你分配一个包装过类型相对应Java类型,就创建两个对象: 

1). Java对象在Java堆中 

2). Mono代理对象在Mono堆中 

Mono for Android不能确保这两个对象相互引用后长时间存活。那就是,Mono的垃圾回收引用一个对象,Java端的对象将一直活着,反之亦然。这个代理对象的创建mandroid.exe是工具编译时完成。 然而,GC是懒惰的,按需运行的集合,而不是简单地对象超出范围时候。 那么这意味着跨虚拟机的垃圾至少比一般更多,这是不可避免的。所以,为了临时使用时分配一个大数字对象,显示释放那些对象所需的资源是宝贵的。约定的方法使用using关键字来new一个object,使用using子句来隐式释放目标的new object是有必要的。释放Mono端的包装的Java-VM收集的对象,从而来防止太多的临时对象关联在一起很长时间。

去官方网站了解更多 关于Mono for Android的垃圾回收

其实,Mono已经对垃圾回收机制,一直在优化。

针对iOS的MonoTouch目前支持了分代式垃圾回收器(generational garbage collector)SGen。直到不久之前,Sgen还只是完整版本Mono中的一个实验性部分。伴随着垃圾回收器一同到来的,还有一个为iOS准备的 内存分析器(Memory Profiler),它可以从MonoDevelop集成开发环境中访问到。

分代式垃圾回收器Sgen取代了Mono中传统的 Boehm垃圾回收器 。虽然Sgen预计会有更好的性能,但其保守式扫描仍然会给它带来一些阻碍。预计将来Sgen会切换到精确的堆栈标记系统,这样应当可以大幅度减少产生的内存碎片。

iOS分析器支持两种模式,默认模式是堆分析模式,在该模式中内存快照可以根据需要或者触发器进行采集,而该触发器可以设置成一定数量的垃圾收集器周期或者基于时间的时间间隔。此外,该分析器还包含了一些标准工具,如对比快照和查找内存中的对象位置。

性能分析支持统计抽样,以及精确性虽佳但速度不足的进入/离开事件记录。统计抽样采集应用程序快照,并根据每个方法被采集器抓取的频度对它们的速度 进行评估。从字面上看,进入/离开事件记录就是在函数每次开始或完成时记录一条日志。它虽然提供了时间花费的完整记录,但会带来严重的性能开销。这种模式 同样能够捕获与对象分配相关的堆栈跟踪。

MonoDevelop 2.8.5和MonoTouch 5.1.1都规定使用该工具。而对于个别项目,必须激活调试、分析和SGen垃圾回收器选项。

Sgen 项目一直在推进着。。。

关于怎么个机制,自己看官方文章吧。太长,不翻译了。

http://www.mono-project.com/docs/advanced/garbage-collector/sgen/

https://developer.xamarin.com/guides/android/advanced_topics/garbage_collection/

院子里倒是有点小资料,了解下就是了。

来自:http://www.cnblogs.com/shanyou/archive/2012/12/09/2810468.html

Mono 3现在是默认 GC是SGen 垃圾回收器,垃圾回收器几个性能和扩展性方面的改进,以更好地利用多核处理器硬件。SGen 已移植到 Windows 和 MIPS。

mono 最开始使用的是 Boehm-Demers-Wiser Conservative Garbage Collector ,mono 3.0之前的版本作为默认的垃圾收集器也是这个,Boehm垃圾收集器的主要问题在于无法精确读取寄存器与栈帧。因为无法确定给定值到底是指针还是标量,因此它总是假设给定值是指针,并且将相关联的对象标记为存活状态。这么做不仅会错误导致大块内存无法分配,同时还使得压缩可用空间这项工作变得异常艰难。

后来mono有了自己的 Simple Generational GC , 就是分代式垃圾回收器Sgen,取代了Mono中传统的 Boehm垃圾回收器 。文档地址: http://www.mono-project.com/Compacting_GC ,它使用精确的分代式(generational)垃圾收集器,类似于.NET版本的CLR。SGen垃圾收集器使用两生代而非.NET中的三个,但像.NET一样对于大对象使用独立的堆。

  1. 分为两代,之前使用 conservative gc..可见其文档的描述。是一种较为落后的实现,没有分代,.NET 的CLR是三代的
  2. 大对象特殊处理,默认大于64KB作为大对象,.NET的大对象是20KB以上,被分配到一个特殊的大对象堆中。该堆中的对象像其它小对象一样可以被finalized和释放。
  3. 小对象采用get_internal_mem/free_internal_mem 进行内存分配处理,大对象使用OS的malloc/free
  4. major collection 的时候采用 mark/sweep
  5. 收集进行时是 “stop the world”
  6. 保守扫描对象
  7. 老一代指向新一代的情况只有下面两种,所以都进行了跟踪:
  • 程序执行中,一个字段进行了赋值
  • 在复制(代移动)过程中,这个对象指向了一个新一代中的对象

Mono 3.2进一步提升了SGen GC,特别是针对下面的场景——

  • 流行对象负载,老一代对象非常欢迎的一些固定新生对象会导致创建很多记忆的集合。为了避免这种情况的发生,流行对象会被作为永恒对象直到下一次主收集。
  • 大规模线程栈负载,这种情况下需要合理地扫描大量大堆栈线程。这在以前通常会把大量压力放到规划阶段,因为它会产生非常大的固定队列。为了避免这种情况,SGen现在会在固定队列上使用哈希过滤,这大大降低了它们的平均大小,并且它会固定主要块而不是个别对象。

Mono 3.0添加了异步支持、改进的SGen垃圾收集器及其他特性

用Xwt构建跨平台应用程序

new Mono GC

SGen – Concurrent Mark

SGen – Concurrency and Evacuation 

Flame Graphs for Instruments 

How sgen rocks

SGen – The Write Barrier

SGen and DTrace

SGen – Finalization and Weak References

SGen – The Major Collectors

SGen

</div>