Flutter 1.17.0版本 碰到的几个引擎的crash

前段时间1.17.0发布,公司的APP就很快就升级了。
不出意外还是有很多的crash,😭。

后来我们发了一个HotPatch,替换了AppDelegate的applicationWillEnterForeground方法,方法中调用FlutterEngine的setViewController方法来确保FlutterEngine始终attach一个FlutterVC。(这是补丁方案,后续我们还是要自定义引擎)

注:APP的FlutterVC是共享引擎的,我们有自己的混合栈路由


写这篇文章记录沉淀一下:

  1. crash堆栈还原;
  2. 引擎定制流程;
  3. 复现crash,debug源码;
  4. 阅读源码,修复crash,编译和发布引擎产物;


    阅读flutter引擎源码,主要是为了及时解决线上的一些crash,不用等着官方发布hotfix版本
    我页是刚刚开始去阅读,理解的比较浅,看到的朋友见谅,欢迎吐槽。
    大家有好的文章和自己的理解给我留言呀,哈哈哈

一,Crash堆栈还原

先找到引擎的Version: 在flutter sdk目录中,flutter/bin/internal/engine.version ,如:540786dd51f112885a89792d678296b95e6622e5
再去下载符号表文件,符号表下载地址,搜索540786dd51f112885a89792d678296b95e6622e5
最后,bugly拿到原始堆栈(注意是原始堆栈)后结合符号表,使用atos -o还原出engine堆栈

堆栈解析脚本,本脚本是考虑到bugly上拷贝过来是有行号的,脚本使用方法:
1, 拿到bugly上的原始堆栈,保存到文件;

原始堆栈

2, 原始堆栈文件、符号表、脚本放在同一目录,执行(./flutter_crash_analysis.sh Flutter.dSYM raw_crash arm64),一般都是arm64;

3, 终端输出:
engine堆栈


二,引擎下载和编译

参考地址:Flutter Engine定制流程Flutter-Engine-编译指北

注意几个点:

  1. 写.gclient文件时候,url直接带上指定的提交节点,就是上面说的engine.version(git提交的sha),
solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "git@github.com:flutter/engine.git@540786dd51f112885a89792d678296b95e6622e5",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]

这样就不需要二次gclient sync。

  1. ShadowsocksX开全局模式,下载会很快。

  2. 代理设置全了

git config --global http.proxy http://127.0.0.1:1087

git config --global https.proxy [http://127.0.0.1:1087](http://127.0.0.1:1087)

export ALL_PROXY=socks5://127.0.0.1:1086

export no_proxy="localhost,127.0.0.1,内网git域名(比如,gitlab.vdian.net)"

export http_proxy=http://127.0.0.1:1087

export https_proxy=[http://127.0.0.1:1087](http://127.0.0.1:1087)

  1. 如果电脑年代比较久远,ninja 带上参数 -j 2,并行2个任务防止电脑卡死,这样在编译漫长的时间里你还能干其他活。
    🌰:ninja -C out/ios_debug_unopt -j 2 && ninja -C out/host_debug_unopt -j 2
  2. 编译后的引擎产物是分模式和CPU架构的,需要使用lipo等命令来做产物归档与符号表备份区长有个脚本来做这个,但是我跑起来有错误没找到解决办法😿,所以我自己写了个脚本flutter_engine_thin(只有iOS)

三,Debug引擎源码和复现修复Crash

1,Debug引擎源码

我一般先把所有mode和arch的组合都编译一遍,然后使用的是ios_debug_unopt和host_debug_unopt,把products.xcodeproj拖到Pods中就可以开始debug了。



四,Crash逐个修复

1,flutter::Rasterizer::Setup surface_空指针

Crash堆栈:

#9 Thread
0 auto fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>::operator()<>() const (in Flutter) (make_copyable.h:24)
1 auto fml::internal::CopyableLambda<flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >)::$_8>::operator()<>() const (in Flutter) (make_copyable.h:24)
2 fml::MessageLoopImpl::FlushTasks(fml::FlushType) (in Flutter) (message_loop_impl.cc:129)
3 fml::MessageLoopDarwin::OnTimerFire(__CFRunLoopTimer*, fml::MessageLoopDarwin*) (in Flutter) (message_loop_darwin.mm:76)
9 fml::MessageLoopDarwin::Run() (in Flutter) (message_loop_darwin.mm:47)
10 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, fml::Thread::Thread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0> >(void*) (in Flutter) (thread:352)

#0 Thread
3 fml::AutoResetWaitableEvent::Wait() (in Flutter) (waitable_event.cc:70)
4 flutter::Shell::OnPlatformViewCreated(std::__1::unique_ptr<flutter::Surface, std::__1::default_delete<flutter::Surface> >) (in Flutter) (shell.cc:678)
5 -[FlutterViewController surfaceUpdated:] (in Flutter) (FlutterViewController.mm:553)
6 -[FlutterViewController applicationBecameActive:] (in Flutter) (FlutterViewController.mm:673)

复现步骤:(注,不进行前后台切换也会发生crash,但不是必现,前后台切换crash必现)
crash1.png

Crash的定位代码,只贴了需要的代码:

//shell.cc,
void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
...
  fml::AutoResetWaitableEvent latch;
  auto raster_task =
      fml::MakeCopyable([& waiting_for_first_frame = waiting_for_first_frame_,
                         rasterizer = rasterizer_->GetWeakPtr(),  //
                         surface = std::move(surface),            //
                         &latch]() mutable {
        if (rasterizer) {
          // crash在这个方法
          rasterizer->Setup(std::move(surface));
        }

        waiting_for_first_frame.store(true);
        latch.Signal();
      });
...
}

