综述

Flutter从架构上来说有3部分:

  • 用Dart写的Framework层,面向开发者
  • 用Java/Kotlin写的Embdder层(For Android,iOS是OC/Swift),纯Flutter App不需要关心
  • 用C++写的Engine层,提供Dart运行环境和底层绘制能力

针对每个部分,对应的源码阅读环境不同,调试方法也不同。

对于阅读环境,最重要的是能够正确地完成调用/定义的跳转

对于调试环境,最重要的是能够设置断点,单步执行

Framework环境配置

Framework的环境设置比较简单。

源码阅读

Framework的代码在 https://github.com/flutter/flutter 下面,直接Clone下来。

亲测安装了Flutter插件的Android Studio是最好的阅读工具,直接打开./packages/flutter 目录,然后flutter pub get即可。

这一步可能报错,主要是一些的版本冲突,按照信息解决即可。

源码调试

通过Flutter Acttach按钮即可开始调试,但是如果要调试启动部分的Dart代码,用Debug而不是Run来启动程序:



flutter 调用ios oc flutter 调用c代码_dart

Embedder环境配置

Embedder的环境稍微复杂一点。

源码阅读

Embedder的代码在engine的./shell/platform下面:

tree -L 1
.
├── BUILD.gn
├── android
├── common
├── config.gni
├── darwin
├── embedder
├── fuchsia
├── glfw
├── linux
└── windows



flutter 调用ios oc flutter 调用c代码_debug_02

用AS直接打开android目录即可,打开后会发现代码都无法解析对,这样就没法跳转了!!!

首先把根目录设置为Source类型:



flutter 调用ios oc flutter 调用c代码_debug_03

这时候只剩androidx无法解析了:



flutter 调用ios oc flutter 调用c代码_java_04

发现旁边一个目录已经声明了依赖,于是按照提示,建立一个local.properties文件,指出本地sdk路径即可,然后执行Gradle命令,拉取更新:



flutter 调用ios oc flutter 调用c代码_dart_05

后来查看文档,发现其实另外一个目录已经有这些依赖了,直接在工程设置页面添加一个classpath即可:

../third_party/android_embedding_dependencies/

这个目录是在engine外,buildroot下的,应该是之前gclient sync的时候就解析build.gradle拉下来的。

源码调试

如果在打开Flutter的工程,打开Andorid的Activity是解析错误的:



flutter 调用ios oc flutter 调用c代码_debug_06

需要以android作为根目录单独打开,然后通过Run/Debug按钮再次启动即可:



flutter 调用ios oc flutter 调用c代码_java_07

这里单独打开工程无需担心如何集成Flutter,gradle脚本已经搞定了。

Engine环境配置

Engine的配置是最复杂的。

源码阅读

把gn工具在src/out 目录生成的compile_commands.json文件移到src/flutter目录下,然后用CLion打开这个文件就可以正确索引Engine的C++代码了。



flutter 调用ios oc flutter 调用c代码_java_08

该文件是预编译生成的索引,其他编辑器也可以支持,当然用CLion是最方便的。

源码调试

官方提供了gdb的调试方法,但是没有文档,按照代码注释的文档,也无法运行成功,一直报下面的错误:

Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
ImportError: No module named site

看到网上有人已经在用lldb调试了,于是也按照这个思路成功了,

首先是把Android SDK的lldb-server push到设备,建立一个信道,通过run-as绕过权限问题:

# 注意换成自己的包名
adb push lldb-server /data/local/tmp/lldb-server

adb shell run-as com.example.flutter_demo \
cp -F /data/local/tmp/lldb-server /data/data/com.example.flutter_demo/lldb-server

adb shell run-as com.example.flutter_demo \
chmod a+x /data/data/com.example.flutter_demo/lldb-server

adb shell "run-as com.example.flutter_demo sh -c '/data/data/com.example.flutter_demo/lldb-server platform --server --listen unix-abstract:///data/data/com.example.flutter_demo/debug.socket'"

通过以上几步已经建立可以调试的通道了,然后启动lldb,attach到指定进程(通过进程id),然后添加符号表:

adb shell pidof com.example.flutter_demo
lldb
(下面是lldb环境)
(lldb) platform select remote-android
(lldb) process attach -p 25382
(lldb) add-dsym ~/WorkProject/flutter_source_code/src/out/android_debug_unopt_arm64/libflutter.so

之前就注意到构建目录下的这个so非常大,打包的so不过10M,这个接近300M,应该是存在大量调试信息。



flutter 调用ios oc flutter 调用c代码_dart_09

这里有两个坑:

  1. lldb进去之后,进程会挂起,必须用c/continue来恢复,不然无法触发逻辑,也就无法触发断点
  2. 必须在System.loadLibrary之后才能添加符号表,否则失败,如果还是失败,就把以上流程重试一遍~

如此,便可以开始调试了,下面演示在帧刷新位置设置断点,然后触发:



flutter 调用ios oc flutter 调用c代码_dart_10

总结

以上便是Flutter源码阅读/调试环境的搭建,欲善其事,先利其器,后面就要开始真刀真枪撸源码了。

参考

  • engine/flutter_gdb at master · flutter/engine
  • Debugging the engine · flutter/flutter Wiki
  • Debugging Flutter apps - Flutter
  • Debugging Flutter apps programmatically - Flutter
  • Using an OEM debugger - Flutter
  • 如何调试Android Native Framework
  • Flutter Engine C++ 源码调试初探
  • Android 调试桥 (adb)  |  Android 开发者  |  Android Developers