技术漫谈之——Jectpack Compose

技术漫谈之——Jectpack Compose

最近Jetpack Compose发布了Beta版本,抽时间了解了一下Compose带来的改变和其中的一些原理。本文不会讲解具体API,只是比较随意的分享自己的一些疑问以及在探寻答案过程中的一些收获。

技术漫谈之——Jectpack Compose

为什么要有Compose?

Android已经十年多了,传统的Android UI ToolKit有很多历史遗留问题,而有些官方也很难修改。比如View.java有三万多行代码,比如Combo box竟然叫Spinner,再比如Button继承自Textview。同时官方的一些widget修复依赖系统升级,到达用户周期过长。

通过在Jetpack中添加Compose,脱离了Android系统,代码修复可以更快地到达用户。

而对国内开发者来说,更统一的代码,意味着没有厂商定制。这几天有位朋友和我抱怨『哪个大佬有时间重写个editText吗,厂商/系统的一堆问题』,我想他可能要梦想成真了。

同时,Compose通过引入声明式编程,依赖Kotlin特性,可以让代码编写更快更简单。

想象写一个搜索通讯录的界面,传统的Android开发写这个界面需要多少代码?activity一个xml,item一个xml,封装一个recyclerview,再写一个Adapter,写了这么多,可能还费力不讨好,xml转成view的过程中,IO和反射影响了性能,界面再复杂一些,走异步layout还是x2c?而在compose中,可能只需要下面这段简短的代码,并且没有xml的性能问题。

技术漫谈之——Jectpack Compose

如上图,在Compose中,不断的方法调方法,就完成了UI的组装。

什么是声明式编程?

提到Compose,就不得不了解什么是声明式编程。

技术漫谈之——Jectpack Compose

我们来看一下维基百科的解释,声明式编程是一种编程范式,表达逻辑但不描述具体控制流程。就是告诉计算机我想要什么,而不是告诉它怎么做。那对应到Android就是我想要一个什么样的UI,而不是这个UI应该如何改变,当然UI的自动改变需要框架的支持。

声明式编程在React、Flutter等框架中已经有广泛的应用,声明状态,状态变化,UI自动重绘。

技术漫谈之——Jectpack Compose

有意思的是,Compose的发起人Jim Sproch之前是React的核心开发人员,他在Slack上聊到VDOM的一些问题,比如vdom分配内存空间在复杂项目中成为性能瓶颈,compose采用调用composable方法的方式,减少内存分配。相比vdom,compose把node隐藏在背后以防滥用,同时可以更方便的使用if/for控制流程。

@Composable是什么原理?

技术漫谈之——Jectpack Compose

上面这个简单的例子,当点击button,button中的文字自动加1,remember用来记录最新的count值。

@Composable是个注解,而要实现自动更新UI,肯定是修改了Class文件,让我们看看class文件变成了什么样?Kotlin编译后的class在build/tmp/kotlin-classes目录中,但在Android Studio中是无法看到class反编译后的内容,可以用Jadx。然后Text()这些Composable方法编译成class后也有改动,为了方便阅读,最好是编译好APK后,再用Jadx阅读反编译源码。

技术漫谈之——Jectpack Compose

上面就是编译后的CountInner方法,可以看到,方法参数都被改变了,方法块中添加了很多start/end,调用Text()的Lambda变成了ComposableLamda,改动还是比较多的。

这些改动是怎么实现的呢?如果我没记错的话,Kotlin的协程也做了有些改变方法参数的操作,两个是不是差不多的实现?但协程是kotlin特性,应用层动态修改class文件,难道是在Gradle Transform里用ASM去操纵class的?

一番搜索,发现Compose应用了Kotlin compiler的新特性,通过IR extension,可以在中间代码生成期间修改逻辑。IR又是什么?intermediate representation的缩写,翻译为中间语言。Kotlin为了Compose开放了扩展能力,并且统一了JVM/JS/Native的IR流水线,为跨平台提供支持。可以理解为Kotlin对协程做的那些事情,通过使用IR extension,你在应用层也可以去做了。

Talk is cheap, I will show you the code.

技术漫谈之——Jectpack Compose

Compose Compiler的源码。ComposePlugin.kt中注册了ComposeIrGenerationextension。ComposeIrGenerationExtension中又有ComposableFunctionBodyTransformer实现上面描述的方法中添加start/end,ComposerLambdaMemoization实现上面描述的改变成ComposerLambda。具体逻辑可以看源码,注释描述的比较清楚。

重组是怎么实现的?

