【Xamarin挖墙脚系列:使用Xamarin进行Hybrid应用开发】

原文: 【Xamarin挖墙脚系列:使用Xamarin进行Hybrid应用开发】

官方地址:https://developer.xamarin.com/guides/cross-platform/advanced/razor_html_templates/html

使用Xamarin进行网页形式的本地APP开发,感受有点不爽,不过为前端开发人员提供了开发APP的入口。前端

呈现引擎支持HTML  和ASP.NET MVC3的Razor引擎!android

Razor引擎是个好同志!ios

不过,建立Hybrid应用的框架不只仅是Xamarin。大名鼎鼎的Phonegape的   Apache Cordova  貌似更为成熟些。可是基于网页开发的Hybird应用,总感受傻不拉几的。呵呵git

这篇文章写的还能够:github

原文地址:http://www.chinaz.com/program/2015/0512/405306.shtmlsql

聊聊移动端跨平台开发的各类技术

介绍shell

最近出现的 React Native 再次让跨平台移动端开发这个话题火起来了,曾经你们觉得在手机上能够像桌面那样经过 Web 技术来实现跨平台开发,却大多由于性能或功能问题而放弃,不得不针对不一样平台开发多个版本。数据库

但这并无阻止人们对跨平台开发技术的探索,毕竟谁不想下降开发成本,一次编写就到处运行呢?除了 React Native,这几年还出现过许多其它解决方案,本文我将会对这些方案进行技术分析,供感兴趣的读者参考。json

为了方便讨论,我将它们分为了如下 4 大流派:

  • Web 流:也被称为 Hybrid 技术,它基于 Web 相关技术来实现界面及功能
  • 代码转换流:将某个语言转成 Objective-C、Java 或 C#,而后使用不一样平台下的官方工具来开发
  • 编译流:将某个语言编译为二进制文件,生成动态库或打包成 apk/ipa/xap 文件
  • 虚拟机流:经过将某个语言的虚拟机移植到不一样平台上来运行

Web 流

Web 流是你们都比较了解的了,好比著名的 PhoneGap/Cordova,它将原生的接口封装后暴露给 JavaScript,能够运行在系统自带的 WebView 中,也能够本身内嵌一个 Chrome 内核 。

做为这几年争论的热点,网上已经有不少关于它的讨论了,这里我重点聊聊你们最关心的性能问题。

Web 流最常被吐槽的就是性能慢(这里指内嵌 HTML 的性能,不考虑网络加载时间),可为何慢呢?常见的见解是认为「DOM 很慢」,然而从浏览器实现角度来看,其实 DOM 就是将对文档操做的 API 暴露给了 JavaScript,而 JavaScript 的调用这些 API 后就进入内部的 C++ 实现了,这中间并无多少性能消耗,因此从理论上来讲浏览器的 DOM 确定比 Android 的「DOM」快,由于 Android 的展示架构大部分功能是用 Java 写的,在实现相同功能的前提下,C++ 不大可能比 Java 慢(某些状况下 JIT 编译优化确实有可能作得更好,但那只是少数状况)。

因此从字面意思上看「DOM 很慢」的说法是错误的,这个见解之因此很广泛,多是由于大部分人对浏览器实现不了解,只知道浏览器有 DOM,因此无论什么问题都只能抱怨它了。

那么问题在哪呢?在我看来有三方面的问题:

  • 早期浏览器实现比较差,没有进行优化
  • CSS 过于复杂,计算起来更耗时
  • DOM 提供的接口太有限,使得难以进行优化

第一个问题是最关键也是最难解决的,如今说到 Web 性能差主要说的是 Android 下比较差,在 iOS 下已经很流畅了,在 Android 4 以前的 WebView 甚至都没有实现 GPU 加速,每次重绘整个页面,有动画的时候不卡才怪。

浏览器实现的优化能够等 Android 4.4 慢慢普及起来,由于 4.4 之后就使用 Chrome 来渲染了。

而对于最新的浏览器来讲,渲染慢的缘由就主要是第二个问题:CSS 过于复杂,由于从实现原理上看 Chrome 和 Android View 并无本质上的差异,但 CSS 太灵活功能太多了,因此计算成本很高,天然就更慢了。

那是否是能够经过简化 CSS 来解决?实际上还真有人这么尝试了,好比 Famo.us,它最大的特点就是不让你写 CSS,只能使用固定的几种布局方法,彻底靠 JavaScript 来写界面,因此它能有效避免写出低效的 CSS,从而提高性能。

而对于复杂的界面及手机下常见的超长的 ListView 来讲,第三个问题会更突出,由于 DOM 是一个很上层的 API,使得 JavaScript 没法作到像 Native 那样细粒度的控制内存及线程,因此难以进行优化,则在硬件较差的机器上会比较明显。对于这个问题,咱们一年前曾经尝试过嵌入原生组件的方式来解决,不过这个方案须要依赖应用端的支持,或许之后浏览器会自带几个优化后的 Web Components 组件,使用这些组件就能很好解决性能问题。

现阶段这三个问题都很差解决,因此有人想干脆不用 HTML/CSS,本身来画界面,好比 React canvas 直接画在 Canvas 上,但在我看来这只是现阶段解决部分问题的方法,在后面的章节我会详细介绍本身画 UI 的各类问题,这里说个历史吧,6 年前浏览器还比较慢的时候,Bespin 就这么干过,后来这个项目被使用 DOM 的 ACE 取代了,目前包括 TextMirror 和 Atom 在内的主流编辑器都是直接使用 DOM,甚至 W3C 有人专门写了篇文章吐槽用 Canvas 作编辑器的种种缺点,因此使用 Canvas 要谨慎。

