来源: Android 团队 - 余自然android
本文主要解决3个问题:ios
这里,咱们以Flutter Module建立一个Flutter工程(flutter),而后run起来,就能够在.android/Flutter/build/outouts/aar文件夹下面获得这个aar git
这里之因此以Flutter Module模式开发,而不是Flutter Application,就是为了获得这个aar。 Flutter Module模式下自动生成的.android文件夹下,才会有这个Flutter文件夹,Flutter Application则没有。 这样的话,咱们才能够借用Flutter已经有的生成aar的gradle脚本,否则还得本身去写gradle打包脚本,很容易踩到坑里就爬不起来了。github
而后咱们再另开一个窗口,新建一个Android工程(flutter_container),将这个aar复制过去 bash
这里须要注意的一个问题,由于Flutter自己缘由,致使复制出来的aar里面缺乏icudtl.dat文件,须要咱们本身手动复制这个icudtl.dat文件到assets/flutter_shared目录下。app
怎么获得这个icudtl.dat文件呢,很简单,解压Flutter工程生成的默认apk便可获得 less
而后,咱们就须要在宿主Android工程里面,创建接收Flutter的Activity了。这里能够借鉴Flutter工程的.android/app目录,核心就是两个:async
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
FlutterMain.startInitialization(this);
}
}
复制代码
/**
* debug模式原生跳转到flutter界面会出现白屏,release包就不会出现白屏了
*/
public class MainFlutterActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
复制代码
这样之后,咱们就能够跳转这个MainFlutterActivity,实如今Android工程里面进入Flutter工程的默认页面了。ide
上面只是简单集成了Flutter,可是咱们知道,咱们从Android工程里面跳转Flutter,确定是须要选择性的跳转指定页面的,不可能只是简单的跳转默认页面就完了,因此,这里须要用到Flutter的静态路由了。布局
修改Flutter工程的main.dart,定义了两个指定页面的路由:homePage、channelPage
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TestPage(),
//这种方式不能传递参数,主要是方便原生调用
routes: <String, WidgetBuilder> {
'homePage': (BuildContext context) => new HomePage(),
'channelPage': (BuildContext context) => new ChannelPage(),
},
);
}
}
复制代码
而后在宿主Android工程下,添加指定页面的容器Activity,经过Flutter.createView来获取指定页面的View
注意,这里的HomeFlutterActivity只须要继承AppCompatActivity 便可,不须要继承FlutterActivity了。
public class HomeFlutterActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FlutterView homePage = Flutter.createView(
this,
getLifecycle(),
"homePage"
);
setContentView(homePage);
}
}
复制代码
这样之后,咱们就能够跳转这个HomeFlutterActivity,实如今Android工程里面进入Flutter工程的指定页面了。
上面虽然可以跳转指定页面了,可是很显然,有一个很大的问题:不能传递参数。
这是Flutter的静态路由的一个很大的弊端,虽然经过动态路由能够传递参数和接收返回值,可是动态路由无法给原生调用。
Navigator.of(context)
.push<String>(new MaterialPageRoute(builder: (context) {
return new NextPage(params);
})).then((String value) {
setState(() {
params = value;
});
});
复制代码
有一个Flutter的路由库:Fluro,能够实现静态路由传参,例如这样:
传参
var bodyJson = '{"user":1281,"pass":3041}';
router.navigateTo(context, '/home/$bodyJson');
复制代码
接收
Router router = new Router();
void main() {
router.define('/home/:data', handler: new Handler(
handlerFunc: (BuildContext context, Map<String, dynamic> params) {
return new FluroHomePage(params['data'][0]);
}));
runApp(MyApp());
}
复制代码
可是,这种方式在Flutter内部还行,却没法给原生调用,在原生里面经过Flutter.createView的时候,是无法使用Fluro的,只能是默认的路由。
调研了不少方案,最后,没有办法了,只好采用最笨的方法:经过MethodChannel来传递参数。
这里须要注意的是MethodChannel的调用,应该FlutterView已经建立完成,因此须要经过flutterView.post(new Runnable())来执行了,直接执行是不会传参给Flutter的。
原生调用
MethodChannel channel = new MethodChannel(flutterView, CHANNEL);
channel.invokeMethod("invokeFlutterMethod", "hello,flutter", new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
Log.i("flutter","1.原生调用invokeFlutterMethod-success:"+o.toString());
}
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
Log.i("flutter","1.原生调用invokeFlutterMethod-error");
}
@Override
public void notImplemented() {
Log.i("flutter","1.原生调用invokeFlutterMethod-notImplemented");
}
});
复制代码
Flutter执行
platform.setMethodCallHandler((handler) {
Future<String> future=Future((){
switch (handler.method) {
case "invokeFlutterMethod":
String args = handler.arguments;
print("2.Flutter执行invokeFlutterMethod:${args}");
return "this is flutter result";
}
});
return future;
});
复制代码
Flutter调用
print("3.Flutter调用invokeNativeMethod");
int result =
await platform.invokeMethod("invokeNativeMethod", "hello,native");
print("5.收到原生执行结果:${result}");
复制代码
原生执行
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
case "invokeNativeMethod":
String args = (String) call.arguments;
Log.i("flutter","4.原生执行invokeNativeMethod:"+args);
result.success(200);
break;
default:
}
}
});
复制代码
最后贴一下这个传参页面的完整代码吧,主要就是跑了一下:
Android:
public class ChannelFlutterActivity extends AppCompatActivity {
private static final String CHANNEL = "com.ezbuy.flutter";
FlutterView flutterView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_channel);
FrameLayout frFlutter = findViewById(R.id.fr_flutter);
flutterView = getFlutterView("channelPage");
frFlutter.addView(flutterView);
flutterView.post(new Runnable() {
@Override
public void run() {
initMethodChannel(flutterView);
}
});
}
public FlutterView initMethodChannel(FlutterView flutterView) {
MethodChannel channel = new MethodChannel(flutterView, CHANNEL);
//1.原生调用Flutter方法
channel.invokeMethod("invokeFlutterMethod", "hello,flutter", new MethodChannel.Result() {
@Override
public void success(@Nullable Object o) {
Log.i("flutter","1.原生调用invokeFlutterMethod-success:"+o.toString());
}
@Override
public void error(String s, @Nullable String s1, @Nullable Object o) {
Log.i("flutter","1.原生调用invokeFlutterMethod-error");
}
@Override
public void notImplemented() {
Log.i("flutter","1.原生调用invokeFlutterMethod-notImplemented");
}
});
Log.i("flutter","1.原生调用invokeFlutterMethod");
//4.Flutter调用原生方法的监听
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
case "invokeNativeMethod":
String args = (String) call.arguments;
Log.i("flutter","4.原生执行invokeNativeMethod:"+args);
result.success(200);
break;
default:
}
}
});
return flutterView;
}
public FlutterView getFlutterView(String initialRoute) {
return Flutter.createView(
this,
getLifecycle(),
initialRoute
);
}
}
复制代码
Flutter
class ChannelPage extends StatefulWidget {
ChannelPage();
@override
_ChannelPageState createState() => _ChannelPageState();
}
class _ChannelPageState extends State<ChannelPage> {
static const platform = const MethodChannel('com.ezbuy.flutter');
String data;
@override
void initState() {
super.initState();
data ="默认data";
initChannel();
}
@override
Widget build(BuildContext context) {
//必须用Scaffold包裹
return Scaffold(body: new Center(child: new Text(data)));
}
void initChannel() {
platform.setMethodCallHandler((handler) {
Future<String> future=Future((){
switch (handler.method) {
case "invokeFlutterMethod":
String args = handler.arguments;
print("2.Flutter执行invokeFlutterMethod:${args}");
setState(() {
data = "2.Flutter执行invokeFlutterMethod:${args}";
});
invokeNativeMethod();
return "this is flutter result";
}
});
return future;
});
}
void invokeNativeMethod() async {
print("3.Flutter调用invokeNativeMethod");
int result =
await platform.invokeMethod("invokeNativeMethod", "hello,native");
print("5.收到原生执行结果:${result}");
}
}
复制代码
对啦,咱们这节说的是将Flutter以View级别嵌套在一个Android的Activity里面,其实很简单了啊,由于咱们经过Flutter.createView建立出来的View和普通的View并无什么太大的区别,直接addView就能够了,没啥特别操做,好比这个ChannelFlutterActivity,我用的布局文件就是以下所示:
最后的执行效果就是:
个人Flutter工程依赖了shared_preferences插件,致使报错:
缘由是:Flutter工程导出成aar的时候,没有包含插件里面的原生代码。
解决方案有2种,网上说是不用默认的生成aar的方式,用fataar-gradle-plugin来让生成的flutter.aar直接包含嵌套的插件工程的aar,这就须要修改Flutter工程的.android/Flutter/build.gradle文件了。我试过,结果报了循环依赖的错误,就放弃了,你们若是这个方案走通了,欢迎告知我具体步骤。
个人解决方案:这里我采起了一个简单粗暴直接的方案,直接找到插件的aar,将它也复制到宿主Android工程了。这个插件的aar在这里:
复制到这里:
可是这个方案的弊端就是,之后每个插件,你都须要复制一下,后期的维护成本是有点高的。不像fataar是一劳永逸,只有flutter.aar这一份aar的。尤为是后期确定会将aar作成远程依赖,而再也不是直接发复制过去,那维护成本就更高了些。
经过上文能够看到,其实Flutter集成到Android项目仍是挺方便的(除了FlutterView传参有点麻烦)。至于Flutter如何集成到ios项目,我尚未实践过,还须要和ios的同事探索,若是你在集成到ios项目的过程当中,填了哪些坑,有哪些经验总结,欢迎和咱们交流。