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 默认参数全部失效
        @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)相互跳转、传参