另外除了 Canvas,还有人觉得 WebGL 快,就尝试绘制到 WebGL 上,好比 HTML-GL,但它目前的实现太偷懒了,简单来讲就是先用 html2canvas 将 DOM 节点渲染成图片,而后将这个图片做为贴图放在 WebGL 中,这等于将浏览器中用 C++ 写的东东在 JavaScript 里实现了一遍,渲染速度确定反而更慢,但却是能用 GLSL 作特效来忽悠人。

硬件加速不等同于「快」,若是你觉得硬件加速必定比软件快,那你该抽空学学计算机体系结构了

其实除了性能问题,我认为在 Web 流更严重的问题是功能缺失,好比 iOS 8 就新增 4000+ API,而 Web 标准须要漫长的编写和评审过程,根本赶不上,即使是 Cordova 这样本身封装也忙不过来,因此为了更好地使用系统新功能,写 Native 代码是必须的。

代码转换流

前面提到写 Native 代码是必须的,但不一样平台下的官方语言不同,这会致使一样的逻辑要写两次以上,因而就有人想到了经过代码转换的方式来减小工做量,好比将 Java 转成 Objective-C。

这种方式虽然听起来不是很靠谱,但它倒是成本和风险都最小的,由于代码转换后就能够用官方提供的各类工具了,和普通开发区别不大,所以不用担忧遇到各类诡异的问题,不过须要注意生成的代码是否可读,不可读的方案就别考虑了。

接下来看看目前存在的几种代码转换方式。

将 Java 转成 Objective-C

j2objc 能将 Java 代码转成 Objective-C,听说 Google 内部就是使用它来下降跨平台开发成本的,好比 Google Inbox 项目就号称经过它共用了 70% 的代码,效果很显著。

可能有人会以为奇怪,为什么 Google 要专门开发一个帮助你们写 Objective-C 的工具?还有媒体说 Google 作了件好事,其实吧,我以为 Google 这算盘打得不错,由于基本上重要的应用都会同时开发 Android 和 iOS 版本,有了这个工具就意味着,你能够先开发 Android 版本,而后再开发 iOS 版本。。。

既然都有成功案例了,这个方案确实值得尝试,并且关键是会 Java 的人多啊,能够经过它来快速移植代码到 Objective-C 中。

将 Objective-C 转成 Java

除了有 Java 转成 Objective-C,还有 Objective-C 转成 Java 的方案,那就是 MyAppConverter,比起前面的 j2objc,这个工具更有野心,它还打算将 UI 部分也包含进来,从它已转换的列表中能够看到还有 UIKit、CoreGraphics 等组件,使得有些应用能够不改代码就能转成功,不过这点我并不看好,对于大部分应用来讲并不现实。

因为目前是收费项目,我没有尝试过,对技术细节也不了解,因此这里不作评价。

将 Java 转成 C#

Mono 提供了一个将 Java 代码转成 C# 的工具 Sharpen,不过彷佛用的人很少,Star 才 118,因此看起来不靠谱。

还有 JUniversal 这个工具能够将 Java 转成 C#,但目前它并无发布公开版本,因此具体状况还待了解,它的一个特点是自带了简单的跨平台库,里面包括文件处理、JSON、HTTP、OAuth 组件,能够基于它来开发可复用的业务逻辑。

比起转成 Objective-C 和 Java 的工具,转成 C# 的这两个工具看起来都很是不成熟,估计是用 Windows Phone 的人少。

将 Haxe 转成其它语言

说到源码转换就不得不提 Haxe 这个奇特的语言,它没有本身的虚拟机或可执行文件编译器,因此只能经过转成其它语言来运行,目前支持转成 Neko(字节码)、Javascript、Actionscript 三、PHP、C++、Java、C# 和 Python,尽管有人实现了转成 Swift 的支持,但仍是非官方的,因此要想支持 iOS 开发目前只能经过 Adobe AIR 来运行。

在游戏开发方面作得不错,有个跨平台的游戏引擎 OpenFL 的,最终可使用 HTML5 Canvas、OpenGL 或 Flash 来进行绘制,OpenFL 的开发体验作得至关不错,同一行代码不须要修改就能编译出不一样平台下的可执行文件,由于是经过转成 C++ 方式进行编译的,因此在性能和反编译方面都有优点,惋惜目前彷佛并不够稳定,否则能够成为 Cocos2d-x 的有利竞品。

在 OpenFL 基础上还有个跨平台的 UI 组件 HaxeUI,但界面风格我以为特别丑,也就只能在游戏中用了。

因此目前来看 Haxe 作跨平台游戏开发或许可行,但 APP 开发就别期望了,而基于它来共用代码实在就更不靠谱了,由于熟悉它的开发者极少,反而增长成本。

XMLVM

除了前面提到的源码到源码的转换,还有 XMLVM 这种不同凡响的方式,它首先将字节码转成一种基于 XML 的中间格式,而后再经过 XSL 来生成不一样语言,目前支持生成 C、Objective-C、JavaScript、C#、Python 和 Java。

虽然基于一个中间字节码能够方便支持多语言,然而它也致使生成代码不可读,由于不少语言中的语法糖会在字节码中被抹掉,这是不可逆的,如下是一个简单示例生成的 Objective-C 代码,看起来就像汇编:

XMLVM_ENTER_METHOD("org.xmlvm.tutorial.ios.helloworld.portrait.HelloWorld", "didFinishLaunchingWithOptions", "?")XMLVMElem _r0;XMLVMElem _r1;XMLVMElem _r2;XMLVMElem _r3;XMLVMElem _r4;XMLVMElem _r5;XMLVMElem _r6;XMLVMElem _r7;_r5.o = me;_r6.o = n1;_r7.o = n2;_r4.i = 0;_r0.o = org_xmlvm_iphone_UIScreen_mainScreen__();XMLVM_CHECK_NPE(0)_r0.o = org_xmlvm_iphone_UIScreen_getApplicationFrame__(_r0.o);_r1.o = __NEW_org_xmlvm_iphone_UIWindow();XMLVM_CHECK_NPE(1)...

