移动端跨平台技术方案研究

1,573 阅读8分钟

自从android和iOS两大移动平台的确立以来,人们就一直在不懈地探索各种跨平台技术,希望于能用一个团队,一套代码高效地横跨两大平台,节省人力成本的同时,快速地完成产品的开发迭代,在快速发展和激烈竞争的移动互联网环境中占得优势。

如何跨平台

所有的跨平台技术方案概括起来无外乎是就是两个方面:

  • UI跨平台

      包括控件、布局、动画、手势等的渲染和交互。
    
  • 非UI平台API调用

      桥接调用平台系统能力。如传感器、摄像头、设备信息、网络状态等。
    

技术划分

跨平台技术的核心在于UI跨平台。根据实现UI跨平台方式的不同,目前的跨平台技术可以分为下面三类:

H5应用或Hybrid应用

  • 原理:基于H5技术,通过WebView渲染UI,通过JS桥接和原生交互

  • 框架:CordovaIonic

  • 优势:开发效率、动态性、包大小

  • 劣势:性能和用户体验

  • 使用情况:非常广泛,个别深度使用(优化性能)

    12306,银行类App,支付宝手机QQ百度App

用Web技术开发原生应用

  • 原理:基于类HTML+CSS+JS开发界面和业务逻辑,并转换为原生视图进行渲染。

  • 框架:React NativeWeex

  • 优势:前端技术栈、开发效率、动态性

  • 劣势:长列表滑动性能、跨端一致性

  • 使用情况:比较广泛,国内国外,大厂小厂均有

    携程美团京东、饿了么、腾讯课堂

Flutter

  • 原理:基于跨平台高性能2D渲染引擎Skia绘制界面。DART在Debug模式下JIT编译,支持热重载;在Release模式下AOT编译成ARM汇编代码,执行效率高,调用原生API效率高;支持isolate多线程执行

  • 优势:精美、接近原生的性能(闲鱼:平均52帧/秒,300毫秒页面加载时间)、跨端一致性

  • 劣势:成熟度(框架本身,社区生态),包大小

  • 使用情况:大厂都在研究和尝试

    闲鱼、腾讯、京东、美团

React Native实例

React Native主要包括以下要点:

  • JavaScript

      let var const 
      数字、布尔、字符串、undefined、null、对象
      Array、Date、Error
      闭包
      箭头函数
    
  • React

      JSX语法   
          JavaScript里嵌XML,用于描述UI。JSX中的JS表达式要{}包起来。
      组件(React.Component)
          state状态
          props属性
          生命周期方法
      	    constructor
      	    componentWillMount
      	    render
      	    componentDidMount
      	    componentWillUnmount
      常用UI组件
          View
          Image
          Text
          FlatList
      声明式UI
      样式分离
      Flexbox布局
    
  • 状态管理 redux

  • 网络请求 Fetch API (Promise或async、await)

  • 依赖管理

      NPM install    Pod install
    
  • 第三方组件

      https://github.com/react-native-community
      https://react.parts/?collection=React+Native
      https://github.com/ant-design/ant-design-mobile-rn/
      https://github.com/Meituan-Dianping/beeshell
    
  • 调试\热重载

      IDE CLI Chrome Shake
    
  • 性能优化

      https://reactnative.cn/docs/performance/
      https://blog.csdn.net/sinat_17775997/article/details/80852485
      https://mp.weixin.qq.com/s/Z1GUJW3qBqDGH1jnGt5qAg
    

下面我们结合一个简单的RN实例代码学习和了解RN的一些基本概念和知识。

例子代码:RNDemo

这个例子非常简单,只有一个页面,它从网络请求数据,然后在列表里显示数据。

// 应用入口
AppRegistry.registerComponent(appName, () => App);