看Compose的文档,一直有重组(Recomposition)这个词,就是状态变化的时候,自动更新UI。那重组是怎么实现的呢?

技术漫谈之——Jectpack Compose

每次调用count.getValue()的时候,最终会回调到Composer,Composer中维持着一个Map,这时就把state和当前的scope进行了关联,scope可以理解为一段可以重组的范围。那当前的scope哪里来呢?还记得编译的class里多了很多start和end吗,在调用start方法的时候,会生成一个scope,放在栈顶。所以调用count.getValue()的时候,直接拿栈顶scope就可以了。当调用end的时候,会调用updateScope更新scope的block属性,而这个block是一个lambda,执行这个lambda会调用对应的composable方法重绘,这样state和block就关联起来了,后面state变化的时候,拿到block执行就可以了。在这个例子中,count state对应的block是一个调用Button方法的lambda。

再来看下更新state的流程。每次调用count.SetValue()的时候,最终会调到Composer中的recordModificationsOf方法,然后从上段说的Map中获取state对应的scope, 并把它添加到invalidations中,通过编舞者监听,下次vsync时,会调用invalidations中lambda的invoke方法,从而更新UI。

请注意,『在调用start方法的时候,会生成一个scope』,但其实只有第一次添加的时候生成就够了,后面更新UI的时候直接用旧的就可以了,太多类似的东西需要存储,Compose中有一个非常重要的数据结构叫插槽表SlotTable,刚说的这个scope复用以及例子中的remember都是利用了SlotTable,具体可以看深入详解 Jetpack Compose | 实现原理。

Text对应的是TextView吗?

Text对应的是TextView吗?不是的。

debug看了一下,所有的composable UI最后被包在一个AndroidComposeView中,放在ContentView下面,所以最上层的东西是没有变化的。传统的Android UI中的view树,变成了node树,view的那些功能被node替代了。

和旧有体系兼容,可以直接把AndroidComposeView添加在xml中,这样就可以混用了。

自定义Layout怎么写?

简单的看了一下,measure/layout走的是measurePolicy,在一个方法中去写measure和layout。measure中有个Constraints最大最小限制,类似MeasureSpec那一套,match_parent变成了Modifier.fillMaxWidth(),这个Modifier会在measure之前修改Constraints,measure的时候会把修改后的Constraints传递进去。

draw是通过Modifier实现的,还是走canvas那一套。

Touch事件怎么处理?

自以为对Android的touch事件还算比较了解,之前在看Android源码的时候也发现了一些有意思的地方,比如down事件在native底层处理,不是作为message在java层looper处理,所以setMessageLogging的方式检测不到down里的耗时。那编舞者不是分发Input/animation/layout的callback吗?那个主要是用来处理move事件。你以为move事件里只有一个坐标点吗,看看MotionEvent.getHistorySize方法吧,那这个size和屏幕采样率以及触控采样率又是什么关系呢?

言归正传,看到Compose的出现,肯定也好奇对Touch事件处理方式的改变。

dispatchTouchEvent/onInterceptTouchEvent/onTouchEvent这些方法不见了,拦截事件需要实现PointerInputFilter,主要逻辑写在onPointerEvent方法,这个方法甚至连boolean都没返回,那怎么判断是否消费了呢?传递进来包装好的event中有个是否消费的属性,每个filter自己判断是否有未消费的事件,去修改已经消费。感觉这一块还有优化空间,好像没有消费之前的事件,后续事件还会回调到。

技术漫谈之——Jectpack Compose

现在定义了Initial、Main、Final三个阶段,在你关心的阶段中去处理,前两个阶段和以前差不多,Final阶段类似用来处理之前的cancel事件。

结尾

Compose还在持续优化中,比如composable函数最近要支持并发执行了。

两年磨一剑,谷歌推广Compose的决心是毋庸置疑的。Compose为了方便开发者,也是考虑到了很多现实的东西,比如像kotlin支持和java互调一样,支持Compose和传统UI互调。虽然投入巨大,的确更快更简单,但在社区中的普及还有待时间验证,毕竟Jetpack中的库很多大家都还没有用过,而Compose的征程也注定要比Kotlin艰难。

时间有限,本文只能纸上谈兵、管中窥豹、抛砖引玉了,如有谬误,还望不吝赐教。