在我看来这个方案至关不靠谱,万一辈子成的代码有问题基本无法修改,也无法调试代码,因此不推荐。

小结

虽然代码转换这种方式风险小,但我以为对于不少小 APP 来讲共享不了多少代码,由于这类应用大多数围绕 UI 来开发的,大部分代码都和 UI 耦合,因此公共部分很少。

在目前的全部具体方案中,只有 j2objc 能够尝试,其它都不成熟。

编译流

编译流比前面的代码转换更进一步,它直接将某个语言编译为普通平台下的二进制文件,这种作法有明显的优缺点:

  • 优势
    • 能够重用一些实现很复杂的代码,好比以前用 C++ 实现的游戏引擎,重写一遍成本过高
    • 编译后的代码反编译困难
    • 或许性能会好些(具体要看实现)
  • 缺点
    • 若是这个工具自己有 Bug 或性能问题,定位和修改为本会很高
    • 编译后体积不小,尤为是若是要支持 ARMv8 和 x86 的话

接下来咱们经过区分不一样语言来介绍这个流派下的各类方案。

C++ 类

C++ 是最多见的选择,由于目前 Android、iOS 和 Windows Phone 都提供了 C++ 开发的支持,它一般有三种作法:

  • 只用 C++ 实现非界面部分,这是官方比较推崇的方案,目前有不少应用是这么作的,好比 Mailbox 和 Microsoft Office。
  • 使用 2D 图形库来本身绘制界面,这种作法在桌面比较常见,由于不少界面都有个性化需求,但在移动端用得还很少。
  • 使用 OpenGL 来绘制界面,常见于游戏中。

使用 C++ 实现非界面部分比较常见,因此这里就不重复介绍了,除了能提高性能和共用代码,还有人使用这种方式来隐藏一些关键代码(好比密钥),若是你不知道如何构建这样的跨平台项目,能够参考 Dropbox 开源的 libmx3 项目,它还内嵌了 json 和 sqlite 库,并经过调用系统库来实现对简单 HTTP、EventLoop 及建立线程的支持。

而若是要用 C++ 实现界面部分,在 iOS 和 Windows Phone 下能够分别使用 C++ 的超集 Objective-C++ 和 C++/CX,因此还比较容易,但在 Android 下问题就比较麻烦了,主要缘由是 Android 的界面绝大部分是 Java 实现的,因此用 C++ 开发界面最大的挑战是如何支持 Android,这有两种作法:经过 JNI 调用系统提供的 Java 方法或者本身画 UI。

第一种作法虽然可行,但代码太冗余了好比一个简单的函数调用须要写那么多代码:

JNIEnv* env;jclass testClass = (*env)->FindClass(env, "com/your/package/name/Test"); 
// get ClassjmethodID constructor = (*env)->GetMethodID(env, cls, "", "()V");jobject testObject = (*env)->NewObject(env, testClass, constructor);methodID callFromCpp = (*env)->GetMethodID(env, testClass, "callFromCpp", "()V");
//get methodid(*env)->CallVoidMethod(env, testObject, callFromCpp);

那本身画 UI 是否会更方便点?好比 JUCE 和 QT 就是本身画的,咱们来看看 QT 的效果:

移动开发技术 跨平台开发技术 开发技术

看起来很不错是吧?不过在 Android 5 下就悲剧了,不少效果都没出来,好比按钮没有涟漪效果,甚至边框都没了,根本缘由在于它是经过 Qt Quick Controls 的自定义样式来模拟的,而不是使用系统 UI 组件,所以它享受不到系统升级自动带来的界面优化,只能本身再实现一遍,工做量不小。

反而若是最开始用的是 Android 原生组件就什么都不须要作,并且还能用新的 AppCompat 库来在 Android 5 如下实现 Material Design 效果。

最后一种作法是使用 OpenGL 来绘制界面,由于 EGL+OpenGL 自己就是跨平台,因此基于它来实现会很方便,目前大多数跨平台游戏底层都是这么作的。

既然能够基于 OpenGL 来开发跨平台游戏,是否能用它来实现界面?固然是可行的,并且 Android 4 的界面就是基于 OpenGL 的,不过它并非只用 OpenGL 的 API,那样是不现实的,由于 OpenGL API 最初设计并非为了画 2D 图形的,因此连画个圆形都没有直接的方法,所以 Android 4 中是经过 Skia 将路径转换为位置数组或纹理,而后再交给 OpenGL 渲染的。

然而要彻底实现一遍 Android 的 UI 架构工做量不小,如下是其中部分相关代码的代码量:

移动开发技术 跨平台开发技术 开发技术

其中光是文字渲染就很是复杂,若是你以为简单,那只能说明你没看过这个世界有多大,或许你知道中文有编码问题、英语有连字符(hyphen)折行,但你是否知道繁体中文有竖排版、阿拉伯文是从右到左的、日语有平假名注音(ルビ)、印度语有元音附标文字(abugida አቡጊዳ)……?

而相比之下若是每一个平台单独开发界面,看似工做量不小,但目前在各个平台下都会有良好的官方支持,相关工具和文档都很完善,因此其实成本没那么高,并且能够给用户和系统风格保持一致的良好体验,因此我认为对于大多数应用来讲本身画 UI 是很不划算的。

