Flutter和Android的混合跳转
1、项目特点
项目是Flutter作为主工程,将Android module或SDK作为模块嵌入到flutter中,与通常所熟悉的Android(或iOS)工程将flutter 为module嵌入到工程中有所不同。
2、业务需求
任意界面间的跳转,不管是flutter页面Android页面亦或是SDK中的页面,需要实现相互间的任意跳转且按照顺序返回(例如进入时A-B-C-D-C-D-B,返回时是B-D-C-D-C-B-A,不考虑启动模式)
3、实现方式探索
我们都知道Android 要打开一个Flutter页面(简称为A页面吧),需要借助与FlutterEngine来开启一个新的任务栈。
startActivity(FlutterActivity.withNewEngine().initialRoute("/second").build(ThreeActivity.this));
这样可以开启在Flutter代码中注册路由为“/second”的A页面,但这个页面加载速度会有些慢,因为每次都需要创建新的FlutterEngine,因此我们可以优化代码为:
public class MyApplication extends FlutterApplication {@Overridepublic void onCreate() {super.onCreate();FlutterEngine flutterEngineInit = new FlutterEngine(this);//flutterEngineInit.// 开始执行Dart代码以预热FlutterEngineflutterEngineInit.getNavigationChannel().setInitialRoute("/second");flutterEngineInit.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());// 缓存FlutterActivity要使用的FlutterEngineFlutterEngineCache.getInstance().put("/second", flutterEngineInit);}
}
自定义Application并在AndroidManifest.xml中注册,在自定义的application的onCreate()方法中将页面提前绑定到创建好的FlutterEngine上,并放入到缓存中
<applicationandroid:label="untitled"android:name=".MyApplication"android:icon="@mipmap/ic_launcher"android:usesCleartextTraffic="true"></application>
这样在打开A页面时就可以,从缓存中获取,加载体验会明显优于使用.withNewEngine()创建并打开A页面
jumpTv.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {startActivity(FlutterActivity.withCachedEngine("/second").build(ThreeActivity.this));}});
注意:需要在AndroidManifest.xml中注册FlutterActivity
<activityandroid:name="io.flutter.embedding.android.FlutterActivity"android:theme="@style/LaunchTheme"android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"android:hardwareAccelerated="true"android:windowSoftInputMode="adjustResize"/>
到这里你可以在Android中打开A页面了。
4、遇到问题
但有一个很大的问题,如果A页面需要与Android交互怎么办?每次打开A页面都是放在一个新的FlutterEngine中,FlutterEngine是相互隔离的,channel无法互通。而我们的项目作为一个flutter项目,APP启动时,已经创建了FlutterEngine,且channel全部建立在这个engine中,上述方法打开的页面是无法使用这些channel;并且flutter B页面中也可以通过navigation.push打卡A页面,那A页面所在的FlutterEngine,即为B页面所在的FlutterEngine。如此情况A页面的在调用channel时会报如下错误:
2023-11-15 15:56:51.504 1528-10332/com.example.untitled E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method jumpToAndroidWebviewPage on channel samples.flutter.jumpto.android)#0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)<asynchronous suspension>
提示我在A页面中调用的channel,没有找到对应的实现。这是因为Android通过FlutterEngine开启的A页面,新的任务栈中没有对应的实现。如何解决这个问题呢?
MethodChannel一般会是这样写:
public class MainActivity extends FlutterActivity implements MethodChannel.MethodCallHandler{@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overridepublic void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {super.configureFlutterEngine(flutterEngine);MethodChannel commonMethodChannel = new MethodChannel(flutterEngine.getDartExecutor(), "samples.flutter.jumpto.android");commonMethodChannel.setMethodCallHandler(this);}@Overridepublic void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {}
}
我注意到在创建channel的时候,接收两个参数一个是messenger,一个是name。name好理解就是channel名称,自己定义。而messenger,我们是通过flutterEngine.getDartExecutor()来获取的一个BinaryMessenger的实现类对象。 也就是说我们只要能拿到每次打开A页面的FlutterEngine对象,那就好解决这个问题了。
我们创建一个channel的管理类,方便我们在每个engine中实现channel(代码有优化空间)
public class ChannelManage implements MethodChannel.MethodCallHandler, EventChannel.StreamHandler{private FlutterEngine flutterEngine;private Context context;EventChannel.EventSink jumpevents;public ChannelManage(FlutterEngine flutterEngine, Context context){this.flutterEngine = flutterEngine;this.context = context;MethodChannel commonMethodChannel = new MethodChannel(flutterEngine.getDartExecutor(), "samples.flutter.jumpto.android");commonMethodChannel.setMethodCallHandler(this);}@Overridepublic void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {if (call.method.equals("jumpToAndroidPage")) {Intent intent=new Intent(context, ContentActivity.class);context.startActivity(intent);result.success("跳转");}else if(call.method.equals("jumpToAndroidSDKPage")){Intent intent=new Intent(context, JumpActivity.class);context.startActivity(intent);result.success("跳转");}else if(call.method.equals("jumpToAndroidWebviewPage")){Intent intent=new Intent(context, ThreeActivity.class);context.startActivity(intent);result.success("跳转");} else {result.notImplemented();}}@Overridepublic void onListen(Object arguments, EventChannel.EventSink events) {events.success("Android");jumpevents=events;}@Overridepublic void onCancel(Object arguments) {}
}
步骤3中的代码改下:
我们在application中已经缓存了A页面和绑定的FlutterEngine,从缓存那取出来,获取对应的messenger设置给ChannelManage,这样A页面的Channel也可以使用啦。
jumpTv.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {new ChannelManage(FlutterEngineCache.getInstance().get("/second"),ThreeActivity.this);startActivity(FlutterActivity.withCachedEngine("/second").build(ThreeActivity.this));}});
但是不要高兴太早,还记得我们要的需求不?任意界面间的跳转,不管是flutter页面Android页面亦或是SDK中的页面。
按照上面的方式假如有一个Android activityB 通过上述方法打开了flutter A页面,A页面又通过Channel新开了activityB页面,activityB又打开了A页面,(这里例子比较极端,现实中A页面和activityB中间可能有其他页面,但不妨碍会出现循环打开的场景)那会是什么样子呢?
童年Andy 2023-11-11 16.19.28
我发现在返回到A页面倒数第二次打开的地方,屏幕上没有任何界面,且不能响应系统的手势放回。这肯定是不行啊,没有按原路返回啊,更大的问题是APP不能用了啊。
这是怎么回事呢?
其实是因为用了FlutterEngineCache,我们每次取到的都是一个FlutterEngine,那么当多次使用时,FlutterEngine就被放在了最后一次出现的位置,原位置将是一个空的engine,那这怎么玩。
再回过头来看下,如果每次都新建engine开启A页面呢?
回顾下上面的代码(中间写的废话太多了)
startActivity(FlutterActivity.withNewEngine().initialRoute("/second").build(ThreeActivity.this));
这肯定没问题了吧,试下。靠,Channel又报错了.......。
那就想法搞到每次的FlutterEngine不就行了吗?
看API....,哎呀,这玩意没有get FlutterEngine的API啊。这怎么玩。
但是如果我写一个MFlutterActivity extends FlutterActivity,然后重写configureFlutterEngine方法不就可以了吗?(MainActivity不就是这样搞的吗)
startActivity(MFlutterActivity.withNewEngine().initialRoute("/second").build(ThreeActivity.this));
public class MFlutterActivity extends FlutterActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overridepublic void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {new ChannelManage(flutterEngine,this);}}
注意:需要在AndroidManifest.xml中注册MFlutterActivity
来一把试试,结果不行。这里的configureFlutterEngine不会回调,不应该啊,MainActivity为什么就可以呢?
想不通啊想不通。翻源代码。
发现如下注释(走了很多弯路,不赘述了,也尝试过反射获取engine对象,我没有成功,小伙伴们可以试试)
/*** Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of* {@code FlutterActivity}.** <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link* #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder} constructed* with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:** <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }*/public NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {this.activityClass = activityClass;}
啥意思?这意思是不是要自己重写withNewEngine方法?干吧,直接把系统的代码粘贴复制一下试试。
/*** Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to* launch a {@code FlutterActivity} that internally creates a new {@link* io.flutter.embedding.engine.FlutterEngine} using the desired Dart entrypoint, initial route,* etc.** @return The engine intent builder.*/@NonNullpublic static NewEngineIntentBuilder withNewEngine() {return new NewEngineIntentBuilder(FlutterActivity.class);}
APP起来,起来吧!!!
童年Andy 2023-11-11 14.30.39
5、总结:
实现flutter和Activity间的任意跳转一定要实现对Channel的统一管理和自定义MFlutterActivity继承FlutterActivity,用MFlutterActivity去执行加载flutter页面的代码开启新的engine,且一定要重写withNewEngine方法,否则Activity的生命周期以及configureFlutterEngine是不会执行的,也就没办法拿到engine,以便为统一管理的Channel设置messenger。
6、原代码
最后上demo代码,自取。
flutter_android_jump: 实现flutter工程下嵌入SDK、Android module后,flutter与Android界面间的任意跳转和通信
相关文章:
Flutter和Android的混合跳转
1、项目特点 项目是Flutter作为主工程,将Android module或SDK作为模块嵌入到flutter中,与通常所熟悉的Android(或iOS)工程将flutter 为module嵌入到工程中有所不同。 2、业务需求 任意界面间的跳转,不管是flutter页…...
CyberRT-共享内存实现
CyberRT共享内存类图 共享内存消息发布 数据用共享内存发布时,首先会创建ShmTransmitter对象,包含两个主要成员segment和notifier,Segment用于创建共享内存(上面绿色部分),Notifer 最终构建ReadableInfo通…...
linux通过串口传输文件
简介 在嵌入式调试过程中,我们经常会使用调试串口来查看Log或者执行指令,其实,调试串口还有另一种功能,就是传输文件,本文说明使用MobaXterm串口工具来传输文件。 环境要求 嵌入式系统需要安装lsz和lrz,…...
uniapp 打包后各静态资源加载失败的问题(背景图,字体等)
原因: 1.部署地址不在域名根目录下 解决办法(推荐办法2): 办法1.如果部署在域名的文件夹下(例如h5), 则运行的基础路径修改为/h5/ 且注意路由模式 办法2.不修改运行的基础路径(还是./), 将代码中涉及背景图(background-image)和字体资源的路径前统一加,如图: tips: 标签内s…...
关于git hooks
Git hooks 是一种在 Git 仓库中触发自定义脚本的机制。这些脚本可以在特定的 Git 操作(如提交、推送、合并等)发生时执行。通过使用 Git hooks,你可以在版本控制的不同阶段自动运行脚本,以执行一些定制化的操作。 在 Git 中&…...
mongodb数据库的常用操作语句
说在前面的话 本文所有的操作示例,都以集合“HistoryTaskBase”为例。 一、查询 1、时间区间 查询“通知时间”介于2019-09-01到2019-10-01之间的数据。 db.getCollection(HistoryTaskBase).find({notifyTime:{$gte:ISODate(2019-09-01T00:00:00.000Z),$lte:ISOD…...
ubuntu安装完qt后发现找不到图标
layout: post # 使用的布局(不需要改) title: Qt启动问题 # 标题 subtitle: ubuntu安装完Qt #副标题 date: 2023-11-18 # 时间 author: BY ThreeStones1029 # 作者 header-img: img/about_bg.jpg #这篇文章标题背景图片 catalog: true # 是否归档 tags: …...
bazel远程构建(Remote Execution) -- Buildfarm部署中的问题
问题1:server报logOverdueOperation和WARNING: removed dispatched operation shard/operations/, worker报WARNING: missing queued operation: shard/operations/等问题,详情如下: Server Log INFO: DispatchedMonitor: Testin…...
论文阅读:MedSegDiff: Medical Image Segmentation with Diffusion Probabilistic Model
论文标题: MedSegDiff: Medical Image Segmentation with Diffusion Probabilistic Model 翻译: MedSegDiff:基于扩散概率模型的医学图像分割 名词解释: 高频分量(高频信号)对应着图像变化剧烈的部分&…...
openssl加解密-干货分享
0.需要包含的头文件和预定义常量 #include <openssl/rand.h>#include <fstream>#include <openssl/aes.h>#include <openssl/rand.h>// 加密密钥和初始化向量(IV)长度#define AES_KEY_LENGTH 32#define AES_IV_LENGTH 16 1.密…...
【考研数据结构代码题7】求一元多项式之和
题目:编写一个算法,求一元多项式之和 考纲:一元多项式的表示与相加 题型:代码填空或算法设计 难度:★★★ 参考代码 typedef struct node{float coef;//系数int exp;//次数struct node *next; }polynode; polynode *…...
python避坑指南(更新中)
os.path.join 避免连续的/,看示例即清楚,最好的避免方法是字符串首末都不要加’/: join用法 用join前面的符号将参数数组里面的字符串连接起来,注意join只有一个参数...
可以远程控制电脑桌面的软件有哪些?
随着电脑办公的普及,人们对于远程控制电脑的需求也越来越大。远程控制电脑技术能够让用户在不同地点的电脑之间进行操作和访问,能够提高工作效率。可以远程控制电脑桌面的软件有哪些? 1. 远程监控电脑软件 需要安装在被控制端电脑ÿ…...
洛谷 P1250 种树
种树 题目背景 一条街的一边有几座房子,因为环保原因居民想要在路边种些树。 题目描述 路边的地区被分割成块,并被编号成 1 , 2 , … , n 1, 2, \ldots,n 1,2,…,n。每个部分为一个单位尺寸大小并最多可种一棵树。 每个居民都想在门前种些树&#…...
java大视频在线预览(支持断点下载)
1.说明 大视频的在线预览,如果不支持断点下载,将无法在苹果手机上播放,同时不支持进度条拖动. 之所以这样,是因为视频文件太大了,通过二进制流向浏览器传输时,整个文件尚未传输完成时,会被浏览器强制关闭流,不再接收,等缓存播放到一定程度时,浏览器会再次向后端请求视频文件,同…...
OpenCV入门10——特征点检测与匹配
文章目录 特征检测的基本概念Harris角点检测Shi-Tomasi角点检测SIFT关键点检测SIFT计算描述子SURF特征检测OBR特征检测暴力特征匹配FLANN特征匹配实战flann特征匹配图像查找图像拼接基础知识图像拼接实战 特征点检测与匹配是计算机视觉中非常重要的内容。不是所有图像操作都是对…...
教育机构拒绝“数据陷阱”,群硕将英孚新一代教学管理系统搬上桌
为什么小机构年年担心招生不够,英孚却令学生家长趋之若鹜? 区别就在教学管理方式。为了更好地管理分布全球的校区、学生和老师,英孚应用了一套教学管理系统,帮助学校管理学员,帮老师智慧排课,帮助家长记录…...
小辰的智慧树(差分+前缀和)
登录—专业IT笔试面试备考平台_牛客网 1.考虑总长度之和不能超过m,2考虑限制每棵树高度不能低于ci,如果用二分最短输能截到的高度,还要另外去判断,是否每棵树mid都能严格大于ci ,这样容易超时,换个角度&…...
Windows如何使用key登录Linux服务器
场景:因为需要回收root管理员权限,禁止root用户远程登录,办公环境只允许普通用户远程登录,且不允许使用密码登录。 一、生成与配置ssh-key 1.使用root管理员权限登录到目标系统。 2.创建一个新的普通用户,和设置密码用…...
k8s无法删除pv,pvc问题
问题: 在k8s里面创建了pv,pvc删除时报错:error: resource(s) were provided, but no name was specified 解决: 正确的删除顺序:1.先删除pod2.再删除pv 3.在删除pvc 删除pv,pvc命令: kubect…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
