NativeScript跨端开发框架全面解析与优缺点探讨

Viewed 0

在跨端开发面面谈系列中,基于WebView的Hybrid开发模式一文总结了该模式的发展路线和优化方式,并介绍了Ionic框架的应用。本文继续该系列,探讨对NativeScript开发框架的理解。由于未在生产环境的复杂应用中深入使用,文中观点可能不够准确,更多是结合个人经验和文章阅读的初步理解。

从WebView到Native

基于WebView的混合开发模式广泛应用,开发者进行了各种优化以提升体验、抹平差异,但Web App运行在WebView容器中,与Native App存在天然性能差距。因此出现了新思路:保持Web技术栈的开发体验,内置运行时环境,通过映射使最终用户操作原生视图,提供接近原生体验的高性能应用。

NativeScript是首个实现这一思路的框架,开发者使用Web技术栈开发,构建出的App均为原生视图,从理论上打破了WebView性能天花板,跨端开发技术从WebView迈向了Native侧。

NativeScript Hello World

NativeScript支持多种前端技术栈,最初采用MVVM模式,以原生JavaScript业务逻辑、XML视图模板和CSS样式开发,后来引入对Angular 2的支持并持续跟进迭代,当前社区也提供了Vue开发支持。

不引入其他前端框架时,demo结构包括以下组件。XML模板示例如下:

<Page  loaded="loaded">
    <Page.actionBar>
        <ActionBar title="Sign in"></ActionBar>
    </Page.actionBar>
    <StackLayout orientation="vertical">
        <Image src="~/images/logo.png" stretch="none" horizontalAlignment="center" ></Image>
        <TextField hint="Email Address" id="email" text="{{ email }}"  keyboardType="email" autocorrect="false" autocapitalizationType="none"/>
    <TextField hint="Password" text="{{password}}" secure="true"/>
    <Button text="Sign in" tap="signIn"/>
        <Button text="Sign up for Groceries" class="link" tap="register"/>
    </StackLayout>
</Page>

JavaScript模型及视图模型为:

//login.js
var frameModule = require('ui/frame');
var UserViewModel = require('../../shared/view-models/user-view-model')
var dialogsModule = require('ui/dialogs');

var user = new UserViewModel()
var page;
var email;

exports.loaded=function (args) {
    page=args.object;
    page.bindingContext = user;
}
exports.signIn = function () {
    user.login()
        .catch(function (error) {
            dialogsModule.alert({
                message:'Unfortunately we could not find your account',
                okButtonText:'OK'
            })
        })
        .then(function () {
            frameModule.topmost().navigate("views/list/list")
        })
}
exports.register=function () {
    var topmost = frameModule.topmost();
    topmost.navigate('views/register/register');
}

//user-view-model.js
var config = require("../../shared/config");
var fetchModule = require("fetch");
var observableModule = require("data/observable");

function User(info) {
    info = info || {};

    var viewModel = new observableModule.fromObject({
        email: info.email || "",
        password: info.password || ""
    });
    viewModel.login = function() {
        return fetchModule.fetch(config.apiUrl + "user/" + config.appKey + "/login", {
            method: "POST",
            body: JSON.stringify({
                username: viewModel.get("email"),
                password: viewModel.get("password")
            }),
            headers: getCommonHeaders()
        })
            .then(handleErrors)
            .then(function(response) {
                return response.json();
            })
            .then(function(data) {
                config.token = data._kmd.authtoken;
            });
    };
    viewModel.register = function() {
        return fetchModule.fetch(config.apiUrl + "user/" + config.appKey, {
            method: "POST",
            body: JSON.stringify({
                username: viewModel.get("email"),
                email: viewModel.get("email"),
                password: viewModel.get("password")
            }),
            headers: getCommonHeaders()
        }).then(handleErrors)
    };

    return viewModel;
}

function getCommonHeaders() {
    return {
        "Content-Type": "application/json",
        "Authorization": config.appUserHeader
    }
}

