细聊移动端混合开发框架(下)

2018-06-22 08:06:04 织梦安装使用
  • 文章介绍
张建伟 网易杭州前端技术部

5+Runtime & Native.js

  • 5+Runtime是对HTML5+规范的实现,除了支持标准HTML5外,还扩展了JavaScript对象plus,使得js可以调用各种浏览器无法实现或实现不佳的系统能力,设备能力如摄像头、陀螺仪、文件系统等,业务能力如上传下载、二维码、地图、支付、语音输入、消息推送等。编写一次,可跨平台运行。

  • 大量的手机OS的原生API无法被HTML5使用,Native.js把原生API封装成了js对象,通过js可以直接调ios和android的原生API。这部分就不再跨平台,写法分别是plus.ios和plus.android。

  • Native.js不是一个js库,不需要下载引入到页面的script中,也不像nodejs那样有单独的运行环境,Native.js的运行环境是集成在5+runtime里的。

使用方式:

  • 对于web端开发者,使用HBuilder IDE,它集成了5+Runtime和Native.js,可以创建移动项目来开发App

  • 对于Native端开发者,可以从平台下载SDK集成到项目中使用

看到5+runtime说是开源(http://weibo.com/p/1001603806548597059383)了,不过在开源项目(https://github.com/dcloudio/H5P.Core)中并未找到iOS native的代码实现,其中还是以静态库的形式提供,不过在pdr.js文件中,看到了plus.tools和plus.bridge的实现,这两个实现在后文中会使用到。

Native.js通信方式实现分析

下面以使用iOS中的UIAlertView为示例。

iOS端使用UIAlertView的代码如下:

#import <UIKit/UIKit.h>
//...
// 创建UIAlertView类的实例对象
UIAlertView *view = [UIAlertView alloc];
// 设置提示对话上的内容
[view initWithTitle:@"自定义标题" // 提示框标题
    message:@"使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容
    delegate:nil // 点击提示框后的通知代理对象,nil类似js的null,意为不设置
    cancelButtonTitle:@"确定(或者其他字符)" // 提示框上取消按钮的文字
    otherButtonTitles:nil]; // 提示框上其它按钮的文字,设置为nil表示不显示
// 调用show方法显示提示对话框,在OC中使用[]语法调用对象的方法
[view show];

JS端使用UIAlertView的方式如下:

    // 创建UIAlertView类的实例对象
    var view = new UIAlertView();
    // 设置提示对话上的内容
    view.initWithTitlemessagedelegatecancelButtonTitleotherButtonTitles("自定义标题" // 提示框标题
        , "使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容
        , null // 操作提示框后的通知代理对象,暂不设置
        , "确定(或者其他字符)" // 提示框上取消按钮的文字
        , null ); // 提示框上其它按钮的文字,设置为null表示不显示
    // 调用show方法显示提示对话框
    view.show();

其中UIAlertView、initWithTitlemessagedelegatecancelButtonTitleotherButtonTitles和show的实现如下:

window.plus.ios.UIAlertView = function(create) {
        this.__UUID__ = window.plus.tools.UUID(JSB);
        this.__TYPE__ = JSBObject;
        var args = window.plus.ios.__Tool.process(arguments);
        if ( create && plus.tools.IOS == plus.tools.platform ) {
        
        } else {
            window.plus.bridge.execSync(Invocation, __Instance, [this.__UUID__, UIAlertView, args]);
        }
    };    
plus.ios.UIAlertView.prototype.initWithTitlemessagedelegatedefaultButtoncancelButtonotherButtons = function () {
        var ret = null;
        try {
            var args = window.plus.ios.__Tool.process(arguments);
            ret = window.plus.bridge.execSync(Invocation, __exec, [this.__UUID__, initWithTitle:message:delegate:defaultButton:cancelButton:otherButtons:, args]);
            ret = plus.ios.__Tool.New(ret, false);
        } catch (e) {
            throw e;
        }
        return ret;
    };   
plus.ios.UIAlertView.prototype.show = function () {
        var ret = null;
        try {
            var args = window.plus.ios.__Tool.process(arguments);
            ret = window.plus.bridge.execSync(Invocation, __exec, [this.__UUID__, show, args]);
            ret = plus.ios.__Tool.New(ret, false);
        } catch (e) {
            throw e;
        }
        return ret;
    };

可以看到,我们创建的UIAlertView是一个JS对象,这个对象是当我们使用UIAlertView = plus.ios.importClass("UIAlertView");时动态创建的JS对象,与Native的UIAlertView相对应,我们称该JS对象为NJS对象。我们对NJS对象UIAlertView进行的方法调用,最终会执行window.plus.bridge.execSync,我们需要看下它的实现,在此之前,关于通过NJS对象访问Native对象,先做一些说明。

  • 首次导入Native类对象时,Native.js会动态创建一个JS对象与之相对应,JS对象包括相应的构造函数、方法、父类(prototype)等信息。

  • 由于是动态创建对应的JS对象,这里有一定的性能损耗,官方文档(http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/88)中性能优化一节建议页面打开后触发的“plusready”事件中进行类对象的导入操作,这样提前导入了我们需要导入的类对象,是我们在后面逻辑中使用时保证其已经导入,这种方式只是将导入时机提前,并不是消除了导入带来的损耗。所以官方也不建议我们在一个页面中导入过多的类对象,这样会影响性能。

  • 数据类型转换:在NJS中调用Native API或从Native API返回数据到NJS时会自动转换数据类型。

  • Native类对象的方法会在JS对象中有份映射,方法名是native方法名去掉‘冒号’之后的名称(字母大小写不变)。

  • 对于映射的JS对象,可以通过“.”调用方式来访问native对象的属性,但这种方式获得的值是Native层对象被映射为NJS对象那一刻的属性值,如果需要实时获取和设置native对象属性值,需要使用plusGetAttribute和plusSetAttribute方法,但这种方式效率比较低。

  • Objective-C和Java中类如果存在继承自基类,在NJS中对应的对象会根据继承关系递归将所有基类的公有方法一一换成NJS对象的方法,所有基类的公有属性也可以通过其plusGetAttribute、plusSetAttribute方法访问。

  • 由于Objective-C中类没有静态变量,而是通过定义全局变量来实现,目前NJS中无法访问全局变量的值。对于全局常量,在NJS中也无法访问。

继续之前的window.plus.bridge.execSync方法调用,其方法实现如下:


其中:

T=plus.tools,
B=plus.bridge

synExecXhr的全称是“同步调用XML HTTP Request”,前面我们提到了plus.bridge的实现中我们可以看到:

synExecXhr: new XMLHttpRequest()

可以看到,synExecXh实际是一个XMLHttpRequest对象,通过它最后将调用信息以http请求的方式发出去。我们在Native端利用oc-runtime hook住UIAlertView的构造函数,添加断点,可以看到调用栈如下图所示:


可以看到,JS调用到Native端通过DCAsycSocket以这种进程间通信的方式来实现,并且在非主线程完成。至此,我们可以得知,native.js中js与native端的通信是通过本地socket同步通信的方式完成的,完成调用之后,调用结果会以字符串的形式保存在sync.responseText中,js端再通过evaluate其中的字符串来得到返回结果。

小结:5+runtime还是基于WebView来做事,属于hybrid流派,通过本地socket通信方式来实现JS与Native的混合调用,支持动态导入native类对象,进行实例化、方法调用、属性访问等操作,与一般的hybrid技术不同的是,不需要native工程师来提供模块或者插件来支持扩展js的能力,web工程是可以参考native的方法调用类似的方式(只需要简单的修改),实现对native对象的访问。同时5+runtime还提供了一些跨平台的通用组件,如摄像头、陀螺仪、文件系统等。使用native.js技术所需要注意的问题就是性能问题,动态导入和访问native对象以及数据类型转化需要付出一定的性能损耗代价,官方给出了一些建议来进行性能优化。另外,值得一提的是DCloud公司还用HTML5做了一套模拟Native UI的开源项目MUI,有兴趣可以参考这里(https://github.com/dcloudio/mui)。

优点:

  • web端可以直接访问native的api,调用接口参考

  • 提供一套native样式库:MUI

缺点:

  • 与native交互性能有点弱

前面篇幅主要介绍WebViewBridge流派,WebViewBridge流派主要还是基于WebView做事情,下面将对JavaSciptBridge流派进行介绍。JavaSciptBridge流派抛弃了WebView,采用脚本语言与native语言之间进行跨语言操作,实现混合开发。

Titanium

Titanium:"Write in JavaScript, run native everywhere".

Titanium与PhoneGap不同,并不是基于WebView来做跨平台开发,属于JavaSciptBridge流派,关于它与PhoneGap的对比可以参考这篇文章(http://www.appcelerator.com/blog/2012/05/comparing-titanium-and-phonegap/)。值得一提的是,Titanium的上层语言并没有采用HTML+CSS+ JavaScript,而是XML+JSON+JavaScript,这增加了一定的学习成本。

React Native

ReactNative:"learn once,write anywhere".

ReactNative和Titanium的思路很像,也抛弃了WebView,属于JavaSciptBridge流派。ReactNative用JavaScript编写程序,渲染的界面全部都是Natvie的。React(https://facebook.github.io/react/)是前端的知名开发库,程序代码通过操作Virturl DOM来编写程序,React runtime负责操作和更新真正的DOM节点,而这个更新是通过diff做增量更新,这提高性能,ReactNative沿用了React的编程模型和更新模型。

ReactNative的JS运行在与应用主线程独立的线程,通过异步操作与Natvie接口通信,线程模型可以参考下图:


JS解释器可以运行于手机中的独立线程,也可以远程调试时运行在浏览器中,另外,I/O操作、图片解码、布局信息计算等其他一些消耗CPU的操作也可以放到独立线程中,iOS应用主线程用来操作UI控件和Native API访问,JS线程与UI主线程之间通过ReactNative桥接进行异步通信,实现JS与Objective-C之间的相互调用。


React Native通信机制源码分析

源码分析基于React Native v0.23.1,不过下载了目前的最新版0.38.0调试了,实现方式大同小异。

React Native 的思路就是将Native方法导出给JS用,使得用户可以用JS编写程序,而采用原生控件构建构建应用。 React Native 导出以模块(Module)为单位,在程序启动时,加载需要注册到js中的module,挂到js解释器的__fbBatchedBridgeConfig变量上,格式如下:

{"remoteModuleConfig":[

["RCTStatusBarManager",
    ["getHeight","setStyle","setHidden","setNetworkActivityIndicatorVisible"]],
["RCTSourceCode",
    {"scriptURL":"http://localhost:8081/index.ios.bundle?platform=ios&dev=true&hot=true"},
    ["getScriptText"],[0]]
]

React Native还在不断的迭代开发中,不同版本的实现方式可能不同,例如,在React Native通信机制详解(http://blog.cnbang.net/tech/2698/)一文中介绍,Natvie模块的注册方式是通过在利用编译指令将需要导出的模块存储到执行文件的二进制DATA端,程序启动时再从中读取导出的模块信息,我使用的源码是v0.23.1版本,可以看到,需要bridge的模块需要使用RCTRegisterModule,其展开如下:

#define RCT_EXPORT_MODULE(js_name) 
RCT_EXTERN void RCTRegisterModule(Class); 
+ (NSString *)moduleName { return @#js_name; } 
+ (void)load { RCTRegisterModule(self); }

可以看到,bridge的模块在load方法中进行注册,注册的模块保存在RCTBridge.m的static NSMutableArray<Class> *RCTModuleClasses;全局静态变量中。模块中需要bridge的方法使用RCT_EXPORT_METHOD宏,默认情况下,使用OC方法的第一个分号之前的部分作为JS中的调用名称,例如模块ModuleName中,需要导出的方法- (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b,需要写成

RCT_EXPORT_METHOD(doSomething:(NSString *)aString
                   withA:(NSInteger)a
                   andB:(NSInteger)b)
 { ... }

最终JS的调用形式是NativeModules.ModuleName.doSomething

应用启动时,会创建一个CADisplayLink添加到线程(真机上是JS线程,模拟器上是主线程)的runloop中,周期性的调用JS的callFunctionReturnFlushedQueue方法,这个方法的作用就是从一个MessageQueue中取出消息内容。JS调用OC方法,会将调用的信息(moduleID、methodID、params)保存在这个MessageQueue中。

这里需要说明一下:测试发现,在模拟器中,JS线程与Native端使用RCTSRWebSocketExecutor来进行通信,在真机上,使用RCTJSCExecutor来执行js脚本,在RCTJSCExecutor中可以看到对它的说明Uses a JavaScriptCore context as the execution engine.

回到刚才的话题,调用callFunctionReturnFlushedQueue之后,会从MessageQueue取出调用信息,已json字符串的形式返回给native端,native端通过RCTJSONParse接口parse出调用信息,信息包括模块id,方法id,参数,以及可能的回调id,格式如下:

<__NSCFArray 0x7f8bd1610e60>(

//moduleIDs
<__NSCFArray 0x7f8bd16c4ae0>(
56,
33,
33,
34
)
,

//methodIDs
<__NSCFArray 0x7f8bd16f0400>(
1,
5,
4,
0
)
,

//params
<__NSCFArray 0x7f8bd16396c0>(
<__NSCFArray 0x7f8bd16f7d60>(
ws://localhost:8097/devtools,
<null>,
<null>,
280
)
,
<__NSCFArray 0x7f8bd163d350>(
29,
RCTView,
1,
<null>
)
,
<__NSCFArray 0x7f8bd162e670>(
7,
<null>,
<null>,
<__NSCFArray 0x7f8bd16e4c90>(
29
)
,
<__NSCFArray 0x7f8bd16f0d90>(
5
)
,
<__NSCFArray 0x7f8bd1621a10>(
5
)
)
,
<__NSCFArray 0x7f8bd16c7670>(
5,
2,
3
)

)
,
//callID
1171
)

我们先来看下JS部分是如何创建这个队列以及将这些消息调用信息保存在队列中的。

//BatchedBridge.js
const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

前面提到过fbBatchedBridgeConfig.remoteModuleConfig是Native端注册的模块,fbBatchedBridgeConfig.localModulesConfig是JS本地模块。


里面调用this.genModules(modulesConfig);,最后会调到genModule。

//config是module信息,包括模块名,导出方法名列表等,moduleID模块对应的id(这个ID就是模块在remoteModules数组中的索引)
_genModule(config, moduleID) {
    if (!config) {
      return;
    }

    let moduleName, constants, methods, asyncMethods;
    if (moduleHasConstants(config)) {
      [moduleName, constants, methods, asyncMethods] = config;
    } else {
      [moduleName, methods, asyncMethods] = config;
    }

    let module = {};
    methods && methods.forEach((methodName, methodID) => {
      const methodType =
        asyncMethods && arrayContains(asyncMethods, methodID) ?
          MethodTypes.remoteAsync : MethodTypes.remote;
      //构建方法信息
      module[methodName] = this._genMethod(moduleID, methodID, methodType);
    });
    Object.assign(module, constants);

    if (!constants && !methods && !asyncMethods) {
      module.moduleID = moduleID;
    }

    //构建的module信息保存在RemoteModules中
    this.RemoteModules[moduleName] = module;
    return module;
  }

调用this._genMethod构建方法信息。

//module:模块ID,method:方法ID(是方法在方法名列表中的索引),type:"remote,remoteAsync",区分是同步调用还是异步调用,异步调用用Promise实现
  _genMethod(module, method, type) {
    let fn = null;
    let self = this;
    if (type === MethodTypes.remoteAsync) {
      fn = function(...args) {
        return new Promise((resolve, reject) => {
          self.__nativeCall(
            module,
            method,
            args,
            (data) => {
              resolve(data);
            },
            (errorData) => {
              var error = createErrorFromErrorData(errorData);
              reject(error);
            });
        });
      };
    } else {
      fn = function(...args) {
        let lastArg = args.length > 0 ? args[args.length - 1] : null;
        let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
        let hasSuccCB = typeof lastArg === function;
        let hasErrorCB = typeof secondLastArg === function;
        hasErrorCB && invariant(
          hasSuccCB,
          Cannot have a non-function arg after a function arg.
        );
        let numCBs = hasSuccCB + hasErrorCB;
        let onSucc = hasSuccCB ? lastArg : null;
        let onFail = hasErrorCB ? secondLastArg : null;
        args = args.slice(0, args.length - numCBs);
        return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }

这里我们可以看到,__nativeCall的调用被包装在一个function中,这个function作为first-classed Value被返回,已的形式保存在module信息中。我们开看到,native调用的成功和失败回调函数也是在这里传入。

至此,Native方法的调用映射表构建完成。下面是JS端网络接口的例子。

var RCTNetworkingNative = require(NativeModules).Networking;

/**
 * This class is a wrapper around the native RCTNetworking module.
 */
class RCTNetworking {

  static sendRequest(query, callback) {
    RCTNetworkingNative.sendRequest(query, callback);
  }

  static abortRequest(requestId) {
    RCTNetworkingNative.cancelRequest(requestId);
  }

}

module.exports = RCTNetworking;

可以看到,RCTNetworking最终调用的是Native映射表中的本地方法。

我们继续来看__nativeCall的实现,这里很重要。


代码说明: (1)往params中添加回调回调方法对应的id,可以看到,回调id保存在params数组最后,还会记录debug信息。 (2)将native调用信息添加到MessageQueue,可以看到,MessageQueue的格式是this._queue = [[], [], [], this._callID];,moduleID、methodID和params分别被加入到MessageQueue中不同数组中,所以就看到了前面我们打印出来的Native端收到的数据格式。 (3)如果是要求立即调用并且超时,则会调用global.nativeFlushQueueImmediate接口。这里我查找了下代码,js端和native端并没有为global.nativeFlushQueueImmediate,可见一般不会进入这个流程,一般还是使用前面介绍的Native定时驱动的方式来获取MessageQueue中的调用信息。但是,既然有这个值,说明是支持js主动调用到native端的,那又是如何实现的呢?

在RCTJSCExecutor.m文件中的setUp方法,我们看到这样一段代码:

[self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
    RCTJSCExecutor *strongSelf = weakSelf;
    if (!strongSelf.valid || !calls) {
      return;
    }

    RCT_PROFILE_BEGIN_EVENT(0, @"nativeFlushQueueImmediate", nil);
    [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
    RCT_PROFILE_END_EVENT(0, @"js_call", nil);
  }];

再看addSynchronousHookWithName的实现:

- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block
{
  __weak RCTJSCExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:^{
    weakSelf.context.context[name] = block;
  }];
}

我们发现,这里是将native的方法挂到js解释器的全局上下文上,这样js端就可以直接调用这些native方法。除了nativeFlushQueueImmediate以外,还有其他一些全局方法。

至此,我们看到了JS端如何调用组织数据发送给Native端,那Native端拿到数据之后是如何处理的呢?

我跟踪了JS调用创建UIView的过程(通过hook UIView的initWithFrame方法,加断点进行测试),其导出的Native方法在RCTUIManager.m中,方法签名如下:

createView:(nonnull NSNumber *)reactTag
                  viewName:(NSString *)viewName
                  rootTag:(__unused NSNumber *)rootTag
                  props:(NSDictionary *)props

创建UIView的流程如下: Native端接收到数据,传给RCTBatchedBridge.m中的handleBuffer函数处理,从中解析出多组信息,每组信息包括模块id,方法id,参数,以及可能的回调id,用匿名block将每组信息包起来(这里我们取个名叫block1),放到moduleID所对应的RCTModuleData的methodQueue中异步执行,RCTModuleData是对module示例的包装,methodQueue是由module实例返回,由于iOS对UI的操作一般需要放在这线程,所以可以看到,UI相关的module的method的queue的实现返回的都是主线程:

//RCTActionSheet.m
- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

同时,还会往methodQueue中添加匿名block(这里我们取个名叫block2)的异步操作。block1执行时,首先根据moduleID在全局表中查找到对应的Module(RCTModuleData,ModuleData的创建会根据前面提到的RCTModuleClasses中的Class一一对应创建,保存在RCTBatchedBridge.m中的 NSArray<RCTModuleData *> *_moduleDataByID; 中),然后再根据methodID在Module中找到相应的方法RCTBridgeMethod(RCTBridgeMethod是对native方法的封装,内部会持有一个NSInvocation),为内部的invocation设置参数并调用。这时实际调用的就是我们导出的Natvie方法了(也就是前面的createView: viewName:rootTag:props:),他会将创建UI的操作再次封装成一个个block(这里我们取个名叫block3),放到一个集合 NSMutableArray<dispatch_block_t> *_pendingUIBlocks; 中,当block2执行时,会从pendingUIBlocks中逐个取出block3并执行,从而创建出相应的Natvie UI。

如果调用Natvie之后,JS端需要CallBack被调用,会将CallBackID通过参数传给Native,Native函数执行完成之后,会通过发送json字符串的形式发送给JS调用结果,其中会带上CallBackID以及此次发送消息的ID。

在查看ReactNative源码之前,本以为JS与OC之前的通信是通过利用JavaScriptCore进行JSBinding这种静态绑定的方式,查看源码之后才发现不是这样。通过上面对源码的分析,可以看到,RN自己实现以一套通信模式,JS与OC之间的调用采用动态查找的方式来实现,JS和Native工作于独立的线程,线程间根据一套基于ID映射协议的方式来进行通信,这样的动态查找的方式通信成本会高一些,为什么没有用JSBinding的方式,个人理解主要有一下两点原因。

  1. ReactNative 的目标是跨平台,并不是所有平台都采用JavaScriptCore引擎,另外,iOS7之前JavaScriptCore也没有开发出来。

  2. ReactNative 的 JS 代码工作在独立线程(非主线程),如果采用静态绑定的方式,无法或者很难保证对UI的操作工作在主线程。

性能测试


(1)Native


(2)ReactNative

性能测试采用 RN v0.38.0,测试环境:iphone6,iOS9.3.2,测试用例,点击‘GO’按钮,push(带动画)一个新页面,新页面包含一个列表,首屏展示6项,向上滑动,总共展示20项,列表使用的数据是本地数据。测试数据如下,其中

  • 响应时间:从点击button开始构建页面,到页面完全构建完成(viewDidAppear)的时间

  • 页面构建内存开销:从点击button开始构建页面,到页面构建完成内存的增量

  • 页面滚动之后内存开销:向下滑动,加载所有的cell之后的内存增量

测试数据响应时间(ms)页面构建内存开销(MB)页面滚动之后内存开销(MB)
React Native8312.560.50
Native5551.291.36

从体验上点击响应的顺滑程度明显native要顺滑很多,从上面的gif图中也可以看出。另外,内存开销上RN首屏列表开销明显高于Native。这里需要补充说明:React Native的ListView并不是使用UITableView实现,而是自己采用UIView实现的,这一点可以从视图层级中看出,如下图:


ListView的缓存策略也是自己做的,从这里(https://github.com/facebook/react-native/issues/499)得知,React Native的ListView的"Cell"每次都是重新创建,之所以这么做原因是RN认为UITableView 的复用存在“脏数据”的问题,而且,在现在的手机设备上,创建新的cell已经足够快。当"Cell"划出屏幕,相应的“Cell”会被从view tree上取下来,但并不会销毁,只有当内存警告或者列表项太多时,会有Cell的销毁工作,下次使用,再重新构建出来。

其实,ListView在RN中无法用UITableView实现,原因是,如前面所介绍,JS对Native的调用是异步操作,而且消息派发的驱动是Native端做的,试想一下,如果采用UITableView来做,在列表页快速滚动的时候,JS端是不能立刻同步获取下一个Cell来展示的。RN现在的实现方式是用ScrollView来实现,并且会预先创建若干个Cell来用户快速滑动时,下一个Cell的展示。测试发现,对于DataSource有20个数据,首屏只能显示6个cell的情况下,Native端首屏会创建6个cell,总共会创建7个(缓速的滑动);React Native首屏会创建17个cell(用于快速滑动展示而预创建),总共会创建20个。

优点:

  • JS与native的异步通信,脚本不会卡住主线程

  • 原生的控件+原生的体验

  • 热调试能力

缺点:

  • JS与native的异步通信

  • 门槛较高

  • 仅面向React前端开发

samurai-native

samurai-native的思路跟ReactNatvie的思路是一样的,也是将native的接口导给web端使用,而界面的渲染采用natvie控件,它与RN区别主要有两点:

(1)表达语法是HTML+CSS;

(2)标签名称与Native的View的名称对应,RN中由于对标签进行了抽象,有些标签与Native类名称上并不能对应,React Native书写一个tableview cell如下,可以看出,和原生的写法的命名有差异。

<TouchableHighlight onPress={() => this.rowPressed(rowData.guid)}
                underlayColor=#dddddd>
              <View>
                <View style={styles.rowContainer}>
                  <Image style={styles.thumb} source={{ uri: rowData.img_url }} />
                  <View  style={styles.textContainer}>
                    <Text style={styles.price}>£{price}</Text>
                    <Text style={styles.title}
                          numberOfLines={1}>{rowData.title}</Text>
                  </View>
                </View>
                <View style={styles.separator}/>
              </View>
            </TouchableHighlight>

关于samurai-native的介绍可以参考他在infoq上的演讲(http://www.infoq.com/cn/presentations/semi-hybrid-app-framework)



    上一篇:html5点击按钮酷炫云雾动画弹出文字..

    下一篇:没有了

    相关文档推荐

    精品模板推荐

    专业的织梦模板定制下载站,在线购买后即可下载!

    商业源码

    跟版网模板,累计帮助5000+客户企业成功建站,为草根创业提供助力!

    立刻开启你的建站之旅
    
    QQ在线客服

    服务热线

    织梦建站咨询