跨平台技术篇 - 使用 Flutter 与原生技术混合开发示例
目前主流的混合开发方案有两种集成方式:
- 源码集成:也就是谷歌官方提供的方案[https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps]
- 产物集成:Flutter 项目单独开发,开发完成后发布成 aar 包或者 iOS 的 framework 形式,原生项目依赖 Flutter 输出的制品即可。
两种方式对比:
源码集成 | 产物集成 | |
---|---|---|
优点 |
1. 简单快捷、Google 原生支持 (beta 版) 2. 和原生交互较多或需要依赖原生的数据环境使用源码集成开发调试更方便 |
1. 不影响原生项目 2. 不参与 Flutter 开发的人员处于无感状态 3. 无需对现有的持续集成环境和编译发布流程进行修改 |
缺点 |
1. 团队所有人都需要安装 Flutter 环境 2. 需要对现有编译发布体系做修改 |
1. 复杂度相对较高,如果是小团队成本较高 2. 需要和原生交互的时候调试不方便 |
两种方式各有优劣,其实产物集成更好一些,不过即使是进行产物集成,也需要弄懂源码集成的方式,因为当有很多和原生交互的功能进行开发的时候,源码集成的方式可以直接调试会方便很多。整个的集成方案是参考谷歌方法:[https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps],但是有一些不一样,我是创建了一个 flutter 项目后,在原生的项目中使用
git submodule的形式进行管理的。
1. 创建 flutter module project
我们假定已经有了原生的项目
Native-iOS和
Native-Android;现在我们需要创建我们的 flutter 项目。
- 1.1 把我们的 flutter 的 channel 切换到 master (master 分支下是 flutter 的 preview 版本)
[code]MacBook-Pro-3:project kuangzhongwen$ flutter channel master Switching to flutter channel 'master'... git: From https://github.com/flutter/flutter git: * [new branch] Hixie-patch-2 -> origin/Hixie-patch-2 git: * [new branch] compress_pngs -> origin/compress_pngs git: 3b3f6c7a0..fbefd6b81 dev -> origin/dev git: de6995cfa..88d50f78f master -> origin/master git: * [new branch] v1.4.5-hotfixes -> origin/v1.4.5-hotfixes git: * [new branch] v1.4.6-hotfixes -> origin/v1.4.6-hotfixes git: * [new tag] v1.4.5 -> v1.4.5 git: * [new tag] v1.4.6-hotfix.1 -> v1.4.6-hotfix.1 git: * [new tag] v1.4.8 -> v1.4.8 git: * [new tag] v1.4.6 -> v1.4.6 git: * [new tag] v1.4.7 -> v1.4.7 git: Switched to a new branch 'master' git: Branch 'master' set up to track remote branch 'master' from 'origin'.
- 1.2 创建 flutter 模块的项目
我这里创建一个 flutter 的模块项目叫
flutter_module:
[code]MacBook-Pro-3:project kuangzhongwen$ flutter create -t module flutter_module Downloading Dart SDK from Flutter engine 309d078b597d3a38fe9bcef8d8bae7b56487e8eb... % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 118M 100 118M 0 0 9638k 0 0:00:12 0:00:12 --:--:-- 10.3M Building flutter tool... Downloading android-arm-profile/darwin-x64 tools... 1.0s Downloading android-arm-release/darwin-x64 tools... 0.9s Downloading android-arm64-profile/darwin-x64 tools... 0.9s Downloading android-arm64-release/darwin-x64 tools... 0.9s Downloading android-arm-dynamic-profile/darwin-x64 tools... 0.9s Downloading android-arm-dynamic-release/darwin-x64 tools... 0.9s Downloading android-arm64-dynamic-profile/darwin-x64 tools... 0.8s Downloading android-arm64-dynamic-release/darwin-x64 tools... 0.8s Downloading android-x86 tools... 2.9s Downloading android-x64 tools... 2.8s Downloading android-arm tools... 1.3s Downloading android-arm-profile tools... 1.1s Downloading androi 4000 d-arm-release tools... 1.0s Downloading android-arm64 tools... 1.4s Downloading android-arm64-profile tools... 1.2s Downloading android-arm64-release tools... 1.0s Downloading android-arm-dynamic-profile tools... 1.1s Downloading android-arm-dynamic-release tools... 1.1s Downloading android-arm64-dynamic-profile tools... 1.2s Downloading android-arm64-dynamic-release tools... 1.0s Downloading ios tools... 4.7s Downloading ios-profile tools... 3.7s Downloading ios-release tools... 2.9s Downloading package sky_engine... 0.8s Downloading common tools... 1.6s Downloading darwin-x64 tools... 4.3s Creating project flutter_module... flutter_module/test/widget_test.dart (created) flutter_module/flutter_module.iml (created) flutter_module/.gitignore (created) flutter_module/.metadata (created) flutter_module/pubspec.yaml (created) flutter_module/README.md (created) flutter_module/lib/main.dart (created) flutter_module/flutter_module_android.iml (created) flutter_module/.idea/libraries/Flutter_for_Android.xml (created) flutter_module/.idea/libraries/Dart_SDK.xml (created) flutter_module/.idea/modules.xml (created) flutter_module/.idea/workspace.xml (created) Running "flutter packages get" in flutter_module... 4.8s Wrote 12 files. All done! Your module code is in flutter_module/lib/main.dart.
看一下创建好的项目目录:
[code]MacBook-Pro-3:project kuangzhongwen$ cd flutter_module/ MacBook-Pro-3:flutter_module kuangzhongwen$ ls -a . .packages .. README.md .DS_Store flutter_module.iml .android flutter_module_android.iml .gitignore lib .idea pubspec.lock .ios pubspec.yaml .metadata test
在 flutter 的模块项目中包含有一个隐藏的
.android和
.ios目录这个目录下是可运行的 Android 和 iOS 项目,我们的 flutter代码还是在
lib下编写,注意在
.android和
.ios目录下都有一个 Flutter 目录,这个是我们 flutter 的库项目了。也就是Android 用来生成 aar,iOS 用来生产 framework 的库。如果我们用
flutter create xxx生成的纯 flutter 项目是没有这个Flutter 目录的。
- 1.3 把该项目使用 git 管理起来,稍后我们要在 native 项目中以子模块的形式添加进去
[code]MacBook-Pro-3:flutter_module kuangzhongwen$ git init Initialized empty Git repository in /Users/kuangzhongwen/Desktop/project/flutter_module/.git/
初始化 git 仓库后我们先编辑一下项目下的
.gitignore文件,当前这个文件是把项目下的
.ios和
.android忽略掉的。这个两个项目我们需要跟踪一下,大家可以去 github 上找一下 iOS 和 Android 的 gitignore 模版文件,然后添加到这个两个目录中,然后把顶层目录的文件作出如下修改,删除
.android 和 .ios添加
.ios/Flutter/Generated.xcconfig。
我的 .gitignore 如下:
[code].DS_Store .dart_tool/ .packages .pub/ .idea/ .vagrant/ .sconsign.dblite .svn/ *.swp profile DerivedData/ .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* build/ .ios/Flutter/Generated.xcconfig .flutter-plugins
- 1.4 提交你的 flutter 模块项目到你的 git 服务器
[code]echo "# FlutterModuleTest" >> README.md git init git add . git commit -m "first commit" git remote add origin https://github.com/kuangzhongwen/FlutterModuleTest.git git push -u origin master
2. 给原生 Android 项目集成 Flutter
iOS 集成的方式可以上网搜一下,这边讲讲 Android 的集成方式。
- 2.1 在原生 Android 项目中添加子模块,将上面创建的 flutter module 项目拉取到原生安卓项目中
[code]cd NativeFlutter/ git submodule add https://github.com/kuangzhongwen/FlutterModuleTest.git git submodule update
- 2.2 在根目录的
settings.gradle
中添加如下配置
[code]setBinding(new Binding([gradle: this])) evaluate(new File( '/Users/kuangzhongwen/Desktop/project/NativeFlutter/FlutterModuleTest/.android/include_flutter.groovy' ))
- 2.3 在原生项目的 app 目录下的 build.gradle 文件中添加 Flutter 库的依赖
[code]dependencies { implementation project(':flutter') }
其中 flutter 工程为创建 Flutter module 过程自动生成的,注意就是 flutter。
- 2.4 在原生项目中新建搭载 flutter view 的 Activity
[code]public class MyFlutterActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final FrameLayout root = new FrameLayout(this); root.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); setContentView(root); root.setVisibility(View.INVISIBLE); final FlutterView flutterView = Flutter.createView(this, getLifecycle(), "route1"); root.addView(flutterView); /** * Android 从原生跳到 Flutter 模块的黑屏问题,在网上看到很多说设置透明主题的但是没有用, * 后来看到一种先隐藏显示,等待渲染好第一帧后才显示 flutter 页面的方法。 */ final FlutterView.FirstFrameListener[] listeners = new FlutterView.FirstFrameListener[1]; listeners[0] = new FlutterView.FirstFrameListener() { @Override public void onFirstFrame() { root.setVisibility(View.VISIBLE); } }; flutterView.addFirstFrameListener(listeners[0]); } }
其中 createView(),第二个参数是 Lifecycle 对象, 第三个参数为 route,这个参数 Flutter 端可以通过window.defaultRouteName() 获取,利用它 flutter 可知道要创建哪个 widget。同理 Flutter.createFragment(String route) 可生成 FlutterFragment,最后别忘了在清单文件中注册。
- 2.5 在原生代码中集成 flutter 跳转到 flutter 页面
[code]public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.go_to_flutter_btn).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyFlutterActivity.class); startActivity(intent); } }); } }
- 2.6 编译运行
编译运行报错:
[code]Invoke-customs are only supported starting with Android O (--min-api 26) Message{kind=ERROR
在 app.gradle 中加入:
[code] compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }
同时注意,最小 sdk 版本要改成 16 或以上:
[code]minSdkVersion 16
flutter 的 dart 代码位于:/FlutterModuleTest/lib/main.dart。
[code]import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the // counter didn't reset back to zero; the application is not restarted. primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Invoke "debug painting" (press "p" in the console, choose the // "Toggle Debug Paint" action from the Flutter Inspector in Android // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) // to see the wireframe for each widget. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
看看效果:
- Android 原生开发、H5、React-Native使用利弊和场景技术分享
- 使用Google的Flutter开发跨平台应用
- 【技术直通车】使用Dreamweaver 5.5 + JQuery + PhoneGap+ArcGIS Javascript API 开发跨平台手机应用程序
- Android 原生开发、H5、React-Native使用利弊和场景技术分享
- APP,原生和H5开发技术混合开发
- 使用SWT技术的跨平台移动应用开发库Tabris
- iOS与H5交互 H5与App原生交互,一般会是前端页面中的JavaScript与App使用的原生开发语言的交互。技术方案应能达到以下要求: 在js与原生进行交互的时候能保证正常的正向调用逻辑返回
- 使用 IBM Worklight 开发跨平台的 HTML5 视频播放混合应用程序
- 横向浅谈移动技术------( 原生,混合,web --- 谁能问鼎移动开发的明天)
- 使用AJAX技术开发新一代Web应用程序 2
- 使用J2ME技术开发RPG游戏(二)——按键处理机制
- 使用wxWidgets开发跨平台的GUI程序
- 使用J2ME技术开发RPG游戏(二)——按键处理机制
- Asp.net 2.0 自定义控件开发专题[详细探讨页面状态(视图状态和控件状态)机制及其使用场景](示例代码下载)
- 如何使用Ajax技术开发Web应用程序
- 如何使用DOTMSN开发简单的MSN机器人示例(附示例源码)
- 使用J2ME技术开发RPG游戏
- 使用J2ME技术开发RPG游戏(一)——程序框架
- 使用J2ME技术开发RPG游戏(一)——程序框架
- MSDN:Windows SharePoint Services 3.0 中使用代码的开发工具和技术(第 2 部分)