不过也有特例,对于 UI 比较独特的应用来讲,本身画也是有好处的,除了更灵活的控制,它还能使得不一样平台下风格统一,这在桌面应用中很常见,好比 Windows 下你会发现几乎每一个必备软件的 UI 都不太同样,并且好多都有换肤功能,在这种状况下很适合本身画 UI。

Xamarin

Xamarin 可使用 C# 来开发 Android 及 iOS 应用,它是从 Mono 发展而来的,目前看起来商业运做得不错,相关工具及文档都挺健全。

由于它在 iOS 下是以 AOT 的方式编译为二进制文件的,因此把它归到编译流来讨论,其实它在 Android 是内嵌了 Mono 虚拟机 来实现的,所以须要装一个 17M 的运行环境。

在 UI 方面,它能够经过调用系统 API 来使用系统内置的界面组件,或者基于 Xamarin.Forms 开发定制要求不高的跨平台 UI。

对于熟悉 C# 的团队来讲,这还真是一个看起来很不错的,但这种方案最大的问题就是相关资料不足,遇到问题极可能搜不到解决方案,不过因为时间关系我并无仔细研究,推荐看看这篇文章,其中谈到它的优缺点是:

  • 优势
    • 开发 app 所需的基本功能所有都有
    • 有商业支持,并且这个项目对 Windows Phone 颇有利,微软会大力支持
  • 缺点
    • 若是深刻后会发现功能缺失,尤为是定制 UI,由于未开源使得遇到问题时不知道如何修复
    • Xamarin 自己有些 Bug
    • 相关资源太少,没有原平生台那么多第三方库
    • Xamarin studio 比起 Xcode 和 Android Studio 在功能上还有很大差距

微软知道本身的 Windows Phone 太非主流,因此很懂事地推出了将 Objective-C 项目编译到 Windows Phone 上运行的工具,目前这个工具的相关资料不多,鉴于 Visual Studio 支持 Clang,因此极有多是使用 Clang 的前端来编译,所以我归到编译流。

而对于 Android 的支持,微软应该使用了虚拟机的方式,因此放到下个章节介绍。

RoboVM

RoboVM 能够将 Java 字节码编译为可在 iOS 下运行的机器码,这有点相似 GCJ,但它的具体实现是先使用 Soot 将字节码编译为 LLVM IR,而后经过 LLVM 的编译器编译成不一样平台下的二进制文件。

好比简单的 new UITextField(new CGRect(44, 32, 232, 31)) 最后会变以下的机器码(x86):

call imp___jump_table__[j]org.robovm.apple.uikit.UITextField[allocator][clinit]mov esi, eaxmov dword [ss:esp], ebxcall imp___jump_table__[j]org.robovm.apple.coregraphics.CGRect[allocator][clinit]mov edi, eaxmov dword [ss:esp+0x4], edimov dword [ss:esp], ebxmov dword [ss:esp+0xc], 0x40460000...

基于字节码编译的好处是能够支持各类在 JVM 上构建的语言,好比 Scala、Kotlin、Clojure 等。

在运行环境上,它使用的 GC 和 GCJ 同样,都是 Boehm GC,这是一个保守 GC,会有内存泄露问题,尽管官方说已经优化过了影响不大。

在 UI 的支持方面,它和 Xamarin 挺像,能够直接用 Java 调用系统接口来建立界面(最近支持 Interface Builder 了),好比上面的示例就是。另外还号称能使用 JavaFX,这样就能在 iOS 和 Android 上使用同一套 UI 了,不过目前看起来很不靠谱。

在我看来 RoboVM 目前最大的用途就是使用 libGDX 开发游戏了,尽管在功能上远不如 Cocos2d-x(尤为是场景及对象管理),但无论怎么说用 Java 比 C++ 仍是方便不少(别跟我说没人用 Java 作游戏,价值 25 亿美圆的 Minecraft 就是),不过本文主要关心的是 UI 开发,因此这方面的话题就不深刻讨论了,

RoboVM 和 Xamarin 很像,但 RoboVM 风险会小些,由于它只须要把 iOS 支持好就好了,对优先开发 Android 版本的团队挺适用,但目前官方文档太少了,并且不清楚 RoboVM 在 iOS 上的性能和稳定性怎样。

Swift - Apportable/Silver

apportable 能够直接将 Swift/Objective-C 编译为机器码,但它官网的成功案例所有都是游戏,因此用这个来作 APP 感受很不靠谱。

因此后来它又推出了 Tengu 这个专门针对 APP 开发的工具,它的比起以前的方案更灵活些,本质上有点相似 C++ 公共库的方案,只不过语言变成了 Swift/Objective-C,使用 Swift/Objective-C 来编译生成跨平台的 SO 文件,提供给 Android 调用。

另外一个相似的是 Silver,不过目前没正式发布,它不只支持 Swift,还支持 C# 和自创的 Oxygene 语言(看起来像 Pascal),在界面方面它还有个跨平台非 UI 库 Sugar,然而目前 Star 数只有 17,太非主流了,因此实在懒得研究它。

使用 Swift 编译为 SO 给 Android 用虽然可行,但目前相关工具都不太成熟,因此不推荐使用。

Go

Go 是最近几年很火的后端服务开发语言,它语法简单且高性能,目前在国内有很多用户。

Go 从 1.4 版本开始支持开发 Android 应用(并将在 1.5 版本支持 iOS),不过前只能调用不多 的 API,好比 OpenGL 等,因此只能用来开发游戏,但我感受并不靠谱,如今还有谁直接基于 OpenGL 开发游戏?大部分游戏都是基于某个框架的,而 Go 在这方面太缺少了,我只看到一个桌面端 Azul3D,并且很是不成熟。