export default class App extends React.Component {
  // 构造函数:初始化属性和变量
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      loaded: false
    };
    // 在ES6中,如果在自定义的函数里使用了this关键字,则需要对其进行“绑定”操作,否则this的指向会变为空
    this.fetchData = this.fetchData.bind(this);
    this.renderMovie = this.renderMovie.bind(this);
  }

  // 组件已挂载:请求数据
  componentDidMount() {
    this.fetchData();
  }

  // 绘制方法:返回JSX视图结构
  render() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <>
        <StatusBar barStyle="dark-content" />
        <SafeAreaView>
          <FlatList
            data={this.state.data}
            renderItem={this.renderMovie}
            style={{ backgroundColor: "#F5FCFF" }}
            keyExtractor={item => item.id}
          />
        </SafeAreaView>
      </>
    );
  }
  
  // 异步网络请求
  async fetchData() {
    try {
      let response = await fetch(REQUEST_URL);
      let responseData = await response.json();
      this.setState({
        data: this.state.data.concat(responseData.movies),
        loaded: true
      });
    } catch (error) {
      console.error(error);
    }
  }
}  
  //样式
var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF",
    paddingHorizontal: 15,
    paddingVertical: 10
  },
});

调用this.setState方法会触发render()方法重绘。

Flutter实例

Flutter主要包括以下要点:

  • Dart

      var final const static
      可选位置参数[]、可选命名参数{}
      箭头函数		
      Class 命名构造函数 工厂构造函数factory  重定向构造方法:
      Map List Set
      级连 ..
      Future async await
    
  • Widget

      无状态StatelessWidget   有状态StatefullWidget & State
      生命周期方法
          initState
          build
          dispose
    
  • 万物皆Widget

    Widget非常轻量,仅包含不可变的配置信息。

      基础Widget     Button Text Image TextField
      布局Widget     Row Column Center Padding Container Expanded
      滚动Widget     ListView	GridView PageView
      装饰Widget     BoxDecoration Opacity 
      
      https://flutter.cn/docs/development/ui/widgets
      https://flutter.cn/docs/reference/widgets
      https://github.com/alibaba/flutter-go
    
  • 两套UI风格 Material Cupertino

    对应android默认的物化风格和iOS系统的原生风格。

  • 声明式UI

  • 网络请求 dio

  • 依赖管理

    包含原生调用的package称为plugin。

      package plugin
      flutter pub get 
    
  • 第三方包

    pub.flutter-io.cn/flutter

  • 调试\热重载

      IDE CLI DevTool
    
  • 性能优化

    Flutter本身已具备较好的性能,性能方面需要关注的点不多。

    flutter.cn/docs/testin…

下面我们结合一个简单的Flutter实例代码学习和了解Flutter的一些基本概念和知识。

例子代码:FlutterDemo

这个例子非常简单,只有一个页面,它从网络请求数据,然后在列表里显示数据。

// 应用入口
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // 构建方法:这里返回应用根Widget
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: FutureBuilderPage(),
    );
  }
}

class FutureBuilderPage extends StatefulWidget {
  // 创建状态对象
  @override
  _FutureBuilderPageState createState() => _FutureBuilderPageState();
}

class _FutureBuilderPageState extends State<FutureBuilderPage> {
  Future future;

  // 初始化方法
  @override
  void initState() {
    super.initState();
    future = getListData(); // 请求网络数据
  }

  // 构建方法:返回Widget树
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('知识体系')), 
        body: buildFutureBuilder(),
        );
  }

  // 使用FutureBuilder处理异步网络请求
  FutureBuilder<List<Data>> buildFutureBuilder() {
    return FutureBuilder<List<Data>>(
        future: future,
        builder: (context, snapshot) {
          //在这里根据快照的状态,返回相应的widget
          if (snapshot.connectionState == ConnectionState.done) {// 请求完成
            if (snapshot.hasError) {// 请求失败
              return Center(
                child: Text("ERROR"),
              );
            } else if (snapshot.hasData) {// 请求成功,数据用列表显示
              List<Data> list = snapshot.data;
              return RefreshIndicator(
                  child: buildListView(context, list), onRefresh: refresh);
            }
          }
          // 正在加载
          return Center(
            child: CircularProgressIndicator(),
          );
        });
  }

  buildListView(BuildContext context, List<Data> list) {
    return ListView.builder(// 构建长列表
        itemCount: list.length,
        itemBuilder: (context, index) {
          Data item = list[index];
          StringBuffer str = new StringBuffer();
          for (Children children in item.children) {
            str.write(children.name + "  ");
          }
          return Column(
              children: [
                ListTile(
                  title: Text(item.name),
                  subtitle: Text(str.toString()),
                  trailing: IconButton(
                    icon: Icon(
                      Icons.navigate_next,
                      color: Colors.grey,
                    ),
                    onPressed: () {})
                ),
                Divider()
              ]
            );
        });
  }

  //获取网络数据,利用dio库进行网络请求,拿到数据后利用json_serializable解析
  //并将列表的数据包装在一个future中
  Future<List<Data>> getListData() async {
    var dio = new Dio();
    Response response = await dio.get("http://www.wanandroid.com/tree/json");
    Map<String, dynamic> map = response.data;
    Entity entity = Entity.fromJson(map);
    return entity.data;
  }

  //刷新数据,重新设置future就行了
  Future refresh() async {
    setState(() {
      future = getListData();
    });
  }
}

