深入浅出 Fuse

jopen 8年前

在上次Fuse 15 分钟入门教程 之后,用 Fuse 开发的第一个 AppProducter 也提交到了 App Store

代码已经开源在了 Github 这篇文章将用来总结一些 Fuse 开发中的 “深坑” 和经验。

引入 Javascript 库

Fuse 的逻辑层都是由 Javascript 来处理,那么利用一些好用的库会使得我们的开发更为有效,以 Moment.js 为例,这是前端常用的一个时间解析库。

第一步就是到 http://momentjs.com 下载最新版本的库,把 moment with locales 这库存储到项目目录里,例如我存储在了 assets/js 目录下,那么在 MainView.ux 里,既可以这样载入这个模块

<JavaScript File="assets/js/moment.min.js" ux:Global="Moment"/>  

如果要使用这库,就可以在 Javascript 文件里这样 require 它,require 里的字符串要和我们 ux:Global 里写的一样

var Moment = require('Moment');  

接下来你就可以直接用 Moment 来解析时间了。

读取本地文件

在 Producter App 里我用到了 Webview 来结合 JSON 和本地的 HTML 模版显示文章,Fuse 的 Javascript 并没有接口可以读取本地文件,所以需要利用 Uno 来实现这个功能。

首先确保你的 .unoproj 里的 Includes 里有打包 *.html

  "Includes": [      "*",      "*.html"    ]

随后将下面这段代码保存为 TextFile.uno

using Uno;    using Uno.Collections;    using Fuse;    using Uno.UX;    using Fuse.Scripting;    using Fuse.Reactive;    public class TextFile: NativeModule    {      readonly FileSource _file;        [UXConstructor]      public TextFile([UXParameter("File")] FileSource file)      {           AddMember(new NativeFunction("readSync", (NativeCallback)readSync));          _file = file;      }        object readSync(Context c, object[] args)      {          return _file.ReadAllText();      }  }

在开始我们用 using 引用了一些我们会用到的库,这些被引用的库要确保存在于 .unoproj 里,你可以参考 Producter 的配置文件

  "Packages": [          "Fuse.PushNotifications",          "Fuse.Animations",          "Fuse.BasicTheme",          "Fuse.Themes",          "Fuse.Controls",          "Fuse.Designer",          "Fuse.Drawing",          "Fuse.Drawing.Primitives",          "Fuse.Effects",          "Fuse.Elements",          "Fuse.Entities",          "Fuse.Gestures",          "Fuse.Navigation",          "Fuse.Shapes",          "Fuse.Triggers",          "Fuse.Reactive",          "Fuse.Android",          "Fuse.Desktop",          "Fuse.iOS",          "FuseCore",          "Fuse.Launcher",          "Uno.Collections",          "Uno.Geometry",          "Fuse.Scripting",          "Experimental.iOS",          "ObjC"    ]

TextFile 这个类后面还有一个 NativeModule 继承,通过这个继承就使得我们这个类可以通过 <TextFile /> 的形式放在 Mainview.ux 里,所有自定义的类都需要用这样的方式加载到我们的 Fuse App 里。

更详细的 NativeModule 使用细节可以参考官方的 Guide Working with Uno Code

UXConstructor 字段的修饰让 <TextFile /> 在创建这个类的时候知道 <TextFile File="xxxxx"/> 对应 [UXParameter("File")] FileSource file 传递构造参数给这个类的构造方法。

在 readSync 方法里,通过 ReadAllText() 读取了路径文本,那么接下来还有一件事情就是要把这个方法暴露给 Javascript。

通过 AddMember(new NativeFunction("readSync", (NativeCallback)readSync)); 我们可以让 Uno 完成方法到 Javascript 的桥接。

完成这两步之后,在 Mainview.ux 加入文件的引用

<TextFile File="videoHTMLTemplate.html" ux:Global="videoHTMLTemplate" />  

接下来就可以在 Javascript 里使用这个文件了

var videoHTMLTemplate = require("videoHTMLTemplate");    // Read Templates  var videoHTMLTemplateString = videoHTMLTemplate.readSync();  

最后 Webview 通过 Source 属性将我们自己生成好的 HTML 填充进去, presentedArticleHTML 绑定到了我们自己生成的 HTML 数据上。

<WebView Source="{presentedArticleHTML}" >    </WebView>  

调用 iOS API

在后续的版本会舍弃现在的调用方法,引入 Foreigon Code 的模式,不过官方说还有几周的时间,所以这个即将过时的方法就先帖出来吧。

这种模式依赖于 Uno,Uno 一面把接口暴露给 Javascript,一面去调用 iOS 的接口。

具体的例子可以参考 Producter 的 FuseStoreKit.uno 这个文件,里面的用法已经相当深入,触及了 API 调用的瓶颈,这也正是为什么 Fuse 接下来要弃用 Uno 桥接的原因。

如果需要引用某些 iOS 的库,确保 .unoproj 已经引入了 Objc 和 Experimental.iOS 这两个库,不确定话可以直接照搬 Producter 的配置文件。

例如我们需要引用 iOS 的 StoreKit 库,那么可以用下面的语句

using global::iOS.StoreKit;  

因为我们的 NativeModule 需要同时存在于 iOS 和 Android,所以 FuseStoreKit 其实是我们实际接口操作类的上一层代理,避免直接调用发生悲剧。

public class FuseStoreKit : NativeModule    {      extern(iOS)    StoreKit storeKit = new StoreKit();      extern(iOS)    Storage storage = new Storage();      extern(iOS)    CloudKit cloudKit = new CloudKit();  }

extern 表示仅当是 iOS 平台的时候才会创建下面的属性。

而具体的方法在执行的时候,也用了 defined 来确保只有在正确的平台上才会执行,如果不是这个平台,Javascript 在调用的时候,就会 null。

  object MakeSubscribe(Context c, object[] args) {      if defined (iOS) {        Subscribe();      }      return null;     }

具体创建 iOS 类的时候,在 Uno 里写起来没有 iOS 那么得心应手,init 方法和类方法都有着不同的调用规则,例如创建 NSUserDefaults 的时候,要这样去写

NSUserDefaults userDefaults = new NSUserDefaults(NSUserDefaults._standardUserDefaults());  

NSUserDefaults 的 standardUserDefaults 是一个类方法,因此要加下划线,然后通过 new NSUserDefaults() 来包住这个方法去创建。

如果是 init 方法,那么使用起来就更麻烦一些,先初始化,然后再 init。

NSString begin = new NSString();    begin.initWithString("producter_month_subscribe");  

奇技淫巧参考 FuseStoreKit.uno 中我注释掉的代码。

Delegate

有些需要注意的是, .delegate = this 这种直接的属性赋值通常是不能工作的,需要使用 request.setDelegate(this); 这种方法。

其次是 Objective C 的方法如果有多个参数一般是成段隔开的,在 Uno 里要合成一段然后再在括号里分别传入参数,例如以下这段

- (void)paymentQueue:(SKPaymentQueue *)queue  restoreCompletedTransactionsFailedWithError:(NSError *)error  

那么在 Uno 里就要这样写

public void paymentQueueRestoreCompletedTransactionsFailedWithError (SKPaymentQueue queue, NSError error)    {      // Restore failed somewhere...  }

目前 Objc 的闭包回调函数在 Uno 里还没实现,有个叫 UXL 的可以做,但是…… 我们还是等 Foreigon Code 吧。

来自: http://tips.producter.io/shen-ru-qian-chu-fuse/