Original: https://www.cnblogs.com/BlueSocks/p/16082743.html
Author: BlueSocks
Title: 技术漫谈之——Jectpack Compose

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607265/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Python Docstring 风格和写法学习

    什么是Python Docstring 和Java类似,Python也通过注释形式的Docstring给程序、类、函数等建立文档。通过Docstring建立的文档不仅对人来说有更好…

    Linux 2023年6月14日
    0102
  • JVM学习 类加载子系统

    JVM 哔哩哔哩 尚硅谷视频 宋红康老师 Java代码执行流程 简图 详细图 1、类加载子系统 类加载器子系统的作用 类加载器子系统负责从文件系统或者网络中加载Class文件,cl…

    Linux 2023年6月7日
    0104
  • python获取Windows硬件特征信息

    1.python pip安装WMI 并用pyinstaller编译出device_chk.exe 参考内容:https://blog.csdn.net/fengmm521/arti…

    Linux 2023年6月7日
    091
  • 项目的部署和环境搭建

    项目的部署和环境搭建 cd /opt (一般项目部署的文件代码都放在/opt目录下) 3.进入项目跟目录下的docker目录: cd crm _pro/docker 4.执行doc…

    Linux 2023年6月7日
    092
  • 高通平台如何避免误入FFBM模式

    前面两篇博客分别介绍了通过fastboot和QFIL工具退出FFBM模式的方法。虽然售后的同学可以这么指导用户做恢复,但步骤多操作也麻烦,且属于事后处理,如果大面积高概率地出现,会…

    Linux 2023年6月7日
    098
  • Windows server 2008 域控制器

    Windows的网络架构 Windows的网络架构大致分为: 工作组架构 域架构 工作组架构:工作组是由一组通过网络连接在一起的计算机组成,组内的计算机可以共享本机的文件,打印机等…

    Linux 2023年6月7日
    095
  • ASP.NET Core 3.0 : 二十八. 在Docker中的部署以及docker-compose的使用

    本文简要说一下ASP.NET Core 在Docker中部署以及docker-compose的使用 (ASP.NET Core 系列目录)。 系统环境为CentOS 8 。 一、概…

    Linux 2023年6月7日
    0106
  • 【开源打印组件】vue-plugin-hiprint初体验

    vue-plugin-hiprint的学习与应用 😄 生命不息,写作不止🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🏆 一个有梦有戏的人 @怒放吧德德🌝分享学…

    Linux 2023年6月6日
    0173
  • 2021 个人年度小结

    因为不用考研,所以大四一整年可以自由自在地学习一直以来想学却又没时间去学的东西。快乐的大四时光总是显得十分短暂,这篇博客主要用来总结过去一年所学的知识。 计算机组成原理 上的是哈尔…

    Linux 2023年6月7日
    0101
  • 网络中冗余备份

    冗余备份的重要性 如今社会,网络是各个产业的新的血脉,网络的稳定性至关重要,一旦网络出现故障,导致断网、延迟丢包等很可能会导致生产作业停滞,造成较经济损失,为此冗余备份至关重要,从…

    Linux 2023年6月6日
    0123
  • 音视频技术入门课-02 音频从采集到输出涉及哪些关键参数?

    我们平常听到的自然界的声音,比如说鸟鸣、水流,其实是一种模拟信号,声音是振动产生的一种声波,通过气态、液态、固态的物理介质传播并能被人或动物感知的波动现象。声音的频率一般会以赫兹(…

    Linux 2023年6月7日
    099
  • 位运算(一)

    位运算的一般应用 功能 例子 运算 去掉最后一位 1110101->111010 x>>1 在最后加0 1110101->11101010 x< 通过…

    Linux 2023年6月8日
    0138
  • 前端基础之JavaScript(一)

    一、JavaScript概述 1.1 ECMAScript和JavaScript的关系 1996年11月,JavaScript的创造者–Netscape公司,决定将Ja…

    Linux 2023年6月14日
    0108
  • 大数据之Hadoop中HDFS的故障排除

    NameNode故障处理 1)需求 NameNode进程挂了并且存储的数据也丢失了 2)故障模拟 (1) kill -9 NameNode进程 kill -9 19886 (2)删…

    Linux 2023年6月8日
    0111
  • VIM快捷键全集

    VIM快捷键大法 vim是我最喜欢的编辑器,也是linux下第二强大的编辑器。 虽然emacs是公认的世界第一,我认为使用emacs并没有使用vi进行编辑来得高效。 如果是初学vi…

    Linux 2023年6月7日
    097
  • grep

    grep &#x57FA;&#x672C;&#x5339;&#x914D;&#xFF1A; grep a*re hello.txt –* …

    Linux 2023年6月11日
    081
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球