由于 Android 的 View 层彻底是基于 Java 写的,要想用 Go 来写 UI 不可避免要调用 Java 代码,而这方面 Go 尚未简便的方式,目前 Go 调用外部代码只能使用 cgo,经过 cgo 再调用 jni,这须要写不少中间代码,因此目前 Go 1.4 采用的是相似 RPC 通信的方式来作,从它源码中例子能够看出这种方式有多麻烦,性能确定有不小的损失。

并且 cgo 的实现自己就对性能有损失,除了各类无关函数的调用,它还会锁定一个 Go 的系统线程,这会影响其它 gorountine 的运行,若是同时运行太多外部调用,甚至会致使全部 gorountine 等待。

这个问题的根源在于 Go 的栈是能够自动扩充的,这种方式有利于建立无数 gorountine,但却也致使了没法直接调用 C 编译后的函数,须要进行栈切换。

因此使用 Go 开发跨平台移动端应用目前不靠谱。

话说 Rust 没有 Go 的性能,它调用 C 函数是没有性能损耗的,但目前 Rust 还没提供对 iOS/Android 的官方支持,尽管有人仍是尝试过是可行的,但如今还不稳定,从 Rust 语言自己的设计来看,它挺适合取代 C++ 来作这种跨平台公共代码,但它的缺点是语法复杂,会吓跑不少开发者。

Xojo

我以前一直觉得 BASIC 挂了,没想到还有这么一个特例,Xojo 使用的就是 BASIC,它有看起来很强大的 IDE,让人感受像是在用 VisualBasic。

它的定位应该是给小朋友或业余开发者用的,由于彷佛看起来学习成本低,但我不这么认为,由于用得人少,反而网上资料会不多,因此恐怕成本会更高。

由于时间关系,以及对 BASIC 无爱,我并无怎么研究它。

小结

从目前分析的状况看,C++ 是比较稳妥的选择,但它对团队成员有要求,若是你们都没写过 C++,能够试试 Xamrin 或 RoboVM。

虚拟机流

除了编译为不一样平台下的二进制文件,还有另外一种常见作法是经过虚拟机来支持跨平台运行,好比 JavaScript 和 Lua 都是天生的内嵌语言,因此在这个流派中不少方案都使用了这两个语言。

不过虚拟机流会遇到两个问题:一个是性能损耗,另外一个是虚拟机自己也会占不小的体积。

Java 系

说到跨平台虚拟机你们都会想到 Java,由于这个语言一开始就是为了跨平台设计的,Sun 的 J2ME 早在 1998 年就有了,在 iPhone 出来前的手机上,不少小游戏都是基于 J2ME 开发的,这个项目至今还活着,能运行在 Raspberry Pi 上。

前面提到微软提供了将 Objective-C 编译在 Windows Phone 上运行的工具,在对 Android 的支持上我没找到的详细资料,因此就暂时认为它是虚拟机的方式,从 Astoria 项目的介绍上看它作得很是完善,不只能支持 NDK 中的 C++,还实现了 Java 的 debug 接口,使得能够直接用 Android Studio 等 IDE 来调试,整个开发体验和在 Android 手机上几乎没区别。

另外 BlackBerry 10 也是经过内嵌虚拟机来支持直接运行 Android 应用,不过听说比较卡。

不过前面提到 C# 和 Java 在 iOS 端的方案都是经过 AOT 的方式实现的,目前还没见到有 Java 虚拟机的方案,我想主要缘由是 iOS 的限制,普通 app 不能调用 mmap、mprotect,因此没法使用 JIT 来优化性能,若是 iOS 开放,或许哪天有人开发一个像微软那样能直接在 iOS 上运行 Android 应用的虚拟机,就不须要跨平台开发了,你们只须要学 Android 开发就够了。。。

Titanium/Hyperloop

Titanium 应该很多人听过,它和 PhoneGap 几乎是同时期的著名跨平台方案,和 PhoneGap 最大的区别是:它的界面没有使用 HTML/CSS,而是本身设计了一套基于 XML 的 UI 框架 Alloy,代码相似下面这个样子:

app/styles/index.tss".container": {  backgroundColor:"white"},// This is applied to all Labels in the view"Label": {  width: Ti.UI.SIZE,  height: Ti.UI.SIZE,  color: "#000", // black  transform: Alloy.Globals.rotateLeft // value is defined in the alloy.js file},// This is only applied to an element with the id attribute assigned to "label""#label": {  color: "#999" /* gray */}app/views/index.xml        

前面咱们说过因为 CSS 的过于灵活拖累了浏览器的性能,那是否本身创建一套 UI 机制会更靠谱呢?尽管这么作对性能确实有好处,然而它又带来了学习成本问题,作简单的界面问题不大,一旦要深刻定制开发就会发现相关资料太少,因此仍是不靠谱。

Titanium 还提供了一套跨平台的 API 来方便调用,这么作是它的优势更是缺点,尤为是下面三个问题:

  1. API 有限,由于这是由 Titanium 提供的,它确定会比官方 API 少且有延迟,Titanium 是确定跟不过来的
  2. 相关资料及社区有限,比起 Android/iOS 差远了,遇到问题都不知道去哪找答案
  3. 缺少第三方库,第三方库确定不会专门为 Titanium 提供一个版本,因此无论用什么都得本身封装

Titanium 也意识到了这个问题,因此目前在开发下一代的解决方案 Hyperloop,它能够将 JavaScript 编译为原生代码,这样的好处是调用原生 API 会比较方便,好比它的 iOS 是这样写的

@import("UIKit");@import("CoreGraphics");var view = new UIView();view.frame = CGRectMake(0, 0, 100, 100);