function handleErrors(response) {
    if (!response.ok) {
        console.log('error')
        console.log(JSON.stringify(response));
        throw Error(response.statusText);
    }
    return response;
}

module.exports = User;

样式文件login.css为:

TextField {
    border-width: 5;
    border-style: solid;
    border-color: #034793;
}
Image {
    margin: 10;
}
Button {
    margin: 10;
    padding: 10;
}

从代码可见,视图模板使用了NativeScript封装的组件系统,包括布局组件和具体视图组件。框架提供了事件绑定和数据绑定处理,在view model与model之间也提供了工具方法,支持异步请求和数据更新。

NativeScript提供对Angular的支持,推荐阅读使用NativeScript和Angular2构建跨平台APP一文,其中使用Angular和NativeScript实现了iOS计算器示例。

NativeScript Inside

NativeScript运行机制核心包括NativeScript Runtimes、Core Modules、CLI和Plugins。深入看其内部实现原理,NativeScript的一大卖点是直接获取全部系统及第三方模块的公共API调用。以Android为例,这是通过反射机制实现。在通过CLI构建应用时,框架会反射获取所有原生公共API,将这些信息以二进制结构作为元数据打进应用包。当JavaScript端调用API时,Runtime根据元数据信息反射调用相应API。

基于NativeScript Runtime获取Native API Access,框架提供了Core Modules抽象层,包含对视图、系统API、布局的统一抽象,其“一次编写,到处运行”的核心基于这一跨平台抽象层封装实现。当已有Module不能满足需求时,NativeScript支持Plugins扩展,可以选择社区第三方plugin或实现自己的plugin。plugin可以是对已有组件的组合包装,也可以是对原生组件的自定义封装。

Bye,NativeScript

初了解NativeScript时感觉“看上去很美”。对于前端开发者,无需切换技术栈,可选用原生JavaScript、Angular或Vue,利用框架组件库和API封装快速开发高性能跨平台应用。但深入了解后,NativeScript主要存在以下三个问题。

首先,“write everything in JavaScript”是伪命题。NativeScript强调始终使用JavaScript,无需学习Java或Object-C。但在实际项目中,当复杂度超过Hello World demo时,很可能遇到个性化特性,框架未实现且社区无现有实现。此时需通过plugin机制扩展,而plugin需要以JavaScript实现原生逻辑。例如,扩展更复杂的ListView时,参考官方实现,在Android和iOS平台需分别用JavaScript编写原生代码。这让前端开发者面临挑战,因为需熟悉原生API;而让客户端开发者用JavaScript实现也增加复杂度。即使在普通业务开发中,也可能出现大段以JavaScript完成原生逻辑的代码,例如在计算器应用中为安卓平台实现文字自适应缩放,需基于Canvas计算宽度,代码混杂在业务中,可读性差。

其次,“run JavaScript on the main UI thread”可能是炸弹。NativeScript将JavaScript运行在UI线程,以去掉异步通信、提升性能,并声称常见异步场景已由框架处理。但如果开发者在代码中编写耗时操作,会直接阻塞UI线程,导致性能问题。相比将JavaScript运行在独立线程的模式,这种设计更易因代码失误引发问题。

最后,Integrating existing native apps支持不足。React Native因热更新和便捷集成能力而火爆,而NativeScript早期是非此即彼的排他性框架。虽然当前版本支持集成进原生应用,但文档不完善,集成复杂度较高,支持刚起步且不成熟。

最后

NativeScript问世已久但未大火。去年React专利风波时曾有推荐,但风潮过后无下文。个人探索后决定从入门到放弃。未在生产环境长期使用,感受可能不准确,但不推荐使用该框架。如果偏爱Angular,可考虑Ionic;偏爱Vue,可了解Weex。跨端开发有多种方案,不必局限于NativeScript的坑。

参考文章

  • How NativeScript Works
  • The Benefits of NativeScript’s Single Threading Model
  • 使用NativeScript和Angular2构建跨平台APP
0 Answers