对比

  • 学习成本、接入成本

    React Native对于前端人员来说学习成本是比较低的。Flutter个人感觉学习成本比RN略高一点,自带的Widget比较多。 接入成本我觉得两者差不多。

  • 框架成熟度

    目前来说,两者都是比较稳定的。但从GitHub上issues的数量上看React Native要更稳定,毕竟RN出现的较早。不过Flutter的迭代速度明显更快,毕竟Google的实力更强,相信Flutter会很快反超。

  • 社区成熟度、行业采用度

    这方面也是React Native更有优势一些,同样得益于它时间上出现的更早。两三年前作为唯一靠谱的跨平台方案,它得到了很多大厂的认可和采用,这些大厂也在RN生态的建设上投入了比较多的精力。有人统计中国市场上排名前100的应用有十几个使用了RN或Weex。而Flutter虽然时间较晚,但发展很快,目前,开发中常用的一些模块都已有成熟的第三方packages。大小公司也都投入了很大的热情去研究和尝试。

  • 开发体验

    开发体验上个人觉得两者差不多。都支持热重载。提供的IDE插件,命令行工具,可视化DevTool也很相似。

  • 一致性

    React Native由于采用原生视图渲染,我们常常需要去适配和桥接两个平台的差异性。且RN不同平台JS引擎的不一致,也埋下了一些隐患。而Flutter则是在更底层抹平了平台差异,使用统一的Skia引擎渲染,所以从原理上来说Flutter的跨端一致性会更好。而实际体验中Flutter也确实更有优势。

  • 动态性

    动态性是React Native最大的优势,这也是很多电商、外卖大厂深度使用RN的主要原因。而Flutter仅在Android上支持动态化。

  • 包大小

    这里有一个对比数据。我们可以看到,同等条件下:

    RN的包iOS比Android小很多。因为iOS内置了JavaScriptCore。

    Flutter包Android比iOS小很多。因为Android内置了Skia引擎。

  • 内存/CPU/FPS

    Flutter在数据和实际体验方面都优于RN。详情可以查看下面的链接: www.yuque.com/xytech/flut…

  • 初始化时长,页面渲染时长

    两者直接的对比比较少,但从两者与原生的对比文章来看,Flutter这方面的性能更优。实际体验也是如此。

详细的对比信息可以参考下面的链接:

https://juejin.cn/post/6844903795965558792#heading-22
https://juejin.cn/post/6844903872130088974#heading-10
https://www.yuque.com/xytech/flutter/blquhk

总结

本文结合简单的实例代码对React Native和Flutter进行了学习和对比,让大家对目前两个主流的跨平台方案有一个初步的认识。其目的是希望给大家的技术选型提供一点帮助。具体到各自的项目,大家还是需要结合根据业务场景、人力情况、需求来决定是否使用跨平台方案,使用哪个跨平台方案。

  • 对于UI和交互简单,性能要求不高,但动态性很重要的应用可以用H5或Hybrid的方式。
  • 对于UI和交互较复杂,有一定的性能要求,动态性很重要的应用可以用React Native(或Weex)。
  • 对于复杂的UI和交互,性能要求高,但动态性要求不是首要的应用可以用Flutter。

Flutter无疑站在了更高的起点上,但它也未必一统江湖。合适的才是最好的。