//rasterizer.cc
void Rasterizer::Setup(std::unique_ptr<Surface> surface) {
  surface_ = std::move(surface);
...
  // surface_是空的,导致下面Get方法crash
  if (surface_->GetExternalViewEmbedder()) {
...
  }
}

分析一下原因:

先大致了解重要的几个类和类之间的关系,我画了一个草草的类图,简单说明涉及的类和属性

类关系图.png

flutter::Shell, 头文件的官方注释说明了Shell的重要性,1.Perhaps the single most important class in the Flutter engine repository...,2.The shell is the central nervous system of the Flutter application... shell持有很多指针,几乎可以干所有活,比如有platform、UI、GPU和IO等任务。
flutter::PlatformViewIOS, 在shell和平台视图中间,运行在plantform线程(主线程)。
flutter::IOSSurface, 没细看,感觉是面向平台视图的图层,根据+[FlutterView layerClass],有三种选择IOSSurfaceGL、IOSSurfaceMetal、IOSSurfaceSoftware,一般真机是IOSSurfaceMetal,模拟器是IOSSurfaceSoftware。
flutter::Rasterizer, 运行在GPU线程上,拥有当前活动的屏幕渲染Surface的实例,展现Engine对象(被shell持有,运行在UI线程)提交给它的layer trees。
flutter::Surface, 面向的是GPU,对应IOSSurface,也有GPUSurfaceMetal、GPUSurfaceSoftware、GPUSurfaceGL。

一般我们都是共享引擎的,所以FlutterEngine只有一个实例,FlutterViewController可能会有很多,所以在APP生命周期内:
    FlutterEngine、Shell、Rasterizer和PlatformViewIOS都只有一个实例;
    FlutterViewController则是push之前new一个,pop之后dealloc;

我自己阅读了源码,简单的理了一下FlutterVC和FlutterEngine绑定的过程(堆栈方法顺序排列):

  1. 初始化和运行引擎流程
+[FlutterEngine initWithName:]
    -[FlutterEngine runWithEntrypoint:]
    -[FlutterEngine createShell: libraryURI:]
        /**
         * 创建_shell(Shell),可以看看shell.h关于shell类的介绍
         **/ 
        flutter::Shell::Create
        flutter::Shell::CreateShellOnPlatformThread
            /**
             * 创建platform_view_(PlatformViewIOS),看platform_view.h的类注释
             **/
            flutter::PlatformViewIOS::PlatformViewIOS
            /**
             * 创建rasterizer_(Rasterizer)
             **/
            flutter::Rasterizer::Rasterizer
        flutter::Shell::Setup
    -[FlutterEngine launchEngine:libraryURI:]
        flutter::Shell::RunEngine
  1. 按顺序push FlutterVC A,NativeVC B,FlutterVC C。下面是push FlutterVC的流程,会根据当前VC的view创建ios_surface_(IOSSurface)和surface(Surface),并绑定到PlatformViewIOS和Rasterizer。
