前段时间1.17.0发布,公司的APP就很快就升级了。
不出意外还是有很多的crash,😭。
后来我们发了一个HotPatch,替换了AppDelegate的applicationWillEnterForeground方法,方法中调用FlutterEngine的setViewController方法来确保FlutterEngine始终attach一个FlutterVC。(这是补丁方案,后续我们还是要自定义引擎)
注:APP的FlutterVC是共享引擎的,我们有自己的混合栈路由
写这篇文章记录沉淀一下:
- crash堆栈还原;
- 引擎定制流程;
- 复现crash,debug源码;
- 阅读源码,修复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;
二,引擎下载和编译
参考地址:Flutter Engine定制流程,Flutter-Engine-编译指北
注意几个点:
- 写.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。
ShadowsocksX开全局模式,下载会很快。
代理设置全了
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)
- 如果电脑年代比较久远,ninja 带上参数 -j 2,并行2个任务防止电脑卡死,这样在编译漫长的时间里你还能干其他活。
🌰:ninja -C out/ios_debug_unopt -j 2 && ninja -C out/host_debug_unopt -j 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必现)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()) {
...
}
}
分析一下原因:
先大致了解重要的几个类和类之间的关系,我画了一个草草的类图,简单说明涉及的类和属性
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绑定的过程(堆栈方法顺序排列):
- 初始化和运行引擎流程
+[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
- 按顺序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
- 现在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()
- 现在当前屏幕的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空指针
未完待续...