最近公司开始推行使用Flutter用于移动端开发,忙活了一个多月的Flutter混合开发迭代端午节后准备上线,写下此过程的坑以及一些接入流程,以及Flutter技术。
由于我主业是搞Android开发的,iOS还是个菜鸟,先介绍一下Android混合接入流程
1.创建Flutter module
很多情况下,Flutter的接入都是在原有的移动端项目的基础上接入,这样相对于侵入原有项目弱,并且接入的成本低,风险也低。
flutter create -t module flutter_module复制代码
在命令行界面录入即可,最好是和原本的项目在相同的目录下,同级文件夹。我的项目基本是原有项目是一个git仓库,对应的Flutter代码是在另一个git仓库,这样版本管理也是比较好的
2.Android 开始接入
在原有setting.gradle文件末尾的添加一下代码
setBinding(new Binding([gradle: this]))evaluate(new File( settingsDir.parentFile, '/flutter_module/.android/include_flutter.groovy'))复制代码
这样就会引入到对应的Flutter的module资源,对应的编译脚本,Flutter框架已经写好,后续有时间可以读读
在app目录下的build.gradle文件中添加Flutter依赖
implementation project(':flutter')复制代码
主要的依赖链如下
flutter_module/.android/include_flutter.groovy ->flutter_module/.android/Flutter/build.gradle ->$flutterRoot/packages/flutter_tools/gradle/flutter.gradle 复制代码
很方便的完成混合开发的打包操作,接入完之后最好在flutter项目下面的.android文件下,用命令行工具执行以下
gradlew assembleDebug复制代码
命令完成对应的Android依赖加载
3.原生显示Flutter的视图
Button open = findViewById(R.id.openBtn);open.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(MainActivity.this, MyFlutterActivity.class); startActivity(intent); }});public class MyFlutterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_flutter); final FlutterView flutterView = Flutter.createView( this, getLifecycle(), "route1" ); final FrameLayout layout = findViewById(R.id.flutter_container); layout.addView(flutterView); }}复制代码
只需要在Activity中的addView即可,看起来很简单,不过存在很多问题,跳转过程可能会有黑屏情况,以及Flutter视图的复用也是个问题,以及对应的Flutter 跳转Native界面也是个问题。于是乎谷歌发现FlutterBoost框架解决了大部分以上的问题。
4.FlutterBoost框架的接入
依照官方的文档,接入发现有很多的坑,不知道是不是开发人员都反感写文档一样的。
接入的流程如下:
4.1 Flutter项目接入FlutterBoost
在对应的pubspec.yaml文件中加入依赖,pubspec.yaml就是一个配置文件。
flutter_boost: git: url: 'https://github.com/alibaba/flutter_boost.git' ref: '0.0.411'复制代码
之后调用Package get,右上角即可查看,之后还是在.android 文件下执行 gradlew assembleDebug,完成依赖下载。
4.2 Flutter中main.dart文件中配置
@override void initState() { super.initState(); FlutterBoost.singleton.registerPageBuilders({ //对应的Page的名字,最好是类Http格式 'demoPage': (pageName, params, _) { return DemoPage(); }, }); FlutterBoost.handleOnStartPage(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Boost example', builder: FlutterBoost.init(postPush: _onRoutePushed), home: Container()); } void _onRoutePushed( String pageName, String uniqueId, Map params, Route route, Future _) {}复制代码
4.3 Android 中的Application的设置
@Override public void onCreate() { super.onCreate(); initFlutterBoot(); } private void initFlutterBoot() { FlutterBoostPlugin.init(new IPlatform() { @Override public Application getApplication() { return mApplication; } @Override public Activity getMainActivity () { //这里返回null,可以避免跳转界面的(MainActivity)的页面整体上移 return null; } @Override public boolean isDebug() { return AppConfig.IS_DEVELOPING; } @Override public boolean startActivity(Context context, String url, int requestCode) { //Flutter 跳转的回调 return PageRouter.openPageByUrl(context,url,requestCode); } @Override public Map getSettings() { return null; } }); }复制代码
坑1:getMainActivity 如果返回MainActivity,会导致对应的主页面的布局整体上移,发现给null也没啥问题。
4.4 Native和Flutter界面的跳转
Native-->Fluuter
可以使用官方Demo中的PageRouter
public class PageRouter { public static final String COMMISSION_TASK_PAGE = "gamma://flutter/commissionTaskPage"; public static final String NATIVE_CUST_INFO_PAGE_URL = "gamma://native/custInfo"; public static boolean openPageByUrl(Context context, String url) { return openPageByUrl(context, url, 0, "{}"); } public static boolean openPageByUrl(Context context, String url, String json) { return openPageByUrl(context, url, 0, json); } public static boolean openPageByUrl(Context context, String url, int requestCode) { return openPageByUrl(context, url, requestCode, "{}"); } public static boolean openPageByUrl(Context context, String url, int requestCode, String json) { try { if (url.startsWith(COMMISSION_TASK_PAGE)) { //贷后任务 Intent intent = new Intent(context, FlutterPageActivity.class); intent.putExtra("pageName", "gamma://flutter/commissionTaskPage"); intent.putExtra("json", json); context.startActivity(intent); return true; } else if (url.contains(NATIVE_CUST_INFO_PAGE_URL)) { //打开客户界面 Mapparams = getUrlParams(url); AppUIHelper .showCustomerInfoActivity(context, params.get("custId")); return true; } } catch (Throwable t) { return false; } }//获取对应url中的参数 private static Map getUrlParams(String url) { Map map = new HashMap<>(); url = url.replace("?", ";"); if (!url.contains(";")) { return map; } if (url.split(";").length > 0) { String[] arr = url.split(";")[1].split("&"); for (String s : arr) { String key = s.split("=")[0]; String value = s.split("=")[1]; map.put(key, value); } return map; } else { return map; } }}复制代码
这样相对于好管理,Native页面可以设置以Native开头,Flutter以flutter开头,这样好区分。
对应的FlutterPageActivity可以根据官方Demo自行修改。
坑2:FlutterPageActivity中最好加上以下代码
@Override protected void onCreate(Bundle savedInstanceState) { FlutterMain.startInitialization(this); super.onCreate(savedInstanceState); }复制代码
不然会运行报错
Flutter -- >Native
这种类型跳转和Activity之间互相跳转有点类似
FlutterBoost.singleton.openPage( "${NativeUrl.NATIVE_CUST_INFO}?custId=${custId};", {});复制代码
这中类型的跳转会回调到前面Application中的 FlutterBoostPlugin.init 方法中的startActivity方法中,只会在用PageRouter来接收即可。 之后自行做界面的跳转
5.打包的坑
在打release包的时候需要在Flutter的跟目录下执行以下命令
flutter build apk复制代码
之后是常规的Android的打包,不然打包出来的apk跳转到Flutter页面就会闪退