这个方案和以前的说的 Xamarin 很类似,基本上等于将 Objective-C 翻译为 JavaScript 后的样子,意味着你能够对着 Apple 的官方文档开发,不过若是发现某些 Objective-C 语法发现不知道对应的 JavaScript 怎么写时就悲剧了,只有本身摸索。

但从 Github 上的提交历史看,这项目都快开发两年了,但至今仍然是试验阶段,从更新频率来看,最近一年只提交了 8 次,因此恐怕是要弃坑了,很是不靠谱。

所以我认为 Titanium/Hyperloop 都很是不靠谱,不推荐使用。

NativeScript

以前说到 Titanium 自定义 API 带来的各类问题,因而就有人换了个思路,好比前段时间推出的 NativeScript,它的方法说白了就是用工具来自动生成 wrapper API,和系统 API 保持一致。

有了这个自动生成 wrapper 的工具,它就能方便基于系统 API 来开发跨平台组件,以简单的 Button 为例,源码在 cross-platform-modules/ui/button 中,它在 Android 下是这样实现的(TypeScript 省略了不少代码)

export class Button extends common.Button {    private _android: android.widget.Button;    private _isPressed: boolean;    public _createUI() {        var that = new WeakRef(this);        this._android = new android.widget.Button(this._context);        this._android.setOnClickListener(new android.view.View.OnClickListener({            get owner() {                return that.get();            },            onClick: function (v) {                if (this.owner) {                    this.owner._emit(common.knownEvents.tap);                }            }        }));    }}

而在 iOS 下是这样实现的(省略了不少代码)

export class Button extends common.Button {    private _ios: UIButton;    private _tapHandler: NSObject;    private _stateChangedHandler: stateChanged.ControlStateChangeListener;    constructor() {        super();        this._ios = UIButton.buttonWithType(UIButtonType.UIButtonTypeSystem);        this._tapHandler = TapHandlerImpl.new().initWithOwner(this);        this._ios.addTargetActionForControlEvents(this._tapHandler, "tap", UIControlEvents.UIControlEventTouchUpInside);        this._stateChangedHandler = new stateChanged.ControlStateChangeListener(this._ios, (s: string) => {            this._goToVisualState(s);        });    }    get ios(): UIButton {        return this._ios;    }}

能够看到用法和官方 SDK 中的调用方式是同样的,只不过语言换成了 JavaScript,而且写法看起来比较诡异罢了,风格相似前面的 Hyperloop 相似,因此也一样会有语法转换的问题。

这么作最大的好处就是能完整支持全部系统 API,对于第三方库也能很好支持,但它目前最大缺点是生成的文件体积过大,即使什么都不作,生成的 apk 文件也有 8.4 MB,由于它将全部 API binding 都生成了,并且这也致使在 Android 下首次打开速度很慢。

从底层实现上看,NativeScript 在 Android 下内嵌了 V8,而在 iOS 下内嵌了本身编译的 JavaScriptCore(这意味着没有 JIT 优化,具体缘由前面提到了),这样的好处是能调用更底层的 API,也避免了不一样操做系统版本下 JS 引擎不一致带来的问题,但后果是生成文件的体积变大和在 iOS 下性能不如 WKWebView。

WKWebView 是基于多进程实现的,它在 iOS 的白名单中,因此能支持 JIT。

它的使用体验很不错,作到了一键编译运行,并且还有 MVVM 的支持,能进行数据双向绑定。

在我看来 NativeScript 和 Titanium 都有个很大的缺点,那就是排它性太强,若是你要用这两个方案,就得完整基于它们进行开发,不能在某些 View 下进行尝试,也不支持直接嵌入第三方 View,有没有方案能很好地解决这两个问题?有,那就是咱们接下来要介绍的 React Native。

 

React Native

关于 React Native 目前网上有不少讨论了,知乎上也有很多回答,尽管有些回答从底层实现角度看并不许确,但大部分关键点却是都提到了。

鉴于我不喜欢重复别人说过的话,这里就聊点别的。

React Native 的思路简单来讲就是在不一样平台下使用平台自带的 UI 组件,这个思路并不新奇,十几年前的 SWT 就是这么作的。

从团队上看,Facebook 的 iOS 团队中很多成员是来自 Apple 的,好比 Paper 团队的经理及其中很多成员都是,由于 iOS 不开源,因此从 Apple 中出来的开发者仍是有优点的,好比前 Apple 开发者搞出来的 Duet 就秒杀了市面上全部其余方案,并且从 Facebook 在 iOS 上开源的项目看他们在 iOS 方面的经验和技术都不错,因此从团队角度看他们作出来的东西不会太差。

在作 React Native 方案的同时,其实 Facebook 还在作一个 Objective-C++ 上相似 React 的框架 ComponentKit,如下是它的代码示例:

