Flutter 和 Android原生(Activity、Fragment)相互跳转、传参
前言
本文主要讲解 Flutter 和 Android原生之间,页面相互跳转、传参,
但其中用到了两端相互通信的知识,非常建议先看完这篇 讲解通信的文章:
Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel_flutter eventchannel methodchannel basemessagechan-CSDN博客
当前案例 Flutter SDK版本:3.13.2
Flutter使用多个轻量型引擎,进行混合开发,是从 2.0 开始的,大大的减轻了内存压力;
轻量型引擎开发-官方介绍视频:https://www.youtube.com/watch?v=p6cK_0jp2ag
Flutter和原生端的关系
混合路由栈
如果是纯Flutter开发,只会有一个Flutter引擎,如果是混合开发,原生端 需要为每个从原生端跳转的Flutter页面创建独立的引擎 (也可以单例,但单例写法需要处理一些问题,这个到 FlutterEngineGroup 章节会具体讲解);
比如:
1.0 从 Android_A页面 ==== 跳转到 ==== Flutter_A页面,Android端需要为Flutter_A页面,创建Flutter引擎;
1.1 紧接着,从 Flutter_A页面 ==== 跳转到 ==== Flutter_B页面,就不需要,它俩共用一个引擎;
1.2 每个Flutter引擎都有自己的路由栈,且这个路由栈只能管理Flutter页面;
1.3 使用Flutter提供的 Navigator.pop(context); 方法,可以从 Flutter_B页面 回退到 Flutter_A页面,但无法从 Flutter_A页面 回退到 Android_A,会黑屏,因为Flutter栈里面没有Android页面,可以使用 Navigator.canPop(context); 来检查Flutter路由栈中,是否还有其他路由;
1.4 如果不使用 Navigator.pop(context); 回退方法,使用手机自带的 Back按键 / 左滑屏幕 进行回退,是没有问题的,因为这种回退方式调用的是原生API,Android原生不光提供FlutterView渲染Flutter页面结果,还会创建FlutterActivity和FlutterView进行绑定;
1.5 看到这,大家应该理解,为什么说Flutter只是UI框架了吧;
Android 和 Flutter 跳转
Android 跳转 Flutter
Flutter 跳转 Android
效果图
Android代码
FlutterRouterManager.kt
package com.example.flutter_nav_android.util import android.app.Activity import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivityLaunchConfigs import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngineCache import io.flutter.embedding.engine.dart.DartExecutor class FlutterRouterManager( val targetRoute: String, val mEngineId: String, val mContext: Activity ) { var mEngine: FlutterEngine? = null init { createCachedEngine() } companion object { /** * 获取缓存中的 Flutter引擎 */ @JvmStatic fun getEngineCacheInstance(engineId: String): FlutterEngine? { return FlutterEngineCache.getInstance().get(engineId) } } /** * 1、创建Flutter引擎 * 2、将初始命名路由,修改为目标页面路由 * 3、缓存Flutter引擎 */ fun createCachedEngine(): FlutterEngine { val flutterEngine = FlutterEngine(mContext) // 创建Flutter引擎 // 将初始命名路由,修改为目标页面路由 flutterEngine.navigationChannel.setInitialRoute(targetRoute) // 这一步,是在执行相关Dart文件入口的 main函数,将Flutter页面渲染出结果 // 原生端获取结果,进行最终渲染上屏 flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) // 将加载好的引擎,存储起来 FlutterEngineCache.getInstance().put(mEngineId, flutterEngine) mEngine = flutterEngine return flutterEngine } /** * 根据引擎ID,前往指定的Flutter页面 */ fun push() { // 创建新的引擎(了解即可) // mContext.startActivity( // FlutterActivity // .withNewEngine() // 创建引擎 // .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) // 背景改为透明,不然切换页面时,会闪烁黑色 // .build(mContext)) // 使用缓存好的引擎(推荐) mContext.startActivity( FlutterActivity .withCachedEngine(mEngineId) // 获取缓存好的引擎 .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) // 背景改为透明,不然切换页面时,会闪烁黑色 .build(mContext) ) } /** * 销毁当前Flutter引擎 */ fun destroy() { mEngine?.destroy() } }
PersonalActivity.kt
package com.example.flutter_nav_android.ui.activity import android.graphics.Color import android.os.Bundle import android.text.SpannableString import android.text.Spanned import android.text.style.ForegroundColorSpan import android.view.View import androidx.appcompat.app.AppCompatActivity import com.example.flutter_nav_android.databinding.ActivityPersonalBinding import com.example.flutter_nav_android.util.FlutterRouterManager import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class PersonalActivity : AppCompatActivity(), MethodChannel.MethodCallHandler, View.OnClickListener { private lateinit var bind: ActivityPersonalBinding private lateinit var homeFlutterEngine: FlutterEngine private lateinit var loginRouterManager: FlutterRouterManager private lateinit var loginMethodChannel: MethodChannel private lateinit var homeMethodChannel: MethodChannel private val METHOD_CHANNEL_LOGIN = "com.example.flutter_nav_android/login/method" private val METHOD_CHANNEL_HOME = "com.example.flutter_nav_android/home/method" private val NAV_FLUTTER_LOGIN_NOTICE = "navFlutterLoginNotice" private val POP_NOTICE = "popNotice" private val PERSONAL_POP_NOTICE = "personalPopNotice" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bind = ActivityPersonalBinding.inflate(layoutInflater) setContentView(bind.root) initView() loginRouterManager = FlutterRouterManager("/login", "login_engine", this) // 两端建立通信 loginMethodChannel = MethodChannel(loginRouterManager.mEngine!!.dartExecutor, METHOD_CHANNEL_LOGIN) loginMethodChannel.setMethodCallHandler(this) // 获取 Flutter Home页面的引擎,并且建立通信 homeFlutterEngine = FlutterRouterManager.getEngineCacheInstance("home_engine")!! homeMethodChannel = MethodChannel(homeFlutterEngine.dartExecutor,METHOD_CHANNEL_HOME) } /** * 监听来自 Flutter端 的消息通道 * * call: Android端 接收到 Flutter端 发来的 数据对象 * result:Android端 给 Flutter端 执行回调的接口对象 */ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { val methodName: String = call.method when (methodName) { // 销毁 Flutter Login 页面 POP_NOTICE -> { val age = call.argument("age") getResult(age.toString()) loginRouterManager.mEngine!!.navigationChannel.popRoute() } else -> { result.notImplemented() } } } override fun onClick(v: View?) { when (v) { bind.toFlutter -> { // 前往 Flutter Login 页面 val map: MutableMap = mutableMapOf() map["name"] = "老王" loginMethodChannel.invokeMethod(NAV_FLUTTER_LOGIN_NOTICE,map) loginRouterManager.push() } bind.pop -> { // 销毁 Android Personal 页面 val map: MutableMap = mutableMapOf() map["age"] = 18 homeMethodChannel.invokeMethod(PERSONAL_POP_NOTICE,map) finish() } } } /** * 初始化页面 */ private fun initView() { bind.toFlutter.setOnClickListener(this) bind.pop.setOnClickListener(this) var name = intent.getStringExtra("name") val title = "接收初始化参数:" val msg = title + name val ss = SpannableString(msg) ss.setSpan( ForegroundColorSpan(Color.RED), title.length, msg.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE ) bind.initV.text = ss } /** * 获取上一页的返回参数 */ private fun getResult(age: String) { val title = "接收上一页返回参数:" val msg = title + age val ss = SpannableString(msg) ss.setSpan( ForegroundColorSpan(Color.RED), title.length, msg.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE ) bind.resultV.text = ss } override fun onDestroy() { super.onDestroy() loginRouterManager.destroy() } }
SchoolActivity.kt
package com.example.flutter_nav_android.ui.activity import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.example.flutter_nav_android.databinding.ActivitySchoolBinding import com.example.flutter_nav_android.util.FlutterRouterManager import io.flutter.embedding.android.FlutterFragment import io.flutter.embedding.android.TransparencyMode import io.flutter.embedding.engine.FlutterEngineCache import io.flutter.plugin.common.MethodChannel class SchoolActivity : AppCompatActivity() { private lateinit var bind: ActivitySchoolBinding private lateinit var bookFragment: FlutterFragment private lateinit var studentFragment: FlutterFragment private val METHOD_CHANNEL_BOOK = "com.example.flutter_nav_android/book/method" private val METHOD_CHANNEL_STUDENT = "com.example.flutter_nav_android/student/method" private val NAV_FLUTTER_BOOK_NOTICE = "navFlutterBookNotice" private val NAV_FLUTTER_STUDENT_NOTICE = "navFlutterStudentNotice" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bind = ActivitySchoolBinding.inflate(layoutInflater) setContentView(bind.root) initView() initChannel() } /** * 建立通信 */ private fun initChannel() { val bookEngine = FlutterRouterManager.getEngineCacheInstance("book_engine") val bookChannel = MethodChannel(bookEngine!!.dartExecutor,METHOD_CHANNEL_BOOK) val map: MutableMap = mutableMapOf() map["title"] = "Book" bookChannel.invokeMethod(NAV_FLUTTER_BOOK_NOTICE,map) val studentEngine = FlutterRouterManager.getEngineCacheInstance("student_engine") val studentChannel = MethodChannel(studentEngine!!.dartExecutor,METHOD_CHANNEL_STUDENT) val map2: MutableMap = mutableMapOf() map2["title"] = "Student" studentChannel.invokeMethod(NAV_FLUTTER_STUDENT_NOTICE,map2) } /** * 初始化页面 */ private fun initView() { bookFragment = FlutterFragment.withCachedEngine("book_engine") .transparencyMode(TransparencyMode.transparent) // 背景透明,避免切换页面,出现闪烁 .shouldAttachEngineToActivity(false) // 是否让Flutter控制Activity,true:可以 false:不可以,默认值 true .build() supportFragmentManager .beginTransaction() .add(bind.bookFragment.id, bookFragment) .commit() studentFragment = FlutterFragment.withCachedEngine("student_engine") .transparencyMode(TransparencyMode.transparent) // 背景透明,避免切换页面,出现闪烁 .shouldAttachEngineToActivity(false) // 是否让Flutter控制Activity,true:可以 false:不可以,默认值 true .build() supportFragmentManager .beginTransaction() .add(bind.studentFragment.id, studentFragment) .commit() } // ================================ 这些是固定写法,Flutter需要这些回调 ================================ override fun onPostResume() { super.onPostResume() bookFragment.onPostResume() studentFragment.onPostResume() } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) bookFragment.onNewIntent(intent) studentFragment.onNewIntent(intent) } override fun onBackPressed() { bookFragment.onBackPressed() studentFragment.onBackPressed() } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) bookFragment.onRequestPermissionsResult( requestCode, permissions, grantResults ) studentFragment.onRequestPermissionsResult( requestCode, permissions, grantResults ) } override fun onUserLeaveHint() { bookFragment.onUserLeaveHint() studentFragment.onUserLeaveHint() } override fun onTrimMemory(level: Int) { super.onTrimMemory(level) bookFragment.onTrimMemory(level) studentFragment.onTrimMemory(level) } }
MainActivity.kt
package com.example.flutter_nav_android.ui.activity import android.content.Intent import android.graphics.Color import android.text.SpannableString import android.text.Spanned import android.text.style.ForegroundColorSpan import android.view.View import androidx.appcompat.app.AppCompatActivity import com.example.flutter_nav_android.databinding.ActivityMainBinding import com.example.flutter_nav_android.util.FlutterRouterManager import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class MainActivity : AppCompatActivity(), MethodChannel.MethodCallHandler, View.OnClickListener { private lateinit var bind: ActivityMainBinding private lateinit var homeMethodChannel: MethodChannel private val METHOD_CHANNEL_HOME = "com.example.flutter_nav_android/home/method" private val NAV_ANDROID_PERSONAL_NOTICE = "navAndroidPersonalNotice" private val NAV_FLUTTER_HOME_NOTICE = "navFlutterHomeNotice" private val POP_NOTICE = "popNotice" private lateinit var homeRouterManager: FlutterRouterManager private lateinit var bookRouterManager: FlutterRouterManager private lateinit var studentRouterManager: FlutterRouterManager override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) bind = ActivityMainBinding.inflate(layoutInflater) setContentView(bind.root) bind.toFlutter.setOnClickListener(this) bind.toFlutterFragment.setOnClickListener(this) homeRouterManager = FlutterRouterManager("/home", "home_engine", this) // 两端建立通信 homeMethodChannel = MethodChannel(homeRouterManager.mEngine!!.dartExecutor,METHOD_CHANNEL_HOME) homeMethodChannel.setMethodCallHandler(this) // 这里Fragment案例的 bookRouterManager = FlutterRouterManager("/book", "book_engine", this) studentRouterManager = FlutterRouterManager("/student", "student_engine", this) } /** * 监听来自 Flutter端 的消息通道 * * call: Android端 接收到 Flutter端 发来的 数据对象 * result:Android端 给 Flutter端 执行回调的接口对象 */ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { val methodName: String = call.method when (methodName) { NAV_ANDROID_PERSONAL_NOTICE -> { // Flutter Home 页面 前往 Android Personal 页面 val intent = Intent(this, PersonalActivity::class.java) intent.putExtra("name",call.argument("name")) startActivity(intent) } POP_NOTICE -> { // 销毁 Flutter Home 页面 val age = call.argument("age") getResult(age.toString()) homeRouterManager.mEngine!!.navigationChannel.popRoute() } else -> { result.notImplemented() } } } override fun onClick(v: View?) { when (v) { bind.toFlutter -> { // 前往 Flutter Home 页面 val map: MutableMap = mutableMapOf() map["name"] = "张三" homeMethodChannel.invokeMethod(NAV_FLUTTER_HOME_NOTICE,map) homeRouterManager.push() } bind.toFlutterFragment -> { val intent = Intent(this, SchoolActivity::class.java) startActivity(intent) } } } /** * 获取上一页的返回参数 */ private fun getResult(age: String) { val title = "接收上一页返回参数:" val msg = title + age val ss = SpannableString(msg) ss.setSpan( ForegroundColorSpan(Color.RED), title.length, msg.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE ) bind.resultV.text = ss } override fun onDestroy() { super.onDestroy() homeRouterManager.destroy() bookRouterManager.destroy() studentRouterManager.destroy() } }
activity_personal.xml
activity_school.xml
activity_main.xml
AndroidManifest.xml
Flutter代码
book.dart
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class Book extends StatefulWidget { const Book({super.key}); @override State createState() => _BookState(); } class _BookState extends State { String title = ''; static const String METHOD_CHANNEL_BOOK = 'com.example.flutter_nav_android/book/method'; static const String NAV_FLUTTER_BOOK_NOTICE = 'navFlutterBookNotice'; @override void initState() { super.initState(); MethodChannel bookMethodChannel = const MethodChannel(METHOD_CHANNEL_BOOK); bookMethodChannel.setMethodCallHandler(methodHandler); } /// 监听来自 Android端 的消息通道 /// Android端调用了函数,这个handler函数就会被触发 Future methodHandler(MethodCall call) async { final String methodName = call.method; switch (methodName) { case NAV_FLUTTER_BOOK_NOTICE: // 进入当前页面 { title = call.arguments['title']; setState(() {}); return 0; } default: { return PlatformException( code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述'); // 返回给Android端 } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.amberAccent, body: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, alignment: Alignment.center, child: Text( 'Flutter $title', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.red, fontSize: 20, ), ), ), ); } }
home.dart
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class Home extends StatefulWidget { const Home({super.key}); @override State createState() => _HomeState(); } class _HomeState extends State { late MethodChannel homeMethodChannel; String name = ''; String age = ''; static const String METHOD_CHANNEL_HOME = 'com.example.flutter_nav_android/home/method'; static const String NAV_ANDROID_PERSONAL_NOTICE = 'navAndroidPersonalNotice'; static const String NAV_FLUTTER_HOME_NOTICE = 'navFlutterHomeNotice'; static const String POP_NOTICE = 'popNotice'; static const String PERSONAL_POP_NOTICE = 'personalPopNotice'; @override void initState() { super.initState(); homeMethodChannel = const MethodChannel(METHOD_CHANNEL_HOME); homeMethodChannel.setMethodCallHandler(methodHandler); } /// 监听来自 Android端 的消息通道 /// Android端调用了函数,这个handler函数就会被触发 Future methodHandler(MethodCall call) async { final String methodName = call.method; switch (methodName) { case NAV_FLUTTER_HOME_NOTICE: // 进入当前页面 { name = call.arguments['name']; setState(() {}); return 0; } case PERSONAL_POP_NOTICE: // Android Personal 页面 销毁了 { age = '${call.arguments['age']}'; setState(() {}); return 0; } default: { return PlatformException( code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述'); // 返回给Android端 } } } /// 销毁当前页面 popPage() { if (Navigator.canPop(context)) { // 检查Flutter路由栈中,是否还有其他路由 Navigator.pop(context); } else { Map map = {'age': 12}; homeMethodChannel.invokeMethod(POP_NOTICE, map); } } /// 前往 Android Personal 页面 navAndroidPersonal() { Map map = {'name': '李四'}; homeMethodChannel.invokeMethod(NAV_ANDROID_PERSONAL_NOTICE, map); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, title: const Text( 'Flutter Home', style: TextStyle( fontWeight: FontWeight.w500, fontSize: 26, color: Colors.yellow), )), body: SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(bottom: 16), child: RichText( text: TextSpan( text: '接收初始化参数:', style: const TextStyle(color: Colors.black, fontSize: 20), children: [ TextSpan( text: name, style: const TextStyle(color: Colors.red,fontWeight: FontWeight.bold), ) ])), ), Padding( padding: const EdgeInsets.only(bottom: 16), child: RichText( text: TextSpan( text: '接收上一页返回参数:', style: const TextStyle(color: Colors.black, fontSize: 20), children: [ TextSpan( text: age, style: const TextStyle(color: Colors.red,fontWeight: FontWeight.bold), ) ])), ), Padding( padding: const EdgeInsets.only(bottom: 8), child: ElevatedButton( onPressed: navAndroidPersonal, child: const Text( '前往 Android Personal', style: TextStyle(fontSize: 20), ), ), ), ElevatedButton( onPressed: popPage, child: const Text( '返回 上一页', style: TextStyle(fontSize: 20), ), ), ], ), ), ); } }
login.dart
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class Login extends StatefulWidget { const Login({super.key}); @override State createState() => _LoginState(); } class _LoginState extends State { late MethodChannel loginMethodChannel; String name = ''; final String METHOD_CHANNEL_LOGIN = 'com.example.flutter_nav_android/login/method'; static const String NAV_FLUTTER_LOGIN_NOTICE = 'navFlutterLoginNotice'; final String POP_NOTICE = 'popNotice'; @override void initState() { super.initState(); loginMethodChannel = MethodChannel(METHOD_CHANNEL_LOGIN); loginMethodChannel.setMethodCallHandler(methodHandler); } /// 监听来自 Android端 的消息通道 /// Android端调用了函数,这个handler函数就会被触发 Future methodHandler(MethodCall call) async { final String methodName = call.method; switch (methodName) { case NAV_FLUTTER_LOGIN_NOTICE: // 进入当前页面 { name = call.arguments['name']; setState(() {}); return 0; } default: { return PlatformException(code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述'); // 返回给Android端 } } } /// 销毁当前页面 popPage() { if (Navigator.canPop(context)) { // 检查Flutter路由栈中,是否还有其他路由 Navigator.pop(context); } else { Map map = {'age': 28}; loginMethodChannel.invokeMethod(POP_NOTICE, map); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, title: const Text( 'Flutter Login', style: TextStyle( fontWeight: FontWeight.w500, fontSize: 26, color: Colors.yellow), )), body: SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(bottom: 16), child: RichText( text: TextSpan( text: '接收初始化参数:', style: const TextStyle(color: Colors.black, fontSize: 20), children: [ TextSpan( text: name, style: const TextStyle(color: Colors.red,fontWeight: FontWeight.bold), ) ])), ), ElevatedButton( onPressed: popPage, child: const Text( '返回 上一页', style: TextStyle(fontSize: 20), ), ), ], ), ), ); } }
student.dart
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class Student extends StatefulWidget { const Student({super.key}); @override State createState() => _StudentState(); } class _StudentState extends State { String title = ''; static const String METHOD_CHANNEL_STUDENT = 'com.example.flutter_nav_android/student/method'; static const String NAV_FLUTTER_STUDENT_NOTICE = 'navFlutterStudentNotice'; @override void initState() { super.initState(); MethodChannel bookMethodChannel = const MethodChannel(METHOD_CHANNEL_STUDENT); bookMethodChannel.setMethodCallHandler(methodHandler); } /// 监听来自 Android端 的消息通道 /// Android端调用了函数,这个handler函数就会被触发 Future methodHandler(MethodCall call) async { final String methodName = call.method; switch (methodName) { case NAV_FLUTTER_STUDENT_NOTICE: // 进入当前页面 { title = call.arguments['title']; setState(() {}); return 0; } default: { return PlatformException( code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述'); // 返回给Android端 } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.cyan, body: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, alignment: Alignment.center, child: Text( 'Flutter $title', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, fontSize: 20, ), ), ), ); } }
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_nav_android/book.dart'; import 'package:flutter_nav_android/student.dart'; import 'home.dart'; import 'login.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.deepPurple, ), useMaterial3: true, ), routes: { '/home': (context) => const Home(), '/login': (context) => const Login(), '/book': (context) => const Book(), '/student': (context) => const Student(), }, initialRoute: '/home', ); } }
踩坑
- 一个路由坑,Flutter 加载 根命名路由标识,默认是 " / ",如果你在Flutter端使用了 " / " 作为根路由页面,有时候,从Android 跳转 Flutter页面时,它会将 根路由页面 先push进栈,再push你的目标页面进栈,会多出一个页面;
- 所有我直接不用 " / " 标识,而使用页面对应的路由标识字符串,来指定根路由。
// 原来带坑的写法 // routes: { // '/': (context) => const Home(), // '/login': (context) => const Login(), // '/personal': (context) => const Personal() // }, // initialRoute: '/', // 解决方式的写法 routes: { '/home': (context) => const Home(), '/login': (context) => const Login(), '/personal': (context) => const Personal() }, initialRoute: '/home',
奇技淫巧
在案例中,我使用的是 命名路由 和 Channel方式 进行 页面跳转、传参,下面直接使用引擎进行 页面跳转、传参,但实用价值不高,因为弊端太大;
比如:
- 只提供了List类型,进行传参;
- 目标Flutter页面内的widget,默认参数会全部失效;
- Flutter的Widget默认参数,是由主文件内的 MaterialApp 组件提供的,一个Flutter应用,一般只会使用一个 MaterialApp,它代表返回一个App,如果将目标Flutter页面套上 MaterialApp ,可以解决这个默认参数问题,但会引发 路由问题;
- 不过也有适用场景:让每个Flutter页面完全独立,之间没有任何交互,比如跳转、数据共享等等,这样路由就不需要了,让每个Flutter页面都使用 MaterialApp,当作App和原生交互;
综上所述,大家将这种方式当作扩展知识就好了。
Android代码
class MainActivity : AppCompatActivity(), View.OnClickListener { ... ... override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) ... ... val flutterEngine = FlutterEngine(this) // 定义参数 val dartEntrypointArgs = mutableListOf() dartEntrypointArgs.add("张三") // 这种方式传参数,会导致这个Flutter页面中的Widget默认参数,全部失效, // 这种只有 Dart主入口文件,比如main.dart的 main函数才能接收到参数 // void main(List) {} // flutterEngine.dartExecutor.executeDartEntrypoint( // DartExecutor.DartEntrypoint.createDefault(), // dartEntrypointArgs // ) // 这种可以指定页面接收,但需要在主入口文件里先声明 // void showPersonal(List args) {} flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint( FlutterInjector.instance().flutterLoader().findAppBundlePath(), "showPersonal"), // 找到目标Flutter页面提供的 暴露函数 dartEntrypointArgs) FlutterEngineCache.getInstance().put("personal_engine", flutterEngine) } override fun onClick(v: View?) { ... ... val map: MutableMap = mutableMapOf() map["name"] = "张三" startActivity( FlutterActivity .withCachedEngine("personal_engine") // 获取缓存好的引擎 .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) // 背景改为透明,不然切换页面时,会闪烁黑色 .build(this)) } }
Flutter代码
main.dart(主文件)
import 'package:flutter/material.dart'; import 'package:flutter_nav_android/personal.dart'; void main(List args) { debugPrint('args:$args'); runApp(const MyApp()); } /// 注解说明文档:https://mrale.ph/dartvm/compiler/aot/entry_point_pragma.html /// 注解:表明它可以在 AOT 模式下直接从本机或 VM 代码解析、分配或调用 @pragma("vm:entry-point") void showPersonal(List args) { // 在主文件入口暴露出来 debugPrint('args:$args'); runApp(Personal(title: args.first)); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.deepPurple, ), useMaterial3: true, ), ); } }
personal.dart(目标页面)
import 'package:flutter/material.dart'; class Personal extends StatefulWidget { final String? title; const Personal({super.key,this.title}); @override State createState() => _PersonalState(); } class _PersonalState extends State { /// 在这个页面中,使用的Widget 默认参数全部失效,外层套上 Material组件 也无效 @override Widget build(BuildContext context) { return Container( color: Colors.white, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, alignment: Alignment.center, child: Directionality( textDirection: TextDirection.ltr, child: Text( widget.title ?? '', style: const TextStyle( color: Colors.lightBlue, fontSize: 30, ), )), ); } }
FlutterEngineGroup
- 如果你接受以上缺点,可以使用 FlutterEngineGroup,这个东西就是换成了单例写法,我们自己写单例,需要处理一个问题,比如这个需求:同时获取两个Flutter引擎,这里就要通过判空新建一个Flutter引擎;
- 而 FlutterEngineGroup 已经处理了这个问题,自首个Flutter引擎起,后面多出的Flutter引擎,都是其子类,如果只剩下最后一个,那么这个Flutter引擎将和首个Flutter引擎性能特征相同;
- FlutterEngineGroup官方文档:多个 Flutter 页面或视图 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter
- 从我个人体验上来说,不使用单例,性能也足够了,如果有很多混合的Flutter页面,那当我没说😐。
- 我没找到创建子类Flutter引擎的相关代码,是从注解中发现的
Debug 和 Release
我用真机做测试,发现在Debug模式下,第一次从 Android 跳转 Flutter 会出现黑屏现象,但 打完包 或在 Release模式 就看不出来了;
我们默认的运行模式就是 Debug模式,可以通过 修改IDE运行命令 --release,切换为 Release模式;
源码地址
GitHub - LanSeLianMa/flutter_nav_android: Flutter 和 Android原生(Activity、Fragment)相互跳转、传参