共计 15268 个字符,预计需要花费 39 分钟才能阅读完成。
1 前言
注: 本文中所有的 xml 都可以直接贴去 mock 测试,文章中还附有运行预览的截图。阅读本文可能需要您亿点点的时间。
2 我们要解决什么问题?
2.1 不讲应用场景就是耍流氓!
在线上,对于某些适用于要求强展示、轻交互、高可配场景,RN 和 WebView 显得不够灵活,性能表现也不够好。
使用 RN 时要占据整个 Activity,而且 Native 和 Js 的通信损耗不可避,WebView 的情况则更加糟糕,还要 lock 主线程来加载 webkit。这在二级、三级页面还好,在首页是绝对不能用这种掉性能的方案的。
并且对于首页 feed 流卡片、一级页面的活动区块来说,这些页面的逻辑本身就不强,而且往往也只是需要局部动态化,所以综合来看 RN 和 WebView 都不是最优选,我们需要 第三条路。
2.2 美团 APP 团队的方案
对于这些应用场景,美团 APP 团队有自己的一套闭源的动态化容器 MTFlexbox 来应对,可以让布局快速开发上线,并且不受发版限制。之前在美团实习期间有幸学习过 MTFlexbox,所以这里我要感谢一下美团 APP 终端业务研发组的同学们,以及我原本的 Leader。
这套方案不仅承载了美团首页的动态化,还解决了团首页在连续滑动过程中出现 FPS 波动的问题,详情可以参考他们的文章????Litho 在动态化方案 MTFlexbox 中的实践。
2.3 我的解决方案
实习结束返校之后,自己一直想做一个在美团实习的总结,但又不知道以什么为主题比较合适。思索之后,觉得自己对于 MTFlexbox 理解得以及其适用的业务特点理解的还算深刻,所以最终我选择了尝试自己去设计实现一个与 MTFlexbox 功能类似的开源框架 Gbox,让 Gbox 成为 MTFlexbox 的开源替代品。
注:Gbox 使用 kotlin 开发,在 Apache 开源协议下发布,我虽然说 Gbox 是 MTFlexbox 的开源替代品,但是 Gbox ≠ MTFlexbox????,它不包含 MTFlexbox 的任何源代码,也不是 MTFlexbox 的兼容版本,它是一个完全基于开源软件实现的全新开源软件。
3 Gbox 简介
3.1 研发历程
Gbox 的开发,从需求分析 -> 设计 -> 技术选型 -> 编码 ->bug 修复,花了大约我三周的时间。在技术选型时主要评估可移植性(或跨平台性)和性能指标,最终确认了 Litho+Tomcat EL+kotlin 的技术选型。
- Litho 足够高效、包体积较小,除了 draw 操作 measure、layout 都在异步线程完成,能够满足性能需要,并且在 iOS 平台上也有等价的 componentkit 实现。
- Tomcat EL 功能强大而且久经考验。在实现上几乎没有外部依赖,后期可以使用 Google 的开源项目 J2Objc 直接移植到 iOS 上。
- kotlin 可以大幅的减少冗余代码,并且使用 kotlin 的内联能够以更少的代码获得更高的性能。
其实框架的渲染层原本是打算使用 Flutter 实现的,但是代码写到一半才发现这玩意太重了(包大小 too 大),然后又重新回到 Litho 这条线上进行开发,这期间自己踩了好多 Litho、Drawable 和 Canvas 的坑,自己对 Android 的整个渲染体系也有了更深刻的理解。
目前为止,Gbox 已经基本基本稳定,但还会有小特性持续补充进来,你可以在 github 上???? 找到它。
3.2 Gbox 所支持的特性
Gbox 是对 业务 以及 性能 友好的:
- 耗时操作异步化。将原本 View 体系中的 measure、layout 搬到异步线程中去,解放主线程,这也是 Gbox 之所以高效的原因之一
- 干掉布局层级。直接使用轻量级的 Drawable 进行渲染,与 WebView 相比有更大的性能优势
- 异步图片加载。使用轻量级 Glide 作为图片加载引擎,所有图片均可以从网络加载,并且不会触发额外的布局更新
- 敏捷开发,随时上线,前后端分离。后端下发布局 + 数据的 json,可集成在数据接口下发,本地自主解析渲染布局
- 单容器 View 接入,基本无入侵性。可用于替换现有的任意一个静态展示型的 View,并支持曝光埋点、点击埋点、点击时间处理等事件
- 提供完整的开发工具链。布局开发可实时预览,提供布局实时预览 APP(overview)和 mock 工具,可通过扫码连接电脑进行实时预览调试
- 基于广泛使用的 flexbox 布局模型,包含丰富的可配置样式,例如边框颜色,圆角,图片,文本等
-
强大的布局内绑定表达式 。包括数学运算,for 语句,三元表达式,简单的 java 方法调用,使用表达式时需使用
${}
包围 - 屏幕适配,布局使用的单位为是设备独立的 pt,以设备屏幕宽度为基准,将屏幕分成 360 份,1pt= 设备屏幕宽度 /360
- 使用 kotlin 实现,代码实现非常简洁,很适合阅读学习
- 对旧逻辑友好,支持原生 View 嵌入 Gbox
3.3 与 MTFlexbox 对比
对比美团首页线上方案 MTFlexbox,左为 MTFlexbox(美团 APP),右为 Gbox(Gbox 的实时预览 APP)。
由于布局文件字太多,所以我就不直接往文章里仍了,你可以在 Gbox 的 github 仓库上拿到???? 这个布局文件。
4 开始搭建 Gbox 开发环境
4.1 安装 Overview APP
使用 git clone 源码:
git clone https://github.com/sanyuankexie/Gbox
由于项目中使用了 APT 技术,所以将源码 clone 完毕后,需要先 rebuild 一次。
首先我们需要安装 overview APP,打开找到 overview 模块,将 overview APP 安装到你的测试机上。
接下来是最重要的一步,确保你的手机和你的电脑处于同一网络环境中,推荐是使用热点。
4.2 执行测试用例
找到 mock 模块中的 MockTestCase 文件。
运行 JUnit 的 @Test,即可在控制台中生成地址二维码,你还可以更改 layout 和 data 的路径使用其他样式或者 mock 数据。注意 Android Studio 的主题色需为白色,否则生成的二维码无法被手机识别。
(调整为白色主题????)
可以看到二维码已经在控制台生成。
此时,使用 overview
4.3 开始实时预览
APP 扫描控制台生成的二维码,即可预览电脑上的布局文件。
打开 LiveReload 开关,可以实现实时预览的效果,在电脑上修改布局之后,使用 Ctrl+ S 保存,即可刷新到 overview APP 上。控制台用于打印埋点和点击信息(如果有的情况下)。
那么 Gbox 是如何实现实时预览的呢?
原理其实很简单,也许你都已经猜到了。mock 模块打开了一个 http 服务器,overview 扫码拿到的是电脑的 ip 地址和端口号,然后 overview 每隔一秒去请求服务器下发布局和数据,这样就可完成布局的实时预览。附上源码????MockSession.kt。
5 开始编写布局文件
5.1 绑定表达式
在开始编写布局之前我们需要了解 Gbox 的绑定表达式。
Gbox 的绑定表达式是基于嵌入式 Tomcat(对!就是用在 Spring Boot 上那个)所使用的 EL 表达式类库开发的,所以它支持 EL 表达式的所有特性,包括 Java Bean 访问、方法调用、三元表达式、数学运算等的。
假如你有一个像下面一样的 json:
{
"number":1000,
"control":{"display":true},
"text":"这段文字不会被显示"
}
编写下面的绑定表达式
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Text
textSize="30"
text="${control.display?' 其他文本 ':text}"
height="100">
</Text>
</Flex>
这个 Text 将不会显示文本 ’ 这段文字不会被显示 ’,而会显示 ’ 其他文本 ’。值得注意的是在绑定表达式中字符串常量使用单引号包裹。
数学运算:
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Text
textSize="30"
text="${number+1000}"
height="100">
</Text>
</Flex>
不止是 text 属性,你还可以在任意属性中使用绑定表达式,以满足你数据展示的需要。
5.2 Flex
由于 Gbox 是基于 Litho 的 UI 框架,而 Litho 又是使用 yoga 这个基于 flexbox 布局模型的布局引擎的,所以首先要支持的就是 Flex,顾名思义,就是弹性容器。
Flex 的实现非常简单,你可以理解为增强版的 LinenerLayout,它支持以下属性:
首先是 flexDirection,它用来指定主轴方向,支持 row、column、rowReverse、columnReverse 四种排布方式,下面是 row 和 column 的截图,没有填写 flexDirection 时则默认为 row。
<?xml version="1.0" encoding="utf-8"?>
<Flex flexDirection="row">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<?xml version="1.0" encoding="utf-8"?>
<Flex flexDirection="column">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
接下来是 justifyContent 属性,它标识了其所有子 Layout 在主轴上的对齐方式,包含 flexStart、flexEnd、center、spaceBetween、spaceAround 五种,下面我通过编写一个 xml,展示了该效果。
flexStart、flexEnd、center 无需多言,而 spaceBetween、spaceAround 需要解释一下。
- spaceBetween 是指两端对齐,项目之间的间隔都相等。
- spaceAround 是指每个项目两侧的间隔相等。
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="column">
<Flex
justifyContent="flexStart"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
justifyContent="flexEnd"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
justifyContent="center"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
justifyContent="spaceBetween"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
justifyContent="spaceAround"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
</Flex>
屏幕太小,一张截图截不完。
然后是 alignItems,它描述的的子 Layout 在副轴上的对齐方式,支持 flexStart、flexEnd、center、baseline、stretch 五种。
其中 baseline 表示与项目的第一行文字的基线对齐,stretch 指定时,如果子 Layout 未指定高度,则会占满父 Layout。
编写下面的 xml:
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
height="360"
background="yellow"
flexDirection="row">
<Flex
height="360"
alignItems="flexStart"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
height="360"
alignItems="flexEnd"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
height="360"
alignItems="center"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
height="360"
alignItems="baseline"
margin="5">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
background="blue"
width="100"
height="100">
</Flex>
</Flex>
<Flex
height="360"
alignItems="stretch"
margin="5">
<Flex
background="red"
width="100">
</Flex>
<Flex
background="blue"
width="100">
</Flex>
</Flex>
</Flex>
在上面的布局中,你会发现 360 这个值会经常出现,没错,在 Gbox 中它是一个特殊值。为了做屏幕适配,Gbox 的大小单位是设备独立的,它以屏幕宽度作为基准,将屏幕宽度分为 360 份,一个单位的像素值 = 屏幕宽度像素值 /360,这也刚好是 2x 设计图纸的大小,相信你们的 UI 设计师会喜欢 Gbox 的。
Gbox 的所有布局属性和层级,最终将被应用到 facebook 的 yoga 布局引擎中去,无论布局有多复杂您都不需要担心,因为所有的计算都是在可指定的 Layout 线程中进行的,根本不会影响主线程,并且最终生成在屏幕上的 View 是没有这些冗余布局层级的。有关更多 Litho 的信息,建议您查阅 Litho 的相关文档。
5.3 Frame
Frame 实现了类似 Android 上 FrameLayout 的布局效果,用于实现 Flex 难以实现的多层叠加效果。
在 Frame 上,Gbox 采用了比 Flex 更激进的布局测量策略。我们都知道,在 Android 的 FrameLayout 中 onMeasure 会去测量所有的子 Layout,最终才能确定宽高,Gbox 中利用 Litho 的 Component 的不可变性(线程安全),将这一操作进行了并行化。
Frame 拥有独立的线程池,可以并发地测量所有地子 Layout,最终的结果在一个线程汇集,下图演示了该过程。
PS: 说人话就是调用了 java.util.concurrent.Executors#newCachedThreadPool
,然后等待在一个线程等待其他java.util.concurrent.Future
完成,源码链接????FrameFactory.kt
编写下面的布局实现叠加效果:
<?xml version="1.0" encoding="utf-8"?>
<Frame
width="360"
height="360"
background="yellow"
flexDirection="row">
<Flex
background="red"
width="100"
height="100">
</Flex>
<Flex
marginTop="50"
marginLeft="50"
background="blue"
width="100"
height="100">
</Flex>
</Frame>
5.4 Image
Image 不仅仅只是一张简单的 ImageView,它封装了 Glide 图片加载引擎,支持异步加载、圆角裁剪和高斯模糊。
使用 url 来加载网络图片:
Gbox 没有使用 Litho 的 State 来实现异步图片加载,所以不会触发 Litho 的布局更新,而是直接替换底层的 Drawble,然后调用 invalidateDrawable,刷新脏矩形。
PS: 说人话就是使用了 DrawableWrapper.kt。
mock 所使用的 json 数据:
{"image2": "http://5b0988e595225.cdn.sohucs.com/images/20180606/0a49d21848324503a1e04c4b942a1631.png"}
编写 xml:
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Image
width="360"
height="360"
url="${image2}">
</Image>
</Flex>
borderRadius 存在时,内部的图片会被裁剪:
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Image
scaleType="fitXY"
borderRadius="100"
width="360"
height="360"
url="${image2}">
</Image>
</Flex>
使用 blurRadius 和 blurSampling 控制高斯模糊:
blurRadius 为弧度,值在 1 -25 之间,blurSampling 为采样率,值要比 1 大。
Gbox 使用 renderscript 技术将高斯模糊的效率最大化,能够减少使用高斯模糊时图片出现的延迟时间。
PS: 说人话就是在 Glide 加载图片的时候加了个Transformation
,源码链接????BlurTransformation.kt
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Image
blurRadius="25"
blurSampling="2"
scaleType="fitXY"
borderRadius="100"
width="360"
height="360"
url="${image2}">
</Image>
</Flex>
与 ImageView 一样支持 scaleType:
细心的朋友会发现,其实上面已经在使用 fitXY 了,笑~
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Image
scaleType="fitEnd"
width="360"
height="360"
url="${image2}">
</Image>
</Flex>
5.5 Text
Text 用于显示文本,目前支持一下属性修饰:
- text 用于指定要显示的文本
- textAlign 用于指定文本对齐方式,支持 center、left、right
- textSize 用于指定文本大小
- textStyle 用于指定文本风格如 normal(默认)、bold(粗体)
- maxLines 指定最大行数
- minLines 指定最小行数
- textColor 指定字体颜色
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="column">
<Text
textColor="#59a9ff"
textSize="30"
text="文本 1111">
</Text>
<Text
textColor="red"
textSize="30"
text="文本 1111">
</Text>
<Text
textColor="blue"
textSize="30"
text="文本 1111">
</Text>
</Flex>
5.6 Native
Gbox 对传统业务组件也是友好的。
如果将 Gbox 接入以后之前写的自定义 View 都得从头编写的话,那 Gbox 就失去了快速开发的意义了。所以 Gbox 也支持原生 View 的接入。
PS: 说人话就是封装了com.facebook.litho.ViewCompatComponent
,源码????NativeFactory.kt。
使用 type 属性,编写下面的代码,就能实现下图中所展示的样式。
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="row">
<Flex
flexDirection="row">
<Text
marginLeft="10"
textSize="28"
text="开关">
</Text>
<Native
marginLeft="10"
type="ch.ielse.view.SwitchView">
</Native>
</Flex>
</Flex>
值得注意的是 Native 已经是布局树的叶子节点,这意味着不支持再使用 Native 包裹其他节点。
5.7 Scroller
Scroller 就是 ScrollView,与 ScrollView 一样,它只能有一个子 View,它由两个属性控制样式:
- scrollBarEnable 是一个布尔值(true 或者 false)控制是否显示滚动条。
- orientation 控制横向还是竖向(vertical 或者 horizontal)。
<?xml version="1.0" encoding="utf-8"?>
<Scroller>
<Flex>
<Image
width="360"
height="600"
scaleType="fitXY"
url="${image2}">
</Image>
</Flex>
</Scroller>
【这 … 这就不展示了吧 … 我实在是觉得这玩意截图没啥意义 …】
5.8 for
当 model 数据中有列表数据需要展开时,就需要用到 for 标签。
for 标签有三个属性,在使用时都是必须指定的,分别是 var,from,to,index 用于指定循环中迭代器的名字,from 和 to 则指定了 var 的的迭代范围。
比如你有下面的数据:
{
"height":1000,
"itemTexts":["Gbox","Facebook","Litho","Google"]
}
编写下面的布局:
<?xml version="1.0" encoding="utf-8"?>
<Flex
width="360"
background="yellow"
flexDirection="column">
<for var="index" from="1" to="3">
<Text
textSize="30"
text="${itemTexts[index]}">
</Text>
</for>
</Flex>
可以被等价展开成:
<Flex
width="360"
background="yellow"
flexDirection="column">
<Text
textSize="30"
text="${itemTexts[1]}">
</Text>
<Text
textSize="30"
text="${itemTexts[2]}">
</Text>
<Text
textSize="30"
text="${itemTexts[3]}">
</Text>
</Flex>
for 标签 var 所指定的迭代器只会在 for 循环所包括的布局标签中生效
PS: for 标签是调用了 Tomcat EL 的 ELContext#enterLambdaScope
和ELContext#exitLambdaScope
实现的,代码我就不在这里贴了,你可以???? 直接跳转到 github 看源码
5.9 内置函数
目前 Gbox 还支持一些内置函数,内置函数必须在绑定表达式中才能调用:
utils:check(o:Any)
可以检测一个变量是否有效,为空或者大小为 0 的集合或者为空的字符串都会返回 false 值
check 方法是由 kotlin 实现的:
fun check(o: Any?): Boolean {return when (o) {is String -> o.isNotEmpty()
is Collection<*> -> !o.isEmpty()
is Number -> o.toInt() != 0
else -> o != null
}
}
在下面的布局逻辑中,屏幕上不会展示任何东西,因为 json 中没有 ’no_found’ 这个变量。
<?xml version="1.0" encoding="utf-8"?>
<Flex>
<Image
width="360"
height="360"
url="${utils:check(no_found)?image2:''}">
</Image>
</Flex>
draw:gradient(o:Orientation,vararg colors: String)
用于实现渐变色,第一个参数为渐变色的方向,有 t2b(上到下),tr2bl(上右到下左),l2r(右到左),br2tl(下右到上左),b2t(下到上),r2l(右到左),tl2br(上左到下右)八种方向可选,第二个参数是可变参,可传入若干个颜色的字符串
kotlin 的源码实现:
fun gradient(orientation: GradientDrawable.Orientation, vararg colors: String): GradientDrawable {
return GradientDrawable(orientation, colors.map {parseColor(it)
}.toIntArray())
}
编写 xml 实现渐变色:
<?xml version="1.0" encoding="utf-8"?>
<Flex>
<Flex
width="360"
height="360"
background="${draw:gradient(tl2br,'red','blue','yellow')}">
</Flex>
</Flex>
5.10 其他通用属性
除了上述属性之外还有很多属性是通用的,受限于篇幅,这里我对一些比较重要的属性进行简单介绍。
background属性是对所有 Widget 都通用的,用于为图片指定背景,它支持以下三种显示来源:
- 图片的 url
- 以 #开头的十六进制颜色值
- 渐变色(上面已经演示过了)
以图片 url 为背景
<?xml version="1.0" encoding="utf-8"?>
<Flex>
<Flex
width="360"
height="360"
background="${image2}">
</Flex>
</Flex>
borderRadius 用于实现裁剪背景边界的圆角。
<?xml version="1.0" encoding="utf-8"?>
<Flex>
<Flex
borderRadius="100"
width="360"
height="360"
background="${image2}">
</Flex>
</Flex>
你还可以使用 borderColor 和borderWidth为边界指定宽度和颜色。
<?xml version="1.0" encoding="utf-8"?>
<Flex>
<Flex
borderColor="red"
borderWidth="10"
borderRadius="100"
width="360"
height="360"
background="${image2}">
</Flex>
</Flex>
使用 clickUrl 并打开 overview APP 的控制台,点击图片,就能在 EventListener 中收到点击事件传递下来的信息,它可以是一个 url,供外部跳转使用。
<?xml version="1.0" encoding="utf-8"?>
<Flex>
<Flex
clickUrl="拉菲世界第一可爱"
borderColor="red"
borderWidth="10"
borderRadius="100"
width="360"
height="360"
background="${image2}">
</Flex>
</Flex>
于此类似的属性还有 reportView(曝光时上报),reportClick(点击时上报)都可以在控制台查看。
还有一些通用属性,我也在这里也简单列了一下:
宽高
- height
- width
可见性
- visibility 与 Android 类似,支持 visible、invisible 和 gone
外边距
- margin
- marginBottom
- marginTop
- marginLeft
- marginRight
内边距
- padding
- paddingTop
- paddingBottom
- paddingLeft
- paddingRight
Flexbox 所支持的其他属性
- alignSelf
- alignContent
- flexGrow
- flexShrink
- ……
对于 Flexbox 这部分,我真诚地推荐你去看阮一峰老师的文章 Flex 布局教程。
Gbox 中的对应属性基本上就是将这些属性使用 java 的命名风格,取消中位线,第二个单词开始使用大写。如在 css 中的 align-content 在 Gbox 中就是alignContent。
6 后端集成注意事项
6.1 集成建议
在后端,可能集成者需要建立一个统一的布局管理系统,包括:
- 布局 cdn。
- git 联动的版本控制功能等等。
当然这些也在未来的开发计划中,欢迎您的 PR。
6.2 下发的是 json 不是 xml
值得注意的是,虽然编写布局使用的是 xml,但是您可以发现最终 mock 服务器下发到客户端的只有 json,这是因为在下发布局时 mock 服务器已经将 xml 转换为了 json,这样做的目的是为了布局集成到数据接口中下发,使用统一工具进行解析,所以 Gbox 没有耦合 xml 的解析模块,而是最大化的利用了现有基础设施。所以在后端集成时需要把 xml 转换为 json 来保存。
7 在新项目中集成 Gbox
7.1 远程依赖 Gbox
Gbox 使用 jitpack 进行构建,在你的根项目的 build.gradle 中添加
allprojects {
repositories {
...
maven {url 'https://jitpack.io'}
}
}
然后在模块中依赖
dependencies {implementation 'com.github.LukeXeon.flexbox:core:latest.release'}
Sync 项目,即可将 SDK 集成到项目中,Gbox 的包大小不算太大,约为 1M 左右,这其中主要是 Facebook 的 Litho 的大小。
7.2 集成 Gbox 的容器 Litho
Gbox 基于 Litho,所以你还得先找个地方初始化 Litho,可以是 Application,或者第一个 Activity 里,反正用 LithoView 之前初始化就行了。
SoLoader.init(this, false);
LithoView 是 Gbox 的容器,在 xml 中使用:
<com.facebook.litho.LithoView
android:id="@+id/host"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.facebook.litho.LithoView>
然后在 Java 层中拿到实例,调用 setComponentAsync 方法,传入一个新构造的 DynamicBox。
mLithoView.setComponentAsync(DynamicBox.create(c)
.bind(data)
.layout(layout)
.eventListener(this)
.build());
其中 layout 是一个 com.guet.flexbox.NodeInfo 类的实例,它用来描述一颗布局树,是整棵布局树的树根。
而 data 则是绑定到布局中的数据,它支持多种数据格式自动匹配,分为以下两种情况:
- 在项目中依赖了 Gson 的情况下,data 可以直接是 String、byte[]、Reader、InputStream 这些数据源。
- 如果项目中没有 Gson,那么 Gbox 也支持嵌套的多层级 Map<String,Object> 以及标准的 java bean(有 getter,setter)作为数据源。
在 overview APP 中,我使用了 Retrofit 来简化了这一过程。
添加 Retrofit 的依赖:
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
用 Retrofit 实例化接口:
package com.guet.flexbox.overview;
import com.guet.flexbox.NodeInfo;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
public interface MockService {@GET("/data")
Call<Map<String, Object>> data();
@GET("/layout")
Call<NodeInfo> layout();}
mMockService = new Retrofit.Builder()
.baseUrl(url)
.client(new OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MockService.class);
EventListener用来处理事件回调,目前有三种事件点击、上报点击和上报曝光。
package com.guet.flexbox
enum class EventType {
CLICK,
REPORT_CLICK,
REPORT_VIEW
}
package com.guet.flexbox
interface EventListener {fun onEvent(type: EventType, action: String?)
}
所传入的 action,就是你在 xml 中编写的字符串所解析得到的结果。
8 结语
8.1 吐槽
这里吐槽一下,写文档是真的难受,但没有高质量的文档是不行的啊,别人会看不懂你在干啥,所以还真是验证了那句老话:“程序员想要的是内容详实的文档,但自己从来不写也最讨厌不写文档的同事”。
8.2 如果你跑不起来
对于之前有同学拉了我的代码出现没跑起来的情况,我在这里说一声抱歉。现在我在 github 上的仓库现在也分了 master 和 develop 分支,develop 分支供我自己开发测试新特性使用,master 分支为稳定的主分支用于 jitpack 打包,develop 分支的代码稳定后,我才会和入 master,这样应该就不会出现之前那样的问题了。
新框架多多少少会有些小问题,Gbox 代码中的注释我也会在日后补充,还请各位海函,发现了问题你可以直接给我提 issue,我会在 github 上跟进,或者直接给我发邮件 imlkluo@qq.com,感谢您的支持。
8.3 关于新特性
以下是将来会进行尝试的的方向:
- 与 Android Studio 集成的 IDEA 插件
- 将绘制操作也放到异步线程中去
- 基于 componentkit 和 EL(J2Objc)的 iOS 侧
8.3 小尾巴
最后的最后,求 star!求 star!求 star!重要的事情说三遍,请各位大佬帮帮忙,点一下玩一年,开源不花一分钱!