@implementation ArticleComponent+ (instancetype)newWithArticle:(ArticleModel *)article{  return [super newWithComponent:          [CKStackLayoutComponent           newWithView:{}           size:{}           style:{             .direction = CKStackLayoutDirectionVertical,           }           children:{             {[HeaderComponent newWithArticle:article]},             {[MessageComponent newWithMessage:article.message]},             {[FooterComponent newWithFooter:article.footer]},           }];}@end

它的可读性比 JSX 中的 XML 差了很多,并且随着你们逐步接受 Swift,这种基于 Objective-C++ 的方案恐怕没几年就过期了,因此 Facebook 押宝 React 是比较正确的。

我看到有人说这是 Facebook 回归 H5,但其实 React Native 和 Web 扯不上太多关系,我所理解的 Web 是指 W3C 定义的那些规范,好比 HTML、CSS、DOM,而 React Native 主要是借鉴了 CSS 中的 Flexbox 写法,还有 navigator、XMLHttpRequest 等几个简单的 API,更别说彻底没有 Web 的开放性,因此 React Native 和 HTML 5 彻底不是一回事。

Facebook Groups 的 iOS 版本很大一部分基于 React Native 开发,其中用到了很多内部经过组件,好比 ReactGraphQL,这里我就八卦一下它,GraphQL 这是一个结构化数据查询的语法,就像 MongoDB 查询语法那样查询 JSON 数据,不过它并非一种文档型数据库,而只是一个中间层,具体的数据源能够连其它数据库,它想取代的应该是 RESTful 那样的先后端简单 HTTP 协议,让前端更方便的获取数据,听说将会开源(看起来打算用 Node 实现)。

写文章拖时间太长的问题就是这期间会发生不少事情,好比 GraphQL 在我开始写的时候外界都不知道,因此须要八卦一下,结果如今官方已经宣布了,不过官方并没提到我说的那个 Node 实现,它目前还在悄悄开发阶段

React Native 的官方视频中说它能作到 App 内实时更新,其实这是 Apple 明文禁止的(App Store Review Guidelines 中的 2.7),要作得低调。

我比较喜欢的是 React Native 中用到了 Flow,它支持定义函数参数的类型,极大提高了代码可读性,另外还能使用 ES6 的语法,好比 class 关键字等。

React Native 比传统 Objective-C 和 UIView 的学习成本低多了,熟悉 JavaScript 的开发者应该半天内就能写个使用标准 UI 的界面,并且用 XML+CSS 画界面也远比 UIView 中用 Frame 进行手工布局更易读(我没用过 Storyboards,它虽然看起来直观,但多人编辑很容易冲突),感兴趣能够抽空看看这个详细的入门教程,亲自动手试试就能体会到了,Command + R 更新代码感受很神奇。

它目前已经有组件仓库了,并且在 github 上都有 500 多仓库了,其中有 sqlite、Camera 等原生组件,随着这些第三方组件的完善,基于 React Native 开发愈来愈不须要写原生代码了。

不过坏消息是 React Native 的 Android 版本还要等半年,这能够理解,由于在 Android 上问题要复杂得多,有 Dalvik/ART 拦在中间,使得交互起来很麻烦。

NativeScript 和 React Native 在侧重点上有很大的不一样,使得这两个产品目前走向了不一样的方向:

  • React Native 要解决的是开发效率问题,它并没期望彻底取代 Native 开发,它的 rootView 继承自 UIView,因此能够在部分 View 是使用,很方便混着,不须要重写整个 app,并且混用的时候还须要显示地将 API 暴露给 JavaScript
  • NativeScript 则像是 Titanium 那样企图彻底使用 JavaScript 开发,将全部系统 API 都暴露给了 JavaScript,让 JavaScript 语言默认就拥有 Native 语言的各类能力,而后再次基础上来开发

方向的不一样致使这两个产品将会有不一样的结局,我认为 React Native 确定会完胜 NativeScript,由于它的使用风险要小不少,你能够随时将部分 View 使用 React Native 来试验,遇到问题就改回 Native 实现,风险可控,而用 NativeScript 就不行了,这致使你们在技术选型的时候不敢使用 NativeScript。

话说 Angular 团队看到 React Native 后表示不淡定了,因而开始从新设计 Angular 2 的展示架构,将现有的 Render 层独立出来,以便于作到像 React 那样适应不一样的运行环境,能够运行在 NativeScript 上。

综合来看,我以为 React Native 很值得尝试,并且风险也不高。

游戏引擎中的脚本

游戏引擎大多都能跨平台,为了提高开发效率,很多引擎还内嵌了对脚本支持,好比:

  • Ejecta,它实现了 Canvas 及 Audio 的 API,能够开发简单的游戏,但目前还不支持 Android
  • CocoonJS,实现了 WebGL 的 API,能够运行 Three.js 写的游戏
  • Unreal Engine 3,可使用 UnrealScript 来开发,这个语言的语法很像 Java
  • Cocos2d-js,Cocos2d-x 的 JavaScript binding,它内部使用的 JS 引擎是 SpiderMonkey
  • Unity 3D,可使用 C# 或 JavaScript 开发游戏逻辑
  • Corona,使用 Lua 来开发
  • ...

目前这种方式只有 Unity 3D 发展比较好,Cocos2d-JS 听说还行,有些小游戏在使用,Corona 感受比较非主流,虽然它也支持简单的按钮等界面元素,但用来写 APP 我不看好,由于不开源因此没研究,目前看来最大的好处彷佛是虚拟机体积小,内嵌版本官方号称只有 1.4M,这是 Lua 引擎比较大的优点。

而剩下的 3 个都基本上挂了,Ejecta 至今还不支持 Android,CocoonJS 转型为相似 Crosswalk 的 WebView 方案,而 Unreal Engine 4 开始再也不支持 UnrealScript,而是转向了使用 C++ 开发,感兴趣能够围观一下 Epic 创始人解释为何要这么作。

固然,这些游戏引擎都不适合用来作 APP,一方面是会遇到前面提到的界面绘制问题,另外一方面游戏引擎的实现通常都要不断重绘,这确定比普通 App 更耗电,很容易被用户发现后怒删。

Adobe AIR

尽管 Flash 放弃了移动端下的浏览器插件版本,但 Adobe AIR 还没挂,对于熟悉 ActionScript 的团队来讲,这是一种挺好的跨平台游戏开发解决方案,国内游戏公司以前有用,如今还有没人用我就不知道了。

但开发 APP 方面,它一样缺少好的 UI 库,Flex 使用体验不好,目前基本上算挂了,目前只有 Feathers 还算能看,不过主要是给游戏中的 UI 设计的,并不适合用来开发 APP。

Dart

Dart 在 Web 基本上失败了,因而开始转战移动开发,目前有两个思路,一个是相似 Lua 那样的嵌入语言来统一公共代码,但由于 Dart 虚拟机源自 V8,在一开始设计的时候就只有 JIT 而没有解释器,甚至连字节码都没有,因此它没法在 iOS 下运行,因而 Dart 团队又作了个小巧的虚拟机 Fletch,它基于传统的字节码解释执行方式来运行,目前代码只有 1w 多行,和 Lua 同样轻量级。

另外一个就是最近比较热门的 Sky,这里吐槽一下国内外的媒体,我看到的报道都是说 Google 想要用 Dart 取代 Android 下的 Java 开发。。。这个东东确实是 Google 的 Chrome 团队开发的,但 Google 是一个很大的公司好很差,内部有无数小团队,某个小团队并不能表明个 Google,若是真是 Google 高层的决定,它将会在 Google I/O 大会主题演讲上推出来,而不是 Dart Developer Summit 这样非主流的技术分享。

有报道称 Sky 只支持在线应用,不支持离线,这错得太离谱了,人家只是为了演示它的在线更新能力,你要想将代码内嵌到 app 里固然是能够的。

Sky 的架构以下图所示,它参考了 Chrome,依靠一个消息系统来和本地环境进行通信,使得 Dart 的代码和平台无关,能够运行在各类平台上。

移动开发技术 跨平台开发技术 开发技术

若是你读过前面的文章,那你必定和我同样很是关心一个问题:Sky 的 UI 是怎么绘制出来的?使用系统仍是本身画?一开始看 Sky 介绍视频的时候,我还觉得它底层绘制基于 Chrome,由于这个视频的演讲者是 Eric Seidel,他是 WebKit 项目中很是有名的开发者,早年在 Apple 开发 WebKit,2008 年跳槽去了 Chrome 团队,但他在演讲中并无提到 WebView,并且演示的时候界面很是像原生 Material Design 效果(好比点击有涟漪效果),因此我又以为它是相似 React Native 那样使用原生 UI。

然而当我下载那个应用分析后发现,它既没使用 Chrome/WebView 也没使用原生 UI 组件,难不成是本身绘制的?

从 Sky SDK 的代码上看,它其中有很是多 Web 的痕迹,好比支持标准的 CSS、不少 DOM API,但它编译后的体积很是小,libsky_shell.so 只有 8.7 MB,我以前尝试精简过 Chrome 内核,将 WebRTC 等周边功能删掉也要 22 MB,这么小的体积确定要删 Web 核心功能,好比 SVG 和部分 CSS3,因此我怀疑它实现了简版的 Chrome 内核渲染。

后来无心间看了一下 Mojo 的代码,才证明确实如此,原来前面那张图中介绍的 Mojo 其实并不完整,Mojo 不只仅是一个消息系统,它是一个简版的 Chrome 内核!使用 cloc 统计代码就暴露了:

移动开发技术 跨平台开发技术 开发技术

C++ 不包含注释的代码部分就有近 70w 行啊,并且一看目录结构就是浓浓的 Chromium 风格,至少从技术难度来讲绝对秒掉前面全部方案,也印证了我前面说过若是有简化版 CSS/HTML 就能很好解决性能问题。

这也让我理解了为何 Eric 在谈到 Mojo 的时候语焉不详,让人误觉得仅仅是一个消息系统,他要是明确说这是一个精简版 Chrome,那得引发多大的误会啊,没准会有小编用「Google 宣布开发下一代浏览器内核取代 Blink」这样的标题了。

以前 Dart 决定不将 Dart VM 放到 Chrome 里,原来并非由于被众人反对而死心了,而是由于 fork 了一个 Chrome 本身拿来玩了。

综合来看,目前 Dart 的这两个方案都很是不成熟,Sky 虽然在技术上看很强大,但 Dart 语言目前接受度很是低,比起它所带来的跨平台优势,它的缺点更大,好比没法使用第三方 Native UI 库,也没法使用第三方 Web UI 库,这致使它的社区会很是难发展,命中注定非主流,真惋惜了这帮技术大牛,但方向比努力更重要,但愿他们能尽早醒悟,让 Sky 也支持 JavaScript。

结论及参考

看到这里估计很多读者晕了,有那么多种方案,最后到底哪一个最适合本身?该学哪一个?这里简单说说个人见解。

若是你只会 JavaScript,那目前最好的方案是 React Native,有了它你即便不了解 Native 开发也能写出不少中小应用,等万一火了再学 Native 开发也不迟啊。

若是你只会 Java,那能够尝试 RoboVM 或 j2objc,j2objc 虽然目前更稳定靠谱,但它不能像 RoboVM 那样彻底用 Java 开发,因此你还得学 Objective-C 来写界面,而 RoboVM 的缺点就是貌似还不太稳定,并且彷佛除了游戏之外还没见到比较知名的应用使用,而它这种方案注定会比 j2objc 更容易出问题,因此你得作好踩坑的心理准备。

若是你只会 C#,那惟一的选择就是 Xamarin 了。

若是你只会 Objective-C,很杯具目前没有比较靠谱的方案,我建议你仍是学学 Java 吧,多学一门语言没啥坏处。

若是你只会 C++,能够作作游戏或非 UI 的公共部分,我不建议使用 QT 或本身画界面,仍是学学 Native 开发吧。

若是你只会 Go,还别期望用它开发移动端,由于目前的实现很低效,并且这和 Go 底层的实现机制密切相关,致使很难优化,因此预计很长一段时间内也不会有改观。

若是你会 Rust,说明你很喜欢折腾,多半也会前面全部语言,本身作决定吧。。。

相关文章
相关标签/搜索