移动端跨平台方案的实现,鸿蒙ArkUI的技术选择?
本文作者
作者:Pika
链接:
https://juejin.cn/post/7370008254719639591
本文由作者授权发布。
这些年,写过Compose,也了解过Flutte、ReactNative ,但是我在ArkUI中,背后看到了跨平台技术的另一个方向,希望本期文章能够给你不一样的视觉。
为什么选择ArkTS
ArkUI的不得不做出跨平台选择
以网页为核心的webkit。 以系统Native渲染引擎为主,具备以JS编写的UI表示代表ReactNative。 以自绘制引擎(Flutter Engine)为主,以Dart编写的UI表示的Flutter。
JS引擎与Webkit渲染
JS引擎与Native渲染
import React, {useState} from 'react';
import {Text, StyleSheet} from 'react-native';
const TextInANest = () => {
const [titleText, setTitleText] = useState("Bird's Nest");
const bodyText = 'This is not really a bird nest.';
const onPressTitle = () => {
setTitleText("Bird's Nest [pressed]");
};
return (
<Text style={styles.baseText}>
<Text style={styles.titleText} onPress={onPressTitle}>
{titleText}
{'\n'}
{'\n'}
</Text>
<Text numberOfLines={5}>{bodyText}</Text>
</Text>
);
};
const styles = StyleSheet.create({
baseText: {
fontFamily: 'Cochin',
},
titleText: {
fontSize: 20,
fontWeight: 'bold',
},
});
export default TextInANest;
public class ReactTextView extends AppCompatTextView implements ReactCompoundView {
private static final ViewGroup.LayoutParams EMPTY_LAYOUT_PARAMS =
new ViewGroup.LayoutParams(0, 0);
// https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L854
private static final int DEFAULT_GRAVITY = Gravity.TOP | Gravity.START;
private boolean mContainsImages;
private int mNumberOfLines;
private TextUtils.TruncateAt mEllipsizeLocation;
private boolean mAdjustsFontSizeToFit;
private float mFontSize;
private float mMinimumFontSize;
private float mLetterSpacing;
private int mLinkifyMaskType;
private boolean mNotifyOnInlineViewLayout;
private boolean mTextIsSelectable;
private boolean mShouldAdjustSpannableFontSize;
private ReactViewBackgroundManager mReactBackgroundManager;
private Spannable mSpanned;
....
换算成
ReactTextView.setText
ReactTextView.setColor
ReactTextView.setFontSize
<pika image width:100 height:100>
<pika image width:100 height:100 click:java function:x:f>
Dart VM + Flutter引擎
现代Flutter,其实包括Dart编写Widget 与Flutter引擎,这里的Flutter引擎包含了dart相关的,比如framework.dart ,object.dart 等相关的核心渲染实现,还有C++编写的Flutter Engine等。后面我都以“Flutter引擎”作为这些的表述的集合,并不是只包含Flutter Engine。一般Flutter引擎指的是(由C++编写的Flutter Engine),但是这里笔者有意把render相关的放在了Flutter引擎这个大类中,读者们可以注意一下。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/object.dart
https://github.com/flutter/engine
Flutter 借助平台相关的渲染View,比如Android 中的SurfaceView,比如FlutterSurfaceView,或者TextureView这些能够提供渲染环境的View,比如提供OpenGLES环境,能够把生成的绘制指令直接在这些View上执行,从而达到自渲染的目的。这些绘制指令生成依赖于由Dart编写的Widget通过层层转化,比如三棵树后,通过抽象的Layer生成这些绘制指令,它并不与原生的普通View交互,比如Flutter的文本可以直接生成满足OpenGLES的文本绘制一系列执行,从而画出想要的东西。
https://github.com/flutter/engine/blob/main/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
顶点着色器
layout(location = 0) in vec4 a_Position;
layout(location = 1) in vec2 a_CameraTexCoord;
// The virtual scene texture coordinate is unused in the background shader, but
// is defined in the BackgroundRenderer Mesh.
layout(location = 2) in vec2 a_VirtualSceneTexCoord;
out vec2 v_CameraTexCoord;
void main() {
gl_Position = a_Position;
v_CameraTexCoord = a_CameraTexCoord;
}
片元着色器
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES u_CameraColorTexture;
in vec2 v_CameraTexCoord;
layout(location = 0) out vec4 o_FragColor;
void main() { o_FragColor = texture(u_CameraColorTexture, v_CameraTexCoord); }
外部调用忽略,大家可以找到很多这种case
虽然如此,Dart可以说并不是一个成本最低的UI表示,同时Flutter Engine 本身其实也依赖了很多由Flutter本身的产物,这些产物都是由framework.dart 或者object.dart 产生的,比如UI的刷新逻辑。Flutter中,Dart的UI表示与渲染引擎其实结构上不存在耦合,但是产物上存在耦合,怎么理解呢?你必须要用Dart去写UI表示,比如Widget的产物LayerTree才能够被FlutterEngine 所识别产生一系列的Scene,感兴趣可以看 scene_builder。
https://github.com/flutter/engine/blob/552a965b707b97d65c6a5aa18d29757c45c11283/lib/ui/compositing/scene_builder.cc
JS引擎 + Flutter 引擎 + 多引擎
笔者阅读了OpenHarmony中对于ArkUI大量的Commit,得出ArkUI的架构布局历程,属于笔者自己的观点,当然华为官方的本身对ArkUI的设计其实还未公开,大家可以顺着笔者的思路去看待ArkUI的设计历程~
FlutterEngine大量依赖了Flutter framework中的概念,比如framework.dart,还有UI渲染的适配,而这些是用dart语言编写的。直接采取Dart编写UI表示,无论是开发者生态还是后续定制化,都比不上TS/JS,因此,需要把dart这部分迁移出来,用C++重写。 架构上要支持多引擎,这对架构设计上更加苛刻。 定义好TS/JS 编写的组件,提供以C++为基础的组件封装,对标dart编写的基础控件。 适配好所有Flutter相关的线程模型,满足Engine需求。
这就是ArkUI的选择,通过ArkTS编写的控件,会经过ArkUI的封装,直接把TS产物对接到引擎上,比如鸿蒙4.0就可以通过默认的Flutter Engine直接适配Android相关的UI展示,在这个过程中,Flutter UI渲染相关的一些好的流程,ArkUI也进行了保留,比如element 更新。同时也把Flutter的三颗树适配为C++的三棵树,ArkUI更新请看这篇。
https://juejin.cn/post/7349722583158521882
RefPtr<Element> Element::UpdateChildWithSlot(
const RefPtr<Element>& child, const RefPtr<Component>& newComponent, int32_t slot, int32_t renderSlot)
{
// Considering 4 cases:
// 1. child == null && newComponent == null --> do nothing
如果Element 为 null 并且 Component为null,则什么也不做
if (!child && !newComponent) {
return nullptr;
}
// 2. child == null && newComponent != null --> create new child configured with newComponent
新增:child == null && newComponent != null:通过Component建立对应的element
if (!child) {
auto newChild = InflateComponent(newComponent, slot, renderSlot);
ElementRegister::GetInstance()->AddElement(newChild);
return newChild;
}
// 3. child != null && newComponent == null --> remove old child
删除:child != null && newComponent == null:移除elemnt
if (!newComponent) {
ElementRegister::GetInstance()->RemoveItemSilently(child->GetElementId());
DeactivateChild(child);
return nullptr;
}
// 4. child != null && newComponent != null --> update old child with new configuration if possible(determined by
// [Element::CanUpdate]), or remove the old child and create new one configured with newComponent.
更新:child != null && newComponent != null
auto context = context_.Upgrade();
不支持更新,那么删除旧的element,添加新的element
if (!child->CanUpdate(newComponent)) {
// Can not update
auto needRebuildFocusElement = AceType::DynamicCast<Element>(GetFocusScope());
if (context && needRebuildFocusElement) {
context->AddNeedRebuildFocusElement(needRebuildFocusElement);
}
ElementRegister::GetInstance()->RemoveItemSilently(child->GetElementId());
DeactivateChild(child);
auto newChild = InflateComponent(newComponent, slot, renderSlot);
ElementRegister::GetInstance()->AddElement(newChild);
return newChild;
}
.....
能够更新
auto newChild = DoUpdateChildWithNewComponent(child, newComponent, slot, renderSlot);
if (newChild != nullptr) {
newChild->SetElementId(newComponent->GetElementId());
ElementRegister::GetInstance()->AddElement(newChild);
}
return newChild;
.....
return newChild;
}
JS引擎 + Flutter引擎+多引擎的方式,它是ArkUI历史发展的产物,它既保留了Flutter中实践的优点,完成了Flutter引擎与TS的适配,能够在Android系统跑通整个体系,同时最重要的是它为后续切换自己的引擎提供了可能。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!