美文网首页
Flutter 1.17.0版本 碰到的几个引擎的crash

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

作者: yx杨小鑫 | 来源:发表于2020-06-16 21:26 被阅读0次

前段时间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空指针

未完待续...

相关文章

网友评论

      本文标题:Flutter 1.17.0版本 碰到的几个引擎的crash

      本文链接:https://www.haomeiwen.com/subject/bwhtxktx.html