范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

iPad大屏ampampampFlutter多引擎适配之路(详细)

  背景
  在电商场景中iPad的大屏拥有比普通手机相比更大的屏幕,对于购物体验而言,如能充分利用好iPad的大屏体验,无疑提高用户购买体验,但一直以来在混合栈应用特别是Flutter混合栈中,在iPad大屏适配和Flutter多引擎适配都是个老大难问题。本文会介绍闲鱼在这iPad适配中的各个疑难点。
  分屏模式
  华为,oppo等厂商折叠屏的方案。界面会在展开和折叠时展示不同的视图样式。oppo称为平行视窗,华为称之为平行视界。苹果虽未推出折叠屏,但在WWDC2019推出了也为iPad的大屏体验的解决方案multi window模式。开发者可以根据自己的需求对进行定制。
  同一款App在不同设备上保持一致操作逻辑,总是让使用者感受愉悦的用户体验。那么,如何让iPad版本拥有折叠屏一样的操作逻辑,让Pad竖屏等同于折叠屏折叠状态,横屏时等同于折叠屏展开左右分屏状态,iPad的大屏适配工作,应运而生。
  分屏模式逻辑
  虽然各个折叠屏厂商对于视图的展示方案各自不同,总体而言共分成两种展示逻辑。
  一种是常用于电商软件,左右分屏商品对比展示逻辑。这里我称为双屏比价模式
  第二种是用于类似iPad设置界面,左固定,右打开。这里因为保留了常见的栈导航栏逻辑,这里我称为双屏导航模式
  下面以连续打开四个视图(ViewController/Activity)为例子。对比一下普通手机设备,折叠屏设备,以及iPad设备在两种模式下的差异。
  双屏比价模式
  最新视图在最右屏,次新视图在最左屏。这种场景适合浏览多个商品宝贝进行比对。
  iPad版本横竖屏逻辑
  竖屏
  竖屏时和普通页面栈展示逻辑相同。新界面push时,覆盖旧界面。
  横屏
  横屏时左右分屏,栈模式:左边永远是次最新,右边永远是最新界面
  push逻辑动画
  pop逻辑
  分屏导航模式
  左屏固定不变,最新、次新视图堆叠在右屏。这种场景适合左边是列表页,在右屏打开多个商品宝贝。其目的是让最新的界面在都在右边打开
  竖屏
  界面:全屏 栈模式:新界面push时,覆盖旧界面。
  横屏时左右分屏,栈模式:左边永远是次最新,右边永远是最新界面
  push逻辑动画
  pop逻辑
  需求
  1. 支持左右分屏
  2. 保留基于栈(UINavigationController)的展示逻辑
  3. 业务改造成本越小越好
  技术方案
  闲鱼内部核心业务都使用flutter进行搭建,涉及由集团中台提供基础业务均是原生ViewController,再有部分业务使用H5进行搭建。这三大部分的业务都需要进行兼容改造
  1. UINavigationController改造
  基于UINavigationController的ContainerViewController永远都把新的ViewController覆盖老的ViewController。无法做到上述说的左右分屏。我的做法是基于UINavigationController创建子类,重写push/pop的ViewCotroller整个排版逻辑。这样让整个应用原来的push/pop逻辑不用修改。只需要在iPad使用不一样的新类NavigationControllerForiPad就能完美的迁移。
  自定义ContainerViewCotroller
  iOS中专门用于控制ViewController的控制类都统称为ContainerViewCotroller。如 UINavigationController, UITabBarController, and UIPageViewController
  最主要的工作是重写一个ContainerViewCotroller,自定定义新ViewCotroller被push进来、旧ViewCotroller被pop移除后,后如何排版,以及其中动画如何展示等问题。
  以push新ViewCotroller为例子
  -(void)pushViewController:(UIViewController*)newVC animated:(BOOL)animated{
  UIViewController* oldVC = 获得最倒数第一个ViewController
  [self pushOldViewController:oldVC newViewController: newVC animated: animated]
  }
  - (void)pushOldViewController:(UIViewController*)oldVC
  newViewController:(UIViewController*)newVC
  animated:(BOOL)animated {
  ...
  [oldVC beginAppearanceTransition:NO animated:animated];
  //1.将新的Viewcontroller.view加入到根viewcontroller.view
  WrapperView* newWrapperView =
  [self appendWrapperViewWithViewController:newVC
  wrapperFrame:[self newViewControllerBeginFrame]
  toView:self.view
  animated:animated];
  newVC.view.frame = [self childViewFrame];
  newWrapperView.delegate = self;
  //2. 把新的Viewcontroller添加为子Viewcontroller
  [self addChildViewController:newVC];
  //3. 进场动画
  [UIView animateWithDuration:0.35
  animations:^{
  newWrapperView.frame = [super newViewControllerEndFrame];
  ;
  }
  completion:^(BOOL finished) {
  //4. 进场动画结束
  [oldVC endAppearanceTransition];
  [newVC didMoveToParentViewController:self];
  }];
  }
  退场- (able UIViewController*)popViewControllerAnimated:(BOOL)animated {
  //1.移除倒数第一个Viewcontroller
  [lastViewController willMoveToParentViewController:nil];
  [lastViewController beginAppearanceTransition:NO animated:animated];
  //2.倒数第二个Viewcontroller即将显示
  [secondToLastViewController beginAppearanceTransition:YES animated:animated];
  //3.退场动画
  [UIView animateWithDuration:0.35
  animations:^{
  lastWrapper.frame = [self newViewStartFrame];
  secondToLastWrapper.frame = [self rightViewFrame];
  }
  completion:^(BOOL finished) {
  //4.移除旧Viewcontroller.view
  [lastWrapper removeFromSuperview];
  [lastViewController endAppearanceTransition];
  [lastViewController removeFromParentViewController];
  // 5.倒数第二个ViewController显示
  [secondToLastViewController endAppearanceTransition];
  }];
  }
  这部分功能很核心的工作是,在进场/退场后,依次调用相关函数,这些函数Viewcontroller的生命周期事件至关重要。
  更详细接口文档:https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
  https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
  2.原生界面改造
  在横竖屏切换时,会导致ViewController.view触发重绘。使用原生进行布局的页面需要保持所有的view都是相对布局。这里推荐使用autolayout方式,这样既一套代码能完美的兼容iPhone/iPad
  具体的工作有:
  • 弃用[UIScreen main].bound.size,改为ViewController.view.size作为当前布局宽高 仅仅支持单屏(iPhone)时,[UIScreen main].bound.size = ViewController.view.size但在iPad多屏横屏时[UIScreen main].bound.size ≠o ViewController.view.size
  • 将绝对布局改为相对布局,即使用autolayout
  • 感知横竖屏的切换事件
  3. H5界面适配
  H5本身已经有非常好的适配不同屏幕大小的特性,但可能在一些特殊的场景上不排除因为历史原因专为iOS适配hardcode
  • 感知横竖屏的切换事件
  4. Flutter业务技术改造界面技术改造
  flutter业务因为原生就本身支持不同屏幕大小的适配。FlutterViewController.view会在界面重排时重新触发界面的重绘。从dart层布局层面代码无需特别调整。
  横竖屏的切换时有不同的展示逻辑,正常监听didChangeMetrics即可 @override
  void didChangeMetrics {
  setState( { _lastSize = WidgetsBinding.instance.window.physicalSize; });
  }
  引擎层技术改造
  这是本次iPad适配中的重头戏。因为闲鱼中大部分核心基础业务都是基于Flutter进行开发。让多个Flutter界面顺畅运行在iPad,绕不过的问题。
  先回到闲鱼的终端路由架构。
  闲鱼路由系统是基于flutter_boost进行搭建。而flutter_boost的原理则是多个界面共享同一个引擎,这一实现的有几个好处
  • 优化内存,同一时刻Flutter引擎只提供给最前的视图即可
  • 多个Flutter视图因为运行在同一个Dart Isolate的缘故,数据能互相访问(单例等)
  • 绑定到Isolate的Flutter插件、messagechannel都只有一份
  但正是这些"优点",同一时刻只能存在一个Flutter引擎,导致两个Flutter视图左右无法同屏。
  那是否可以给每个视图(ViewController/Activity)创建一个flutter引擎?这种方案也出现如下问题
  业务问题
  • 每个Flutter引擎会创建两份原生plugin实例,如原生plugin的实现是基于单例则会出现紊乱
  • 创建多个Flutter引擎增加非常多的性能开销
  性能问题
  如不复用Flutter引擎,除此外还有其他场景也会导致内存激增
  • 混合栈中存在多个flutter界面
  • 原生tab中存在多个Flutter界面
  • 多个Flutter views同时存在,比如列表中每个cell都是Flutterview
  那官方Flutter引擎是否类的方案?
  Flutter官方在2020年也推出了轻量级多引擎的技术方案: http://flutter.dev/go/multiple-engines
  轻量级多引擎方案使用的场景
  • 混合栈应用中多个flutter界面
  • 原生tab中存在多个Flutter界面
  • 多个Flutter views同时存在,比如列表中每个cell都是Flutterview
  方案的初衷是在底层对某些如下的资源/类进行共享:
  1. Threads Host(线程)
  2. Skia Contexts (Skia绘制上下文)
  3. Graphics Contexts (图像绘制上下文本)
  4. Image Caches (图片缓存)
  5. Isolate Groups (Dart的isolate)
  6. Fonts (字体)
  这个方案中需要注意点是,两个引擎中的isolate是不共享的。
  这种方案正因为各个界面/view中的因为不共享isolate的缘故。引擎之间的变量是无法共享的。随之绑定到isolate的对象也存在有多份。
  为能和原来闲鱼的整个逻辑兼容,使业务平滑无感迁移,多引擎共享isolate方案势在必行。
  基于共享isolate的Flutter多引擎方案
  修改后C++侧代码
  引擎A和引擎B有各自的 shell,Engine,Window对象实例,且不同的引擎使用application_id 来标记。 他们之间共享isolate。
  Application
  这里我们称创建出来的多个引擎称为不同Application,每个引擎用不同ApplicationId标识。
  不同Application使用自己的渲染管线,这样就达到了不同引擎的渲染流程既互不影响。 但有因为运行在同一个isolate下,业务代码自己的单例、数据等能互相访问。
  c++层支持后,在调用dart侧的入口函数时,带上application_id,传递到Dart侧的入口main函数bool DartIsolate::InvokeEntryPointInSharedIsolate(
  std::unique_ptr platform_configuration,
  std::optional library_name,
  std::optional entrypoint,
  const std::vector& args) {
  tonic::DartState::Scope scope(this);
  int64_t application_id = platform_configuration->application_id;
  ....
  if (!InvokeMainEntrypoint(user_entrypoint_function, entrypoint_args,
  application_id)) {
  return false;
  }
  return true;
  }
  dart代码单例问题
  在原来Fltuter引擎framework层中存在多个单例如window,Bindings,PlatformDispatcher,如何解决不同Application访问自己的单例?
  使用Application.current获取当前application后再访问具体单例对象。以window为例SingletonFlutterWindow get window => Application.current.get(
  SingletonFlutterWindow,
  => SingletonFlutterWindow._(0, PlatformDispatcher.instance)
  );
  再dart层的渲染计算后,最终还会将渲染树数据调用回到的c层。在回调到c函数时,也需要带上applicationId// platform_configuration.cc
  void Render(Dart_NativeArguments args) {
  UIDartState::ThrowIfUIOperationsProhibited;
  Dart_Handle exception = ptr;
  int64_t application_id =
  tonic::DartConverter::FromArguments(args, 1, exception);
  Scene* scene =
  tonic::DartConverter::FromArguments(args, 2, exception);
  if (exception) {
  Dart_ThrowException(exception);
  return;
  }
  UIDartState::Current
  ->platform_configuration(application_id)
  ->client
  ->Render(scene);
  }
  自此从flutter的Dart函数入口,到dart函数内部调用渲染流程,最后调用回到c++层,都有applicatinId去标识不同的引擎。
  这样就做到多个引擎之间既能渲染相互隔离,但内部又能访问的结果。
  总结
  最后看下完成后的效果视频截图
  横竖屏切换
  分屏导航模式
  分屏比价模式
  Flutter多引擎特性除了能应用到iPad场景外,还能应用到Android折叠屏场景。目前这部分的工作也在有序过程中。自定义NavigationViewController以及Flutter引擎的修改工作,也会在性能稳定后开源到社区共建。
  闲鱼iPad版发布工作还在紧张灰度中,欢迎大家继续关注。
  引用资料supporting multiple windows on ipad https://developer.apple.com/documentation/uikit/uiscenedelegate/supporting_multiple_windows_on_ipad
  Light weight Flutter engine :https://docs.google.com/document/d/1NwiZPWHd1te46eP2GWwIezDV9CdMQkODAMuF5kWdtLw/edit# https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
  https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html

