- 重0开始新建一个module
- 利用已有的module
- 在原生页面中展示Flutter页面
- flutter和原生通信
- 使用FlutterBoost实现各种路由需求
其实Flutter的混编方案并非只有一种,但是本文所介绍的是Flutter团队官方给出的方案。它的优缺点我暂不讨论,接下来我们一步一步的实现一个Android项目的混编。
新建module
使用命令新建一个flutter的module,或者也可以使用AndroidStudio直接新建一个flutter module。
flutter create -t module mymodule
然后在app下的build.gradle文件中添加下面两个部分的内容
在android中添加
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
在dependencies添加flutter项目的依赖
implementation project(':flutter')
在项目的中的setting.gradle文件中添加如下代码
setBinding(new Binding([gradle: this]))
evaluate(new File(
'./mymodule/.android/include_flutter.groovy'))
注意:如果你的最低支持的sdk版本是低于16的话是会报错的,因为flutter最低支持api 16版本。ok如果不出什么其他意外的话集成的步骤已经完成了,可以开始原生和Flutter的混编之旅了。
Flutter页面在Activity中展示
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FlutterView flutterView = Flutter.createView(
MainActivity.this,
getLifecycle(),
"route1"
);
FrameLayout layout = findViewById(R.id.content);
layout.addView(flutterView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
}
Flutter页面在Fragment中展示
public class DemoFragmentActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.someContainer, Flutter.createFragment("route1"));
tx.commit();
}
}
或者是这样写
public class DemoFlutterFragment extends Fragment {
@Override
public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return Flutter.createView(getActivity(), getLifecycle(), "route1");
}
}
这两种方式其实并没有什么区别,只是前者的createFragment方式是帮我们创建了一个FlutterFragment对象,这个FlutterFragment类的路径如下图所示:
FlutterFragment的源码是这个样子的
public class FlutterFragment extends Fragment {
public static final String ARG_ROUTE = "route";
private String mRoute = "/“;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mRoute = getArguments().getString(ARG_ROUTE);
}
}
@Override
public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
}
@Override
public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return Flutter.createView(getActivity(), getLifecycle(), mRoute);
}
}
如何展示指定的Flutter页面
修改main.dart代码如下,根据传过来的路由名称来展示不同的flutter页面,默认情况是route1
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'route1':
return SomeWidget(...);
case 'route2':
return SomeOtherWidget(...);
default:
return Center(
child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
);
}
}
如何解决flutter页面第一次加载黑屏的问题
public class DemoActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
FlutterView flutterView = Flutter.createView(
DemoActivity.this,
getLifecycle(),
"route1"
);
FrameLayout layout = findViewById(R.id.content);
layout.addView(flutterView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
final FlutterView.FirstFrameListener[] listeners = new FlutterView.FirstFrameListener[1];
listeners[0] = new FlutterView.FirstFrameListener() {
@Override
public void onFirstFrame() {
layout.setVisibility(View.VISIBLE);
}
};
flutterView.addFirstFrameListener(listeners[0]);
}
}
使用消息通道和原生通信
这里以一个实际例子来说明问题,比如说flutter中打印日志只能使用print,并没有像Android里面的日志分级调试起来很不方便,这个时候就可以使用消息通道来调用原生的Log。
flutter代码
class LogUtils {
static const perform = const MethodChannel("android_log");
static void v(String tag, String message) {
perform.invokeMethod('logV', {'tag': tag, 'msg': message});
}
static void d(String tag, String message) {
perform.invokeMethod('logD', {'tag': tag, 'msg': message});
}
static void i(String tag, String message) {
perform.invokeMethod('logI', {'tag': tag, 'msg': message});
}
static void w(String tag, String message) {
perform.invokeMethod('logW', {'tag': tag, 'msg': message});
}
static void e(String tag, String message) {
perform.invokeMethod('logE', {'tag': tag, 'msg': message});
}
}
原生代码,这里使用了kotlin
MethodChannel(flutterView,"android_log").setMethodCallHandler { call, result ->
logPrint(call)
}
private fun logPrint(call: MethodCall) {
var tag: String = call.argument("tag")!!
var message: String = call.argument("msg")!!
when (call.method) {
"logV" -> Log.v(tag, message)
"logD" -> Log.d(tag, message)
"logI" -> Log.i(tag, message)
"logW" -> Log.w(tag, message)
"logE" -> Log.e(tag, message)
}
}
如何使用呢?在dart代码中调用如下:
LogUtils.v("tag", "v------");
LogUtils.d("tag", "d------");
LogUtils.i("tag", "i------");
LogUtils.w("tag", "w------");
LogUtils.e("tag", "e------");
更多关于消息通道的问题请参考https://flutter.dev/docs/development/platform-integration/platform-channels
怎么使用FlutterBoost做路由
flutter混编之后的路由跳转情况
原生跳原生
原生跳flutter
flutter跳原生
flutter跳flutter
原生跳原生这个不用说了之前怎么样就是怎么样,原生跳flutter、flutter跳原生、flutter跳flutter这三种情况我们想想要怎么处理呢?原生跳flutter页面我们可以通过原生页面A跳转到原生页面B然后通过原生页面B展示flutterB页面,flutter跳原生可以让flutterA通过消息通道通知原生页面A跳转到原生页面B,那么flutter跳flutter怎么办呢?首先要说的一点是在混编方案中Flutter本身的那套路由跳转(Navigator.push)是不起作用的,这点我们也是在开发新闻开发者这个demo的时候才发现的,那么怎么办呢?最笨的办法是flutterA通知原生页面A跳转原生页面B然后使用原生页面B展示flutterB,但是这样做不仅麻烦而且还消耗内存。原因是原生页面加载一个flutter页面的时候都要初始化一个flutter引擎,而不同页面的flutter引擎资源是不共享的,而且引擎本身也是非常重的。于是FlutterBoost进入我们的视野,FlutterBoost采用的方案是共享引擎模式,换句话说我们不同原生页面可以使用同一个flutter引擎,这样是不是就解决了我们的苦恼?
如何使用?
在Flutter的pubspec.yaml文件中添加依赖
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
flutter_boost: ^0.0.400//添加flutterboost依赖
或者这样
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.0.408'
然后在对应的flutter module路径先执行 flutter paskages get命令,然后重新build。
在Flutter端注册FlutterBoost
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
///register page widget builders,the key is pageName
FlutterBoost.singleton.registerPageBuilders({
'MyHomePage': (pageName, params, _) => MyHomePage(),
'FlutterPage': (pageName, params, _) => FlutterPage(),
'FlutterFragmentPage': (pageName, params, _) => FlutterFragmentPage()
});
///query current top page and load it
FlutterBoost.handleOnStartPage();
}
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(),//注册FlutterBoost
///init container manager
home: Container());
}
原生方面在app的build.gradle添加flutterboost依赖
implementation project(path: ':flutter_boost')
在Application中注册
FlutterBoostPlugin.init(new IPlatform() {
@Override
public Application getApplication() {
return App.this;
}
@Override
public Activity getMainActivity() {
return null;//返回MainActivity对象
}
@Override
public boolean isDebug() {
//表示是否是debug模式
return false;
}
@Override
public boolean startActivity(Context context, String url, int requestCode) {
//用于做路由跳转,url是在main.dart中定义好的页面的名称,返回true表示跳转成功,返回false表示失败
//return false;
return PageRouter.openPageByUrl(context, url);
}
@Override
public Map getSettings() {
return null;
}
});
在Activity中展示Flutter页面
public class FlutterPageActivity extends BoostFlutterActivity {
@Override
public String getContainerName() {
return "FlutterPage";
}
@Override
public Map getContainerParams() {
//传参数的时候用
return null;
}
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
}
在Fragment中展示Flutter页面
public class DemoFlutterFragment extends BoostFlutterFragment {
@Override
public void destroyContainer() {
}
@Override
public String getContainerName() {
return "FlutterFragmentPage";
}
@Override
public Map getContainerParams() {
return null;
}
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry)
}
}
注意:getContainerName()方法返回的是当前Flutter页面的名称,并且这个Flutter页面必须已经被注册这样才能正常展示。
使用FlutterBoost发起路由跳转
FlutterBoost.singleton.openPage(
"flutter:FlutterPage", {}, animated: true);
自定义协议
注册Flutter页面
FlutterBoost.singleton.registerPageBuilders({
'xxxPage': (pageName, params, _) => xxxPage(),
});
发起路由,跳转名字为xxxPage的Flutter页面
FlutterBoost.singleton.openPage(
"flutter:xxxPage", {}, animated: true);
跳转Native页面
FlutterBoost.singleton.openPage(
"native:xxxActivity", {}, animated: true);
PageRouter
public class PageRouter {
public static boolean openPageByUrl(Context context, String url) {
return openPageByUrl(context, url, 0);
}
public static boolean openPageByUrl(Context context, String url, int requestCode) {
if (url.startsWith("flutter")) {//跳转Flutter
Intent intent = new Intent(context, FlutterPageActivity.class);
String[] strings = url.split(":");
intent.putExtra("url", strings[1]);
intent.putExtra("requestCode", requestCode);
try {
context.startActivity(intent);
return true;
} catch (Throwable t) {
return false;
}
} else {
//跳转native,根据:后面打的字符串来跳转不同的Activity
}
return false;
}
}
FlutterPageActivity代码也做修改如下:
public class FlutterPageActivity extends BoostFlutterActivity {
String pageRoute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pageRoute = getIntent().getStringExtra("url");
}
@Override
public String getContainerName() {
return pageRoute;
}
@Override
public Map getContainerParams() {
//传参数的时候用
return null;
}
@Override
public void onRegisterPlugins(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
}
这样会使我们代码量减少,如在多个Fragment切换的场景下,遇到闪屏问题可以尝试在onRegisterPlugins方法中添加1如下代码
boostFlutterView.setZOrderOnTop(true)
boostFlutterView.holder.setFormat(PixelFormat.TRANSLUCENT)
参考
https://yq.aliyun.com/articles/693659?spm=a2c4e.11153959.0.0.7d75616br7x3bm
https://www.jianshu.com/p/1397936bbf9b
https://flutter.dev/docs/development/platform-integration/platform-channels