当前位置: 首页 > 科技观察

如何开发移动跨平台库

时间:2023-03-18 11:39:14 科技观察

“一次编写,随处运行”是用来说明Java跨平台能力的著名口号。程序员希望他们的代码可以随处运行。尽管如此,要跟上CPU架构的变化是如此困难,每天都会弹出新的编程语言,框架来来去去,如果你想与操作系统打交道,代码可重用性并不重要。但也许,如果我们将范围限制在移动设备上,也许还有机会!近年来,移动化的趋势越来越明显,开发移动跨平台肯定会对一些开发者有所帮助。如果我们进一步缩小范围,到iOS和Android,它们目前的市场份额为93.9%。这将我们锁定在7个目标CPU架构/ABI(ARMv7、armv7s、用于IOS的arm64、armeabi、armeabi-V7A、x86和用于Android的MIPS)和两种编程语言(用于iOS的Objective-C和用于Android的Java)).至于框架和操作系统,支持最新的两个iOS版本应该就足够了,因为较新的iOS版本使用率更高,但是当涉及到Android时,为了获得良好的覆盖率,我们需要支持Froyo(或Gingerbread)及更高版本。如您所见,这不是一件容易的事,但我们需要这样做。我们要做的事情总结在下图中;使用一些特定于平台的粘合代码在两个平台之间共享一个库。由于Skyscanner严重依赖互联网,因此某些网络功能必不可少。通常,在iOS中,可以通过Objective-C源代码或带有相应头文件的预编译静态二进制库来导入库。在Android中,除了Java源代码之外,还可以通过.class(字节码)文件和静态/共享二进制库来导入库。然而,由于这些选项是有限制的,这里的研究更进一步探索在Android和iOS中导入代码的替代方法。那么,我们如何开始呢?有什么选择?有没有简化的工具?方案一——移动端跨平台开发工具如果你是一名移动端开发者,你一定听说过PhoneGap、AppceleratorTitanium、Xamarin等数不胜数的移动端跨平台开发工具。其中一些工具允许我们开发类库,对吧?具有此功能的工具通常存在的主要问题如下:1.输出到最终产品(.app/.ipa或.apk)而不是类库2.嵌入运行时环境运行跨平台代码,这些代码在兼容环境中与本机代码交互是非常困难的。Web视图工具-这些工具使用视图作为运行时环境,并使用JavaScript/HTML5编写代码。如果他们难以与在Web视图中运行的代码进行交互,他们会立即(但是,它总是尝试这样做,稍后您会发现)。此类工具包括PhoneGap、RhoMobile、SechaTouch、appMobi、Telerik。AdobeAIR-它的运行时环境是Adob??eIntegratedRuntimeEnvironment。它的失败取决于与AIR中的代码进行交互的难度。Xamarin--只能输出到Xamarin中间库,不能输出到原生库。AppceleratorTitanium——创建一个不受官方支持的本地类库,但如果我们编写允许与本地代码交互的Titanium扩展,它可能会起作用。太麻烦了,那些导致问题,而且不确定这些功能是否会在下一个版本的Titanium升级中保留。Corona——Corona的人声称它已经支持Android,而未来将支持iOS。MoSync——类似于Kony之类的Corona——不支持它。Trigger.io-不支持OpenFL-不支持DragonRad-已经过时并且似乎不支持它因此,失败,没有什么真正有效:(但是等等,C/C++代码不能访问iOS和Android?选项2-C++使用C++开发类库是两个可行的解决方案之一。在Android平台上,原生开发工具包(NDK)和Java原生工具包框架(JNI)允许Java和C/C++代码运行和交互。NDK负责为Android的各个目标对象(armeabi、armeabi-v7a、x86和mips)编译C++代码;而JNI允许两种语言进行通信。使用JNI非常冗长;程序员必须遵守命名规则,需要使用Java和C++两层封装。一方面,通过在Java语言和方法(包括native关键字)中公开所有C++类,Java包装器为C++类库提供了Java接口。另一方面,C++包装器提供了Java包装器和C++类库之间的桥梁,两种语言的对象可以相互转化。在iOS中,事情要简单得多。在这个系统中,没有命名规则,只是用“Objective-C++”多了一层封装。“Objective-C++”是一种允许变量在单个源文件中同时使用“Objective-C”代码和“C++”代码的语言。因此,所有对象翻译都只发生在这个单一的封装层中。稍加修改的Android/iOS应用程序流程图如下:第三方库的引入也是非常规的,因为程序员无法直接访问JRE/Android和CocoaTouch框架。在这种情况下,可以通过两种方式引入第三方库,源代码或预编译的二进制文件(要么找到它们,要么编译它们)。一个特例是执行网络操作(HTTP请求),这是标准模板库(STL)不支持的,因此我们将libcurl集成到跨平台库中。libcurl不能作为源代码导入,它只能用作可执行的配置脚本。幸运的是能够找到iOS的预编译二进制文件。在Android中,我们使用NDK工具链/编译器为每个Android目标系统编译libcurl。为7种目标架构(iOS3种,Android4种)编译库非常耗时,但该过程的一部分可以使用脚本自动执行。这种方式效果很好,C++是一门流行的语言,有海量的第三方库可用,所用的工具(Android的NDK、JNI、Objective-C++)都有官方的解决方案,由Google和Apple提供支持。这种措施唯一的缺点是,在Android上,如果我们想保留Java包装器对象对C++对象的引用,必须在Java对象释放前手动回收C++对象(通常称为删除C++对象)。但是,如果没有理由保留C++对象,它们可以在复制到Java对象后立即销毁。选项3-代码移植另一个考虑过的选项是只维护一个代码库,然后使用适当的工具将代码翻译成特定于平台的语言。这种选择也有其缺点:生成的代码不会像本地开发人员编写的代码那样高效。翻译过程很容易引入错误,必须手动修复。导入的二进制文件很难翻译,因为大多数工具只能翻译源代码。下面介绍几款移动平台代码移植工具。不幸的是,它们都没有满足需求:J2ObjC-由谷歌开发的用于将Java代码转换为Objective-C的工具。看起来质量相对较高(与下面的其他人相比)。到目前为止,它可以将一些Java类翻译成Objective-C,但开发还没有完成。不幸的是,它目前不翻译JavaHTTP请求,但如果我们为每个平台单独实现这一部分,它可能是可能的。该项目自2012年9月成立。Hyperloop-将JavaScript转换为平台原生代码的工具。截至目前,它仅支持iOS且不稳定,但他们的计划是扩展到所有流行的平台。该项目自2013年8月成立。ObjC2J-将Objective-C翻译成Java的工具。这是一个好主意,但不幸的是,它不成熟,包含许多错误,并且经常输出无法编译的代码。XMLVM-将JVM字节码交叉编译为Objective-C的工具。这个工具不仅不完美,而且使用起来很复杂,需要下载/导入很多遗留的jar。Apportable-将iOS应用程序转换为Android应用程序的工具。不幸的是,它不符合我们的要求,因为它只能翻译整个应用程序,不能翻译库,而且它直接输出.apk(Android应用程序安装包)文件。Avian-一个轻量级的Java虚拟机,可以嵌入iOS应用程序包并运行Java代码。这种方案不能满足需求,因为要让运行在iOS上的UI代码与运行在虚拟机上的Java库代码进行交互是非常困难的。inthebox-在iOS上运行的移植Dalvik虚拟机和AndroidGingerbread(2.3)API。此选项已弃用,因为该项目不再可用。选项4-WebView中的JavaScriptJavaScript是近年来非常流行的一种语言。它的初衷是作为一种客户端脚本语言,但现在也被用于服务端应用程序(node.js),并成为了上面提到的移动跨平台工具的一部分。它能成为一种跨平台的语言来解决我们的问题吗?所有的移动端跨平台都可以在浏览器视图中执行JavaScript脚本(WebViews),WebView的API通常呈现在开发者面前。我们在JavaScript中需要的最低功能如下:执行函数调用脚本评估全局变量并返回结果执行回调(到本机代码)让我们分别探索每个平台。在Android中,WebView可以执行脚本字符串。Java代码的回调在JavaScript中实现,它注释(使用@JavascriptInterface)Java类中可以调用的某些方法,并将这些类的实例添加到WebView的JavaScript全局范围引用(使用addJavascriptInterface()方法)。然而,计算变量或函数调用并不是那么简单,因为没有办法像脚本那样直接计算。处理这个问题的唯一办法就是给JavaScript传递一个回调函数,这样在计算出结果的时候调用回调函数,把结果作为参数传递给Java方法。有关详细信息,请参见此处。在iOS中,UIWebView可以执行脚本字符串。与Android不同,IOS中可以对全局变量和函数调用进行求值(使用stringByEvaluatingJavaScriptFromString:),但是必须以字符串形式返回,所以当结果不是字符串时应该做一些适当的转换。但是回调函数没有Android那么简单,因为UIWebView中没有这种机制。JavaScript调用Objective-C的唯一解决办法是尝试在JavaScript中打开一个带有自定义协议(如skycallback://)的URL,并在Objective-C中捕获这个事件,然后解析该URL,看protocol是否它包含回调协议的名称,或解析后的URL资源路径的字符串值,或用于计算和存储结果的全局变量。有关详细信息,请参阅此处的答案。可以看到,JavaScript和原生代码的交互是非常困难的,依赖于平台,当代码量增长时,这种交互很容易出现bug,不可避免地变得困难。维持。因此,这个选项被放弃了。选项5-JS引擎中的Javascript让JavaScript在单独的JavaScript引擎中运行也有效。与Webview的方式相比,Javascript直接与Js引擎交互更直接。但不幸的是,纯JS引擎缺乏Web功能。在JS中处理Http请求的XMLHttpRequest对象是无效的,因为它是Web浏览器的一部分,而不是严格的JavaScript规范。因此,通过将网络功能代理到特定于平台的(胶水)代码,一个独特的架构出现了。虽然这使事情变得有点复杂,但我们特别感兴趣的是使开发跨平台的JavaScript库成为可能。它是这样工作的:在iOS中,JavaScriptCore引擎是通过令人敬畏的JavaScriptCore框架使用的。这个框架是在iOS7中引入的,它可以在几秒钟内轻松集成到应用程序中,就像您使用任何CocoaTouch框架一样。它的API非常简单,所需的绑定代码也非常简洁。在Android中,事情还是有点复杂,因为没有JavaScript引擎,所以我们必须手工嵌入一个。可以成功嵌入两个JavaScript引擎,Rhino和V8。Rhino是用Java编写的,因此很容易嵌入,并且只增加了2.6MB的应用程序大小。它由Mozilla基金会开发,但它的开发已经有一段时间不活跃了。V8更难嵌入并且是用C++编写的。因此,Java必须使用AndroidNDK和JNI与之交互,增加另一个翻译层(Java<->C++<->JavaScript而不是Java<->JavaScript)。此外,应用程序大小增加了7.1MB,这对于某些应用程序来说是不可忽略的。不管怎样,它的发展非常活跃。跨平台库是用JavaScript开发的,将Http请求处理作为存根。这个存根最初用作占位符,并在库加载到JavaScript引擎后被重写。它被调用实现请求的本机(特定于平台)方法的方法所取代。“JavaScriptinaJavaScriptEngine”解决方案的完整描述将出现在本系列文章的第三部分,该文章将在接下来的几周内发布。结论在研究许多工具和技术时,只有其中两种有效。一方面,C++解决方案是一种广泛使用、可靠且灵活的解决方案,但Java中的手动垃圾收集(建议作为一种变通方法)有一个明显的缺点。另一方面,JavaScript解决方案更容易实现,但缺乏复杂架构的特性,并且依赖于开发不活跃的Rhino或对应用程序大小有重大影响的V8。如果您使用其中一种方法,请考虑这些缺点并谨慎行事。一些项目看起来很有前途,值得在未来重新审视:CoronaMoSyncJ2ObjCAppceleratorHyperloopNashorn(Oracle的Javascript引擎用Java重写)