Flutter Layout过程分析
layout的基本流程:
- parent将约束信息传递给child,来限制child的最大宽高;
- child根据根据约束信息确认自己的大小(如果儿子还有孙子,则儿子继续执行所有1~4的布局流程);
- parent根据child返回的大小来确定自己的大小;
- parent确定child的位置偏移,并将位置偏移记录在child.parentData.offset中;
Center的布局
1 | //RenderPositionedBox |
layout的基本流程:
1 | //RenderPositionedBox |
main函数就是Flutter app的启动入口,在main函数中调用runApp就可以启动我们的界面了:
1 | void main() { |
下面是runApp的源码:
1 | void runApp(Widget app) { |
WidgetsFlutterBinding继承自BindingBase,但通过with语句混入了各种Binding,我们都知道mixin的类是没有构造函数的。ensureInitialized()会调用WidgetsFlutterBinding构造函数,主要实现在BindingBase构造函数中。BindingBase在构造函数中会调用initInstances(),这个函数在各个mixin的Binding类中都有实现,首先调用的是WidgetsBinding的实现,然后一层一层调用super.initInstances(),这样所以Binding类的initInstances()都会被执行。下面是initInstances的调用时序:
zenuml WidgetsBinding.initInstances { RendererBinding.initInstances { SemanticsBinding.initInstances { PaintingBinding.initInstances { ServicesBinding.initInstances { SchedulerBinding.initInstances { GestureBinding.initInstances { return } return } return } createImageCache() return } return } createRootPipelineOwner return } "_buildOwner = BuildOwner();" "buildOwner!.onBuildScheduled = _handleBuildScheduled;" }
GestureBinding.initInstances:会向PlatformDispatcher设置手势事件的回调,会把从flutter engine接收的数据包解析成PointerEvent,包括PointerDownEvent、PointerUpEvent等;
SchedulerBinding提供了scheduleFrame()接口来主动触发一次绘制刷新。并且还会向PlatformDispatcher注册屏幕刷新事件的回调,如下:
1 | void ensureFrameCallbacksRegistered() { |
在packages/flutter/lib/src/widgets/binding.dart
中的WidgetsBinding.drawFrame()
的注释中介绍了渲染流程。
下面是Flutter官网提供的关于三棵树的介绍,比较形象,这里直接摘录了,可以参考原文。
1 | Container( |
上面这段代码构建的三棵树如下所示:
在开发调试阶段使用Flutter inspector可以看出实际的Widget树要比代码中描述的层级更深。
先看下Widget的家族:
两外还有如下经常使用的Wiget:
导入命名空间
1 | // 导入整个命名空间到当前作用域,这样std::cout就可以写成cout了。 |
一般头文件中要避免使用using导入命名空间。否则引入这个头文件的也相当于导入了命名空间,容易带来命名冲突。
指定别名,C++11中可以使用using来指定别名,作用与typedef相同
1 | typedef int T; // 用 T 代替 int |
派生类引入基类成员
1 | class Base { |
1 | class B {}; |
代码中D和B的继承关系不是虚继承(没有virtual修饰继承关系),如果是虚继承,就无法使用static_cast了。
1 | class B { |
上述代码有两个关键点:1)D与B的继承关系通过virtual修饰;2)B包含虚函数。只有这两个条件同时满足才能使用dynamic_cast。另外与static_cast区别是:static_cast必然成功,而dynamic_cast失败的情况会返回target-type类型的空指针。
std::shared_ptr共享智能指针,可以支持多个智能指针共享一个对象实例。shared_ptr持有的实例会在下面场景下被销毁:
使用方法:
1 | shared_ptr<User> sp1 = make_shared<User>(1, "sp1", "123456"); |
注意:不要使用裸指针来初始化智能指针,会导致多次释放内存造成崩溃:
1 | void test() { |
当test()函数退出时,p2会先调用User析构,然后p1会再次调用User析构函数,同时两次释放同一块内存,就会导致程序出错。下面是正确的写法:
1 | class Base { |
通过VisualStudio来查看A类的内存布局:
1 | class A size(28): |
从A::$vftable@B@
可以看出虚函数表中A和B合并在一起了,并且A::fb2
覆盖了B::fb2
。而C类的vftable则是独立存储。
B和C两个类都定义了test()函数,而A类则没有覆盖test方法,如果A类调用test方法到底采用的是那个呢?在A::$vftable@B@
指向的是B::test
。而A::$vftable@C@
指向的是C::test
。我们通过下面代码进行测试:
1 | int main() { |
所以基于A类的实例是无法调用test函数的,但是把A类转换成B/C的引用或者指针后,就能调用test方法了,但只能分别调用自己的函数实现。
我们现在把C继承Base使用virtual进行修饰,代码改动如下:
1 | class C : public virtual Base |
延迟加载,比如通过IdleHandler在系统空闲时加载模块
异步初始化
缺点:界面展示了,但是用户操控出现卡断
Android 性能优化 - 启动优化
How to Capture Heap Dump From an Android App
浏览 Systrace 报告
Dalvik 可执行文件格式
应用性能优化之VerifyClass
防止因视图层次结构导致性能下降包括两个目标:一个是实现视图层次结构扁平化,一个是减少 Double Taxation。布局耗时过长的一个常见原因是,View 对象的层次结构互相嵌套(层级过深)。每个嵌套的布局对象都会增加布局阶段的开销。层次结构越扁平,完成布局阶段所需的时间越少。
优化方案:
性能和视图层次结构
Jetpack Compose 性能
检查 GPU 渲染速度和过度绘制
Android技术质量 是谷歌官方介绍的性能检查和优化措施。
频繁GC:申请大量对象、图片内存占用
1 | @Preview |
上述代码中,如果不使用key
,就会导致每次顶部插入时,for循环内部的组合项都会被重新构造。加上key
后,就会只构造text变化的DemoText。另外对于for循环外部的两个DemoText时不会重新构造的,因为这两个组合项的输入参数时不变的。
参考:可组合项的生命周期
1 | sample-host-debug.apk -> projects/sample/source/sample-host |
下载Shadow工程后,在根目录执行./gradlew build
等待编译成功,然后通过adb安装:
1 | adb install projects/sample/source/sample-host/build/outputs/apk/debug/sample-host-debug.apk |
在sample-host-debug.apk的assets目录下包含pluginmanager.apk
和plugin-debug.zip
,其中plugin-debug.zip包含的文件:
1 | . |
config.json文件内容如下:
1 | { |
在sample代码中启动插件Activity会通过一个跳板PluginLoadActivity
,跳板Activity会展示插件loading view,从而避免插件启动过程中的黑屏给用户带来困惑。
Android屏幕分辨率适配一般有三种方案:
ConstraintLayout
中,当子View尺寸设置为MATCH_CONSTRAINT(0dp)时,默认行为是占据所有可用空间,但是可以通过layout_constraintHeight_percent
和layout_constraintWidth_percent
来设置为占用父View的百分比,注意,这个百分比是整个父View的宽高的百分比,而不是可用空间的百分比。
1 | <androidx.constraintlayout.widget.ConstraintLayout |
通过bias来设置位置偏移,下面用法可以让子View左边与parent的间距占比为20%,而不是默认的50%。详细介绍可参考Centering positioning and bias。
1 | <androidx.constraintlayout.widget.ConstraintLayout |
通过AndroidStudio的ScreenMatch插件生成不同分辨率的dimens资源文件来进行适配。该插件会基于res/values/dimens.xml
生成不同分辨率的比如:
1 | res/values-sw240dp/dimens.xml |