-[FlutterViewController initWithEngine:nibName:bundle:]
    -[FlutterView initWithDelegate:opaque:]
    -[FlutterEngine setViewController:]
        flutter::PlatformViewIOS::SetOwnerViewController
    -[FlutterEngine maybeSetupPlatformViewChannels]
        flutter::PlatformViewIOS::SetTextInputPlugin
-[FlutterViewController viewDidLoad]
    -[FlutterEngine attachView]
        flutter::PlatformViewIOS::attachView
            /**
             * 创建了ios_surface_(IOSSurface)
             **/
            -[FlutterView createSurface:]
                flutter::IOSSurface::Create
-[FlutterViewController viewWillAppear:]
-[FlutterViewController surfaceUpdated:]
    flutter::PlatformView::NotifyCreated
        /**
         * 创建了surface(Surface),通过ios_surface_(IOSSurface)的CreateGPUSurface创建
         **/
        flutter::PlatformViewIOS::CreateRenderingSurface
            flutter::IOSSurfaceMetal::CreateGPUSurface
        flutter::Shell::OnPlatformViewCreated
        /**
         * 将surface(Surface)绑定到rasterizer_(Rasterizer),
         **/
        flutter::Rasterizer::Setup
  1. 现在pop Flutter C,Flutter C对应的ios_surface_(IOSSurface)和surface(Surface)被回收,engine对VC是弱引用,所以pop之后引擎就没有绑定的VC了,PlatformViewIOS的ios_surface_和Rasterizer的surface_是空指针
-[FlutterViewController viewDidDisappear:]
-[FlutterViewController surfaceUpdated:]
    flutter::PlatformView::NotifyDestroyed
    flutter::Shell::OnPlatformViewDestroyed
        flutter::Rasterizer::Teardown
            surface_.reset()
-[FlutterViewController dealloc]
    /**
     * 发送通知:FlutterViewControllerWillDealloc
     **/
    -[FlutterEngine notifyViewControllerDeallocated]
        /**
         * 这时候VC是空的
         **/
        flutter::PlatformViewIOS::SetOwnerViewController
            ios_surface_.reset()
  1. 现在当前屏幕的VC是NativeVC B,虽然FlutterVC C已经被回收了,但是FlutterVC A(不在屏幕内)还在监听着UIApplicationDidBecomeActiveNotification。APP重新唤起后,FlutterVC A会执行surfaceUpdated方法,但是当前engine没有绑定VC,PlatformViewIOS的ios_surface_和Rasterizer的surface_是空指针,无法创建surface(Surface)
-[FlutterViewController applicationBecameActive:]
-[FlutterViewController surfaceUpdated:]
    flutter::PlatformView::NotifyCreated
        /**
         * PlatformViewIOS::CreateRenderingSurface返回是空指针,传给了Rasterizer::Setup,导致crash
         * 代码中的log,"Could not CreateRenderingSurface, this PlatformViewIOS has no ViewController."
         **/
        flutter::PlatformViewIOS::CreateRenderingSurface
            flutter::IOSSurfaceMetal::CreateGPUSurface
        flutter::Shell::OnPlatformViewCreated
        flutter::Rasterizer::Setup

修复:提交的代码

在FlutterViewController的applicationBecameActive方法中判断当前VC是否正在显示,

- (void)applicationBecameActive:(NSNotification*)notification {
   TRACE_EVENT0("flutter", "applicationBecameActive");
   // if (_viewportMetrics.physical_width)
   if (_viewportMetrics.physical_width && (self.isViewLoaded && self.view.window))
     [self surfaceUpdated:YES];
   [self goToApplicationLifecycle:@"AppLifecycleState.resumed"];
 }

在flutter::Rasterizer::Setup方法中判断surface_是否空指针,

...
   if (!surface_) {
     FML_DLOG(INFO) << "Rasterizer::Setup called with no surface.";
     return;
   }
...

在engine外不好处理这个问题,因为FlutterVC的dealloc方法之后,感觉找不到合适时机给engine绑定新的VC

2,flutter::GPUSurfaceMetal::AcquireFrame submit_callback回调中canvas空指针

未完待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354