源代码
Flutter版本2.0.3
GitHub源代码
本文目标
Flutter集成到现有的Android应用中,并封装通信框架,简单好用
Flutter集成到现有的Android应用中步骤
- 首先,创建Flutter module
- 为已存在 的Android 应用添加Flutter module 依赖
1.创建Flutter module
在做混合开发之前我们首先要创建一个Flutter module项目.
假如你的Native项目是这样的: xxx/flutter_hybrid/Native项目
cd xxx/flutter_hybrid/
flutter create -t module flutter_module
上面的代码会切换到你的Android项目的上一级目录,并创建一个flutter模块
这是创建的flutter_module项目的目录结构
你会发现它里面包含.android 和 .ios,这两个文件夹是隐藏文件,也是这个flutter_module的宿主工程
- .android - flutter_module的Android宿主工程
- .ios - flutter_module的iOS宿主工程
- lib- flutter_module的Dart部分代码
- pubspec.yaml - flutter_module的项目依赖配置文件
因为宿主工程的存在,我们这个flutter_module在不加额外的配置的情况下是可以独立运行的,通过安装了Flutter与Dart插件的AndroidStudio打开这个flutter_module项目,通过运行按钮是可以直接运行的
2.创建Native Android Studio项目(如果还没有)
新创建的 FlutterHybridAndroid项目和 flutter_module项目放在同一级
打开原生工程,照着下图操作
最低minSdkVersion 为16
minSdkVersion 16
添加Java8编译选项
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
implementation project(':flutter')
//for flutter
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'flutter_module/.android/include_flutter.groovy'// new
))
//可选,主要作用是可以在当前AS的Project下显示flutter_module以方便查看和编写Dart代码
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')
至此,我们已经为我们的Android项目添加了Flutter所必须的依赖,接下来我们来看如何在java中调用Flutter模块
Andoid和Flutter通信框架
准备工作已经完成,这个时候我们要基于MethodChannel实现Flutter与Android通信架构
Android端
1.定义IFlutterBridge顶层接口
/**
* Author: 信仰年轻
* Date: 2021-06-11 12:50
* Email: [email protected]
* Des:以下定义的方法都是Flutter调用Android
*/
interface IFlutterBridge {
/**
* 返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
*/
fun onBack(p: P?)
/**
* 去Android页面或者传递数据到Android这边
*/
fun goToNative(p: P)
/**
* 获取到Android这边的Header信息
*/
fun getHeaderParams(callback: Callback)
}
以上定义的接口里面的方法都是Flutter调用Android的
- fun onBack(p: P?) : 返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
- fun goToNative(p: P?) : 去Android页面或者传递数据到Android这边
- fun getHeaderParams(callback: Callback) : 获取到Android这边的Header信息
2.实现该IFlutterBridge接口
/**
* Author: 信仰年轻
* Date: 2021-06-11 12:54
* Email: [email protected]
* Des: Flutter通信桥梁,实现了MethodChannel.MethodCallHandler和IFlutterBridge接口
*/
class FlutterBridge : MethodChannel.MethodCallHandler, IFlutterBridge {
//因多FlutterEngine后每个FlutterEngine需要单独注册一个MethodChannel,所以用集合将所有的MethodChannel保存起来
private var methodChannels = mutableListOf()
//单例
companion object {
@JvmStatic
var instance: FlutterBridge? = null
private set
@JvmStatic
fun init(flutterEngine: FlutterEngine): FlutterBridge? {
val methodChannel = MethodChannel(flutterEngine.dartExecutor, "FlutterBridge")
if (instance == null) {
FlutterBridge().also {
instance = it
}
}
methodChannel.setMethodCallHandler(instance)
//因多FlutterEngine后每个FlutterEngine需要单独注册一个MethodChannel,所以用集合将所有的MethodChannel保存起来
instance!!.apply {
methodChannels.add(methodChannel)
}
return instance
}
}
///////以下方法为Android调用Flutter/////////////////////////////////////////////////
/**
* Android调用flutter
*/
fun fire(method: String, argument: Any?) {
methodChannels.forEach {
it.invokeMethod(method, argument)
}
}
/**
* Android调用flutter
*/
fun fire(method: String, argument: Any, callback: MethodChannel.Result?) {
methodChannels.forEach {
it.invokeMethod(method, argument, callback)
}
}
///////以下方法为Flutter调用Android/////////////////////////////////////////////////
/**
* flutter调用原生
* 处理来自Dart的方法调用
*/
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"onBack" -> onBack(call.arguments)
"goToNative" -> goToNative(call.arguments)
"getHeaderParams" -> getHeaderParams(result)
else -> result.notImplemented()
}
}
/**
* 返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
*/
override fun onBack(p: Any?) {
if (ActivityManager.instance.getTopActivity(true) is MyFlutterActivity) {
(ActivityManager.instance.getTopActivity(true) as MyFlutterActivity).onBackPressed()
}
}
/**
* 去Android页面或者传递数据到Android这边
*/
override fun goToNative(p: Any?) {
if (p is Map<*, *>) {
val action = p["action"]
if (action == "goToDetail") {
val goodsId = p["goodsId"]
Toast.makeText(AppGlobals.get(),"商品ID="+goodsId,Toast.LENGTH_LONG).show();
} else if (action == "goToLogin") {
Toast.makeText(AppGlobals.get(),"去登录",Toast.LENGTH_LONG).show();
} else {
}
}
}
/**
* 获取到Android这边的Header信息
*/
override fun getHeaderParams(callback: MethodChannel.Result) {
val map = HashMap()
map["boarding-pass"] = "boarding-pass"
map["auth-token"] = "auth-token"
callback.success(map)
}
}
Flutter通信桥梁,实现了MethodChannel.MethodCallHandler和IFlutterBridge接口,而且是个单例,参数中接受FlutterEngine,并创建MethodChannel通信渠道,因为支持多Flutter引擎,多FlutterEngine后每个FlutterEngine需要单独注册一个MethodChannel,所以用集合将所有的MethodChannel保存起来,
然后在override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) 方法中接受Flutter那边的调用,
我们要做的就是Android侧和Flutter侧两边的方法名字保持一致
- onBack(p: Any?) 用于返回到上一页
- goToNative(p: Any?) 中的参数是个map,然后我们可以定义几个key,首先是action,这个key表示要做的动作,举个例子,action为goToDetail表示去详情页,
action为goToLogin表示去登录页面,action定义好之后在定义具体要传递的值,比如说定义goodId这个key(用来接收从Flutter侧传过来的参数) - getHeaderParams(callback: MethodChannel.Result) 是获取Android侧这边的头信息,因为Flutter那边也要进行网络请求
3.创建Flutter引擎管理类
然后我们这个时候需要创建Flutter引擎了,我们这里有两个需求
- 如何让预加载不损失"首页"性能?
- 如何支持多个Flutter引擎并分别加载不同的dart 入口?
/**
* Author: 信仰年轻
* Date: 2021-06-11 13:50
* Email: [email protected]
* Des:Flutter优化提升加载速度,实现秒开Flutter模块
*/
class FlutterCacheManager private constructor() {
/**
* 伴生对象,保持单例
*/
companion object {
//喜欢页面,默认是flutter启动的主入口
const val MODULE_NAME_FAVORITE = "main"
//推荐页面
const val MODULE_NAME_RECOMMEND = "recommend"
@JvmStatic
@get:Synchronized
var instance: FlutterCacheManager? = null
get() {
if (field == null) {
field = FlutterCacheManager()
}
return field
}
private set
}
/**
* 空闲时候预加载Flutter
*/
fun preLoad(context: Context){
//在线程空闲时执行预加载任务
Looper.myQueue().addIdleHandler {
initFlutterEngine(context, MODULE_NAME_FAVORITE)
initFlutterEngine(context, MODULE_NAME_RECOMMEND)
false
}
}
fun hastCached(moduleName: String):Boolean{
return FlutterEngineCache.getInstance().contains(moduleName)
}
/**
* 初始化Flutter
*/
private fun initFlutterEngine(context: Context, moduleName: String): FlutterEngine {
val flutterLoader: FlutterLoader = FlutterInjector.instance().flutterLoader()
//flutter 引擎
val flutterEngine = FlutterEngine(context,flutterLoader, FlutterJNI())
//插件注册要紧跟引擎初始化之后,否则会有在dart中调用插件因为还未初始化完成而导致的时序问题
FlutterBridge.init(flutterEngine)
FImageViewPlugin.registerWith(flutterEngine)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
flutterLoader.findAppBundlePath(),
moduleName
)
)
//存到引擎缓存中
FlutterEngineCache.getInstance().put(moduleName,flutterEngine)
return flutterEngine
}
/**
* 获取缓存的flutterEngine
*/
fun getCachedFlutterEngine(context: Context?, moduleName: String):FlutterEngine{
var flutterEngine = FlutterEngineCache.getInstance()[moduleName]
if(flutterEngine==null && context!=null){
flutterEngine=initFlutterEngine(context,moduleName)
}
return flutterEngine!!
}
/**
* 销毁FlutterEngine
*/
fun destroyCached(moduleName: String){
val map = FlutterEngineCache.getInstance()
if(map.contains(moduleName)){
map[moduleName]?.apply {
destroy()
}
map.remove(moduleName)
}
}
}
首先这个Flutter引擎管理类是如何实现最开始的两个需求呢?
问题1: 如何让预加载不损失"首页"性能?
fun preLoad(context: Context) 该方法是在app启动的时候在Application中去调用的,我们这里是在在线程空闲时才去创建Flutter引擎问题2: 如何支持多个Flutter引擎并分别加载不同的dart 入口?
我们在初始化Flutter引擎的方法中定义一个moduleName的参数,该参数是在创建Flutter引擎的时候去使用,用以区分是哪个引擎,我们在伴生对象中也有定义 flutter引擎启动的两个入口,main和recommend
4.创建FlutterFragment
通信bridge和Flutter引擎管理类都创建好了,这个时候创建FlutterFragment,用以去加载具体的Flutter页面
/**
* Author: 信仰年轻
* Date: 2021-06-11 15:20
* Email: [email protected]
* Des: fragment的基类
*/
public abstract class BaseFragment extends Fragment {
protected View mLayoutView;
@LayoutRes
public abstract int getLayoutId();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mLayoutView = inflater.inflate(getLayoutId(), container, false);
return mLayoutView;
}
public void showToast(String message) {
if (TextUtils.isEmpty(message)) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Author: 信仰年轻
* Date: 2021-06-11 14:32
* Email: [email protected]
* Des:
*/
abstract class FlutterFragment(moduleName: String) : BaseFragment() {
private val flutterEngine: FlutterEngine?
private lateinit var flutterView: FlutterView
private val cached = FlutterCacheManager.instance!!.hastCached(moduleName)
init {
flutterEngine =
FlutterCacheManager.instance!!.getCachedFlutterEngine(AppGlobals.get(), moduleName)
}
override fun getLayoutId(): Int {
return R.layout.fragment_flutter
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// 注册flutter/platform_views 插件以便能够处理native view
if(!cached){
flutterEngine?.platformViewsController?.attach(activity,flutterEngine.renderer,flutterEngine.dartExecutor)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(mLayoutView as ViewGroup).addView(createFlutterView(activity!!))
}
private fun createFlutterView(context: Context): FlutterView {
val flutterTextureView = FlutterTextureView(activity!!)
flutterView = FlutterView(context, flutterTextureView)
return flutterView
}
/**
* 设置标题
*/
fun setTitle(titleStr: String) {
rl_title.visibility = View.VISIBLE
title_line.visibility = View.VISIBLE
title.text = titleStr
}
/**
* 生命周期告知flutter
*/
override fun onStart() {
flutterView.attachToFlutterEngine(flutterEngine!!)
super.onStart()
}
override fun onResume() {
super.onResume()
//for flutter >= v1.17
flutterEngine!!.lifecycleChannel.appIsResumed()
}
override fun onPause() {
super.onPause()
flutterEngine!!.lifecycleChannel.appIsInactive()
}
override fun onStop() {
super.onStop()
flutterEngine!!.lifecycleChannel.appIsPaused()
}
override fun onDetach() {
super.onDetach()
flutterEngine!!.lifecycleChannel.appIsDetached()
}
override fun onDestroy() {
super.onDestroy()
flutterView.detachFromFlutterEngine()
}
}
FlutterFragment很简单,就是接受一个moduleName的参数,然后在类初始化的时候去创建flutter引擎,然后fragment创建完毕后去添加FlutterView,这就把Flutter页面加载进来了
5.具体的fragment使用
/**
* Author: 信仰年轻
* Date: 2021-06-11 15:20
* Email: [email protected]
* Des: 收藏页面
*/
class FavoriteFragment : FlutterFragment(FlutterCacheManager.MODULE_NAME_FAVORITE) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setTitle(getString(R.string.title_favorite))
//点击标题,Android 调用Flutter,然后Flutter返回给Android
title.setOnClickListener {
FlutterBridge.instance!!.fire(
"onRefreshFavorite",
"我是收藏的参数",
object : MethodChannel.Result {
override fun notImplemented() {
Toast.makeText(context, "dart那边未实现", Toast.LENGTH_LONG).show()
}
override fun error(
errorCode: String?,
errorMessage: String?,
errorDetails: Any?
) {
Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
}
override fun success(result: Any?) {
if (result != null) {
Toast.makeText(context, result as String, Toast.LENGTH_LONG).show()
}
}
})
}
}
}
/**
* Author: 信仰年轻
* Date: 2021-06-11 15:20
* Email: [email protected]
* Des: 推荐页面
*/
class RecommendFragment : FlutterFragment(FlutterCacheManager.MODULE_NAME_RECOMMEND) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setTitle(getString(R.string.title_recommend))
title.setOnClickListener {
FlutterBridge.instance!!.fire(
"onRefreshRecommend",
"我是推荐的参数",
object : MethodChannel.Result {
override fun notImplemented() {
Toast.makeText(context, "dart那边未实现", Toast.LENGTH_LONG).show()
}
override fun error(
errorCode: String?,
errorMessage: String?,
errorDetails: Any?
) {
Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
}
override fun success(result: Any?) {
if (result != null) {
Toast.makeText(context, result as String, Toast.LENGTH_LONG).show()
}
}
})
}
}
}
至此,Android这边的类基本创建完毕
Flutter端
1.创建FlutterBridge
import 'package:flutter/services.dart';
class FlutterBridge {
static FlutterBridge _instance = FlutterBridge._();
//该名称还要Android侧的保持一致
MethodChannel _bridge = const MethodChannel("FlutterBridge");
var _listenerMap = {}; //map key:String value:MethodCall方法
var header;
FlutterBridge._() {
_bridge.setMethodCallHandler((MethodCall call) {
String method = call.method;
if (_listenerMap[method] != null) {
return _listenerMap[method](call);
}
return null;
});
}
static FlutterBridge getInstance() {
return _instance;
}
///////以下方法为Android调用Flutter///////////////////////////////////////////////
///注册
void register(String method, Function(MethodCall) callBack) {
_listenerMap[method] = callBack;
}
///解除注册
void unRegister(String method) {
if (_listenerMap.containsKey(method)) {
_listenerMap.remove(method);
}
}
///////以下方法为Flutter调用Android/////////////////////////////////////////////////
///去Android页面或者传递数据到Android这边
void goToNative(Map params) {
_bridge.invokeMethod("goToNative", params);
}
///返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
void onBack(Map params) {
_bridge.invokeMethod("onBack", params);
}
///获取到Android这边的Header信息
Future
很简单,就是创建MethodChannel通信类,然后其渠道参数名字要和Android侧保持一致,在这里都为FlutterBridge,
然后定义register和unRegister方法,这些方法用于Android调用Flutter的时候,Flutter这边来接收信息,当然也支持返回给Android侧
然后一开始我们在Android侧定义了3个方法,goToNative,onBack,getHeaderParams这个时候要在Flutter侧保持一致,这几个方法是Flutter调用Android用的
2.创建具体的Flutter页面
首先是FavoritePage 收藏页面
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_module/bridge/flutter_bridge.dart';
class FavoritePage extends StatefulWidget {
@override
_FavoritePageState createState() => _FavoritePageState();
}
class _FavoritePageState extends State {
@override
bool get wantKeepAlive => true; //保活,切换tab的时候不会重新刷新页面
@override
void initState() {
_registerEvent();
super.initState();
}
var arguments;
///注册事件,登录成功后会发射事件到这里
void _registerEvent() {
var bridge = FlutterBridge.getInstance();
//监听onRefresh消息,登录的时候和点击当前页面标题的时候会发射到这里,然后请求数据进行刷新
bridge.register("onRefreshFavorite", (MethodCall call) {
setState(() {
arguments = call.arguments;
});
return Future.value("Flutter 收到,我是收藏");
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: [
Text(
"收藏---${arguments}",
style: TextStyle(fontSize: 20),
),
MaterialButton(onPressed: (){
var map = {"action":"goToDetail","goodsId":123456};
FlutterBridge.getInstance().goToNative(map);
},child: Text("Flutter 调用 Android"),)
],
),
),
);
}
@override
void dispose() {
super.dispose();
FlutterBridge.getInstance().unRegister("onRefreshFavorite");
}
}
然后是推荐页面
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_module/bridge/flutter_bridge.dart';
class RecommendPage extends StatefulWidget {
@override
_RecommendPageState createState() => _RecommendPageState();
}
class _RecommendPageState extends State {
@override
bool get wantKeepAlive => true; //保活,切换tab的时候不会重新刷新页面
@override
void initState() {
_registerEvent();
super.initState();
}
var arguments;
///注册事件,登录成功后会发射事件到这里
void _registerEvent() {
var bridge = FlutterBridge.getInstance();
//监听onRefresh消息,登录的时候和点击当前页面标题的时候会发射到这里,然后请求数据进行刷新
bridge.register("onRefreshRecommend", (MethodCall call) {
setState(() {
arguments = call.arguments;
});
return Future.value("Flutter 收到,我是推荐");
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: [
Text(
"推荐---${arguments}",
style: TextStyle(fontSize: 20),
),
MaterialButton(onPressed: (){
var map = {"action":"goToLogin"};
FlutterBridge.getInstance().goToNative(map);
},child: Text("Flutter 调用 Android"),)
],
),
),
);
}
@override
void dispose() {
super.dispose();
FlutterBridge.getInstance().unRegister("onRefreshRecommend");
}
}
3.Flutter程序入口
import 'package:flutter/material.dart';
import 'package:flutter_module/page/favorite_page.dart';
import 'package:flutter_module/page/native_page.dart';
import 'package:flutter_module/page/recommend_page.dart';
//至少要有一个入口,而且这下面的man() 和 recommend()函数名字 要和FlutterCacheManager中定义的对应上
void main() => runApp(MyApp(FavoritePage()));
@pragma('vm:entry-point')
void recommend() => runApp(MyApp(RecommendPage()));
class MyApp extends StatelessWidget {
final Widget page;
const MyApp(this.page);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: page,
),
);
}
}
在这里,至少要有一个入口,而且man() 和 recommend()函数名字 要和Android侧FlutterCacheManager中定义的对应上
到这里就可以完全进行通信了,具体可以参考demo