产后风寒该如何调理?请注意保暖,现在是冬天了,虽然是出了月子,还是要带帽子的。如果不舒服可以去妇幼保健院看看的,不要硬抗。产后50天就喝点药膳汤调理身体。具体咨询一下中药师。我们以前就煲羊肉猪脚汤来喝国足剩下的4轮比赛换上斯科拉里会全胜进入附加赛吗?导读4轮全胜12分,总计17分,这个前提必须建立在澳大利亚连输日本沙特的前提下,同时斯科拉里挤掉李铁的帅位,简单展开一下。后4轮比赛中日澳的出线形势分析12强赛中国队最后4轮比赛顺如果拿林丹和杨阳赵剑华比,林丹胜在哪?林丹是位天材球员,特点预判性好步法灵活执行战术坚决体力分配均衡便于调动对方,防守严密进攻胜赵剑华,防守胜杨阳,有比较吗?羽毛球的进化随着运动员训练的系统和对身体素质的提高已经不是以死刑犯进了监狱会受到怎样的对待?一般情况下,死刑犯是没有机会进入监狱的,法院判决后基本上在看守所就走完程序了。只有被判死刑缓期二年执行的犯人,才可能被送入监狱服刑。真正判决死刑的,在看守所会和其它未决犯羁押在一起河北唐山五年内可以发展成什么样?五年内它将是中国东部的卫星城,是中国钢铁制造业的新兴基地,铁路,公路,轻工业,对外贸易,车辆制造,电气化完整的工业新城,将与天津争高低。是中国五年计划的新现代智能城,展现高科技智能你到饭店吃饭时,被坑得最惨的一次是什么时候?我被坑的最惨的一次是2012年秋,在青岛石老人公园附近的一个中等饭店,名字记不清楚了,好像叫什么海鲜馆。我们一家三口,初到青岛,晚上没事了,心想来一趟青岛,就尝尝海鲜吧。我点了两只常喝酸奶好吗?如今养生时代大家越来越注意自己的健康了,酸奶作为公认的最最健康的饮品之一,在减肥的道路中的各位饿了馋了拿出一杯酸奶下肚,这个时候感觉离瘦瘦美美的自己又近了一步。然而,有文章提出一杯为什么电信的上门只装了光猫,没有装路由器还能上无线网看电视?为什么电信的上门只装了光猫,没有装路由器还能上无线网看电视?不仅仅只是电信的上门只装光猫,中国的三大运营商的移动联通电信几乎都是一样的。这些运营商们自己不可以生产光猫和路由器,它们我有几瓶1994年的明光特曲,当时时价是14元一瓶,现在市值多少?不要以为一些老酒。家里存放一些年限就非常值钱这观念是错误的,否则大大小小无数酒厂都会这么弄酿点纯粮酒存个十几年卖天价,值钱的酒一般是那些非常具有品牌号召力比如茅台,而且独一无二的那为何如今人们宁愿买自行车也不骑共享单车了?共享单车虽然方便,可是当你骑过一次山地车或者公路赛,就再也不想骑共享单车了。为撒子呢?,因为山地车或者公路赛感受跟共享单车不是一个层面。轻便,灵活,速度快山地车或者公路赛要比共享好铃木吉姆尼的用车感受如何?对于吉姆尼的试驾,我期待已久。因为在很早以前,我在网上看到过一段吉姆尼成功解救陷坑悍马的视频,这很让人好奇一辆小车怎么会有如此强悍的性能?而这次,我通过一天的体验终于找到了答案。外
生娃最厉害的女性76年生69个娃!没一个娃单独出生,都是结伴来你后悔过生孩子吗?可能不少人的回复都是不后悔孩子就是天使,治愈了我。但同样地,也有少部分可能会后悔过这个决定网友A生完孩子后十分后悔,他让我完全没有了自己的时间,整日腰酸背痛胸下垂公主永不外嫁法律偏袒妇女古埃及女性社会地位为何如此之高?在中国漫长的历史中,有绝大一部时期的社会主流思想就是男尊女卑,甚至到了现代,许多女性依然遭受着这种思想的迫害。不仅是中国,历史上,世界各地的女性都长期处于历史和政治舞台的边缘,成为治理网络诈骗,短视频平台有新招新京报专栏面对层出不穷的网络诈骗新花招,互联网平台治理需要新方式新方法。抖音安全产品经理刘欢介绍抖音小安如何提示用户。资料图片文竹梦眼下,正是春季招聘重要阶段,大量求职者上网求职。一些机构和生下畸形儿,医院也得管?医方在对产妇进行产前检查时未尽到与当前医疗水平相应的诊疗义务,对应当发现的先天性疾患未能发现,导致缺陷儿出生,损害了孕父母生育知情权和优生优育选择权,应当承担与其过错相应的赔偿责任接吻神器未来成人用品市场的发展方向?随着社会的不断进步和人们生活水平的提高,成人用品市场也在不断地发展和壮大。在这个市场中,接吻神器成为了备受关注的一种产品。那么,什么是接吻神器?它又将成为成人用品市场的下一个发展方一味的付出和忍让,真的能养育出懂得感恩的孩子吗?妈妈在家什么也不干,好懒。你的宝贝女儿宝贝儿子有没有对你说过这样的话?你小时候有没有对父母说过这样的话?今天早晨,收拾完1岁女儿的尿片和衣服,趁着上厕所的时间刷了下抖音,推送给我的2023羊了个羊2。23怎么快速过关羊了个羊2。23快速通关攻略羊了个羊2。23怎么过?2。23通关要注意什么呢?下面小编为大家带来羊了个羊2。23通关攻略,一起看看吧。2。23通关攻略今天主要消除的地方在红色区,里面是一个三角形大别墅,一个T2023年春季赛AG有所回暖,XYG两场被零封了,要埋了呀,愁2023年春季赛已进行了两周的比赛,作为两个人气战队AG和XYG都被对手零封,粉丝们的心都碎了一地。AG第二周赢得了RNGTTG,打RNG第一场现场粉丝又要退票的心情了吧,纯菜,打电竞选手生命周期有多长?DK选手ShowMaker表示已对游戏失去热爱头条创作挑战赛就在最近的LCK春季赛上,DK以02的成绩输给了LSB,让喜欢这个战队的粉丝有点大呼意外,DK这只战队在国内还是有一定的人缘的,特别是秀兵的梗在一段时间也是刷的飞起,白夜极光维多利亚三觉需要材料大全白夜极光维多利亚觉醒需要材料有哪些?白夜极光维多利亚怎么快速觉醒?白夜极光里面的话一些玩家呢都在玩维多利亚的呢,那么要想维多利亚变得更强的话那么就需要去觉醒维多利亚的呢,那么就需要呆萌小绿人,亮相乐高Ideas社区,启动坎巴拉太空计划引言不知道屏幕前的各位,小时候爱不爱吃喜x郎?反正弗莱德长大了没能当成宇航员(苦笑),这也是为什么弗莱德十分喜欢乐高CITY系列的航天套组。同时有这样一款游戏,支持玩家在一个平行世