Jetpack Compose 深入探索系列六:Compose runtime 高级用例
Compose runtime vs Compose UI
在深入讨论之前,非常重要的一点是要区分 Compose UI
和 Compose runtime
。Compose UI
是 Android
的新 UI 工具包,具有 LayoutNodes
的树形结构,它们稍后在画布上绘制其内容。Compose runtime
提供底层机制和许多状态/组合相关的原语。
随着 Compose 编译器
支持完整的 Kotlin 平台谱系,现在可以在几乎任何地方(只要它运行Kotlin)使用 runtime来管理 UI 或任何其他树形结构。注意“其他树形结构”部分:Compose runtime 中几乎没有直接提到 UI(或Android)。虽然该运行时肯定是为了支持该用例而创建和优化的,但它仍然足够通用,可以构建任何类型的树结构。事实上,它在这方面与 React JS 非常相似,React JS 的主要用途是在 Web上创建 UI,但它在合成器或 3D 渲染器等领域找到了更广泛的用途。大多数自定义渲染器重用 React runtime 的核心功能,但在浏览器 DOM 的位置上提供自己的构建块。
从上图中可以看到 JetBrains 团队在多平台下的 UI 模块布局,虽然它现在还不完整,缺少 iOS 端的支持,但是 Compose iOS 目前已经在极地的开发当中,这一点我们可以从 Kotlin 官网的讨论帖子中得到证实(如你感兴趣,可以点击这里查看他们的讨论):
也就是说在未来 compose-jb 发布稳定版本之后,我们是一定可以获得对 Compose iOS 的支持,这一点是毋庸置疑的,现在只是时间问题。到那时,Compose 将成为历史上真正意义上的只使用一种语言(Kotlin)实现的支持多平台的开发框架,这将是革命性的。
总而言之,在 Compose 编译器 和 Compose runtime 的支持基础之上,我们可以构建任何一种类似于 Compose UI 这样的客户端 UI 库,而在 Android 平台上使用的 compose-ui
依赖库只是 Compose 架构能够支持的多平台场景之一。
Composition
Composition 为所有可组合函数提供上下文。它提供了由 SlotTable 支持的“缓存”以及通过 Applier创建自定义树的接口。Recomposer 驱动 Composition,在某些相关内容(例如状态)发生更改时启动重新组合。正如文档所提到的,通常情况下,Composition 由框架本身为您构建,但猜猜怎么着? 我们可以自己来管理它。
要构建 Composition,您可以使用提供的工厂方法:
- 父级
context
通常可以通过rememberCompositionContext()
在任何组合函数中获得。或者,Recomposer
也实现了CompositionContext
,它也可以在 Android 上获取到,或者为你自己的需求单独创建。 - 第二个参数是
Applier
,决定如何创建和连接由Composition
生成的树形结构。
有趣的事实:如果需要可组合函数的其他属性,你可以提供一个完全不执行任何操作的 Appler实例。即使没有节点,
@Composable
注解也可以为数据流转换或事件处理程序提供动力,这些处理程序可以像所有的组合一样对状态变化做出反应。只需要这样Applier<Nothing>
,不要在那里使用ComposeNode
!
接下来的其余部分将着重介绍如何在不使用 Compose UI 的情况下使用 Compose runtime。第一个例子来自 Compose UI 库,其中使用自定义树来呈现矢量图形(我们在之前简要介绍过它)。之后,我们将切换到 Kotlin/JS 并使用 Compose 创建浏览器 DOM 管理库的玩具版本。
矢量图形的组合
在 Compose 中,矢量渲染是通过 Painter 抽象实现的,类似于经典 Android 系统中的 Drawable。
rememberVectorPainter
块中的函数(特别是 Group
和 Path
)也是组合函数,但是是不同类型的。它们不像 Compose UI
中的其他组合函数那样创建 LayoutNode
,而是创建特定于矢量图的元素。组合这些元素会产生一个矢量树,然后将其绘制到画布上。
Group
和 Path
存在于一个不同的 Composition
之中,与其他 UI 组件分开。这个 Composition
被包含在 VectorPainter
之中,只允许使用描述矢量图像的元素,而普通的 UI 组件则是被禁止的。
构建矢量图像树
矢量图像是由比 LayoutNode 更简单的元素创建的,以更好地适应矢量图形的需求。
上面的节点定义了一个树结构,类似于经典的 vector drawable XML中使用的树结构。树本身由两种主要类型的节点构建:
- GroupComponent:组合其子节点并对它们应用共享的变换;
- PathComponent:叶子节点(没有子节点),用于绘制
pathData
。
函数 DrawScope.draw()
提供了一种绘制节点及其子节点内容的方法。这个函数的签名与 Painter
接口相同,后者与此树的根集成。
相同的
VectorPainter
也被用于显示来自经典 Android 系统的 XMLvector drawable
资源。XML 解析器创建了类似的结构,这些结构转换为一系列Composable
调用,从而为看似不同类型的资源提供了相同的实现。
上面的树节点被声明为 internal
,而创建它们的唯一方式是通过相应的 @Composable
声明。这些函数是与 rememberVectorPainter
相关的函数。
ComposeNode 调用将节点发射到组合 Composition 中,创建树形元素。在此之外,@Composable
函数不需要与树交互。在初始插入时(创建节点元素时),Compose会跟踪已定义参数的更新,并逐步更新相关属性。
factory
参数定义如何创建树节点。这里,它只调用对应的Path
或Group
组件的构造函数。update
提供了一种逐步更新已创建实例的属性的方法。在 lambda 内部,Compose 使用辅助函数(例如fun <T> Updater.set(value: T)
或fun <T> Updater.update(value: T)
)对数据进行记忆。 它们仅在提供的值更改时刷新树节点属性,以避免不必要的失效。content
参数是将子节点添加到其父节点的方法。这个可组合参数在节点的更新完成后执行,然后所有发出的节点都将作为当前节点的子节点。ComposeNode 还有一个没有content
参数的重载版本,可以用于叶节点,例如Path
。
为了将子节点连接到父节点,Compose 使用 Applier,我们在之前文章简要讨论过。VNode
通过 VectorApplier
组合。
Applier 接口中的大部分方法经常会用于列表操作(插入/移动/删除)。为了避免反复实现它们,AbstractApplier
甚至为MutableList
提供了方便的扩展。在 VectorApplier
的情况下,这些列表操作直接在 GroupComponent
中实现。
在 Compose runtime 篇我们提到过,Applier 提供了两种插入方法:topDown 和 bottomUp,它们按不同的顺序组装树:
topDown
首先将节点添加到树中,然后逐个添加其子节点。bottomUp
创建节点,添加所有子节点,然后再将其插入到树中。
其底层原因是出于性能考虑:某些环境下将子节点添加到树中具有相关成本(考虑在经典 Android 系统中添加 View 时的重新布局)。对于矢量图案例,不存在这样的性能成本,因此节点从上向下插入。有关更多信息,请参见 Applier 文档。
将 Vector Compostion 集成到 Compose UI 中
有了 Applier,矢量合成几乎可以投入使用了。最后一步就是 VectorPainter 的集成。
集成的第一部分是连接 Compose UI 组合和矢量图像的组合:
- RenderVector 接受具有矢量图像描述的
content
Composable 内容。Painter实例通常在重新组合之间保持不变(使用remember
),但是如果内容已更改,则每次都会调用RenderVector。 - 创建组合始终需要父上下文,在这里它从 UI 组合中使用
rememberCompositionContext
获取。它确保两者连接到同一个 Recomposer,并将所有内部值(例如density
的CompositionLocals
)也传播到矢量组合中。 - 组合通过更新而保留,但应在 RenderVector 每次离开作用域时销毁。DisposableEffect 和Compose 中其他类型的订阅以类似的方式管理清理工作。
最后一步是用图像内容填充组合,创建矢量节点树,然后将其用于在画布上绘制矢量图像:
- VectorPainter 维护了自己的 Composition,因为 ComposeNode 需要匹配传递给 Composition 的 Applier,而 UI 上下文使用的 applier 与 Vector nodes 不兼容。
- 如果VectorPainter 尚未初始化,或已初始化但是其 Composition 已经被Dispose,则创建 Composition。否则就重用 Composition 。
- 创建 Composition 后,通过
setContent
填充它,类似于ComposeView
中使用的方式。每当使用不同内容调用RenderVector
时,setContent
就会再次执行以刷新向量结构。content
内容将子项添加到根节点,稍后用于绘制 Painter 的内容。
完成了整合,现在VectorPainter可以在屏幕上绘制 @Composable
内容。Painter 内部的组合项也可以访问 UI 组合项中的状态和组合局部值来驱动它们自己的更新。
通过上述内容,您已经知道了如何创建自定义树并将其嵌入到已有的组合中。在下一部分中,我们将介绍如何基于相同的原则创建一个独立的 Kotlin/JS 组合系统。
使用 Compose 管理 DOM
多平台支持对于 Compose 来说仍然是一件新事物,只有运行时和编译器可以在 JVM 生态系统之外使用。然而,这两个模块足以让我们创建一个组合并在其中运行某些内容,这会带来更多的体验!
Google 提供的 Compose 编译器依赖支持所有 Kotlin 平台,但是运行时仅分发给 Android。然而,Jetbrains 发布了他们自己的版本的 Compose(大部分没有变化),其中包含 JS 的多平台构件。
让 Compose 产生魔力的第一步是找出它应该操作的树。值得庆幸的是,浏览器已经具有基于 HTML/CSS 的“视图”系统。我们可以通过 DOM API 从 JS 操作这些元素,这也是由 Kotlin/JS 标准库提供的。
在开始 JS 之前,让我们先看一下浏览器中的 HTML 表示。
上面的 HTML 显示了一个带有三个 Item 的无序列表。从浏览器的角度来看,这个结构如下所示:
DOM 是一个类似树的结构,由元素构成,这些元素在 Kotlin/JS 中以 org.w3c.dom.Node
的形式公开。对于我们而言,相关的元素包括:
HTML
元素(org.w3c.dom.HTMLElement
的子类)表示标记(例如li
或div
)。可以使用document.createElement(<tagName>)
创建它们,浏览器会自动找到标记的正确实现。- 标记之间的文本
Text
元素(例如上面的“Item”)表示为org.w3c.dom.Text
。可以使用document.createTextElement(<value>)
创建此元素的实例。
使用这些 DOM 元素,从 JS 的角度来看,它会看成是如下的树形结构:
这些元素将为 Compose 管理的树提供基础,类似于在前面的部分中如何使用 VNode 进行矢量图像组合。
标签不能就地更改,例如 <audio>
元素与 <div>
元素在浏览器中的表示完全不同,因此如果标签名称已更改,则应该重新创建该元素。Compose 不会自动处理这个问题,因此重要的是避免在同一个 Composable 中传递不同的标签名称。
最简单的实现节点重新创建的方法是为每个节点都创建一个单独的 Composable(例如为相应元素创建
Div
和Ul
)。这样一来,您就可以为每个元素创建不同的编译时组,提示 Compose 应完全替换这些元素,而不仅仅是更新其属性。
但是对于文本元素而言,在结构上是相同的,我们可以使用 ReusableComposeNode 来指示这一点。这样,即使 Compose 在不同的组中找到这些节点,它也会重用实例。为确保正确性,文本节点创建时没有内容,并使用 update
参数设置其值。
为了将元素组合成树形结构,Compose 需要一个操作 DOM 元素的 Applier 实例。其中的逻辑与上文中的 VectorApplier 非常相似,只是用于添加/删除子元素的 DOM 节点方法略有不同。大多数代码都是完全机械化的(将元素移动到正确的索引位置),因此我在此省略了它。如果您需要参考,请查看 Compose for Web 中使用的 Applier。
浏览器中的独立组合
为了将我们的新 Composables 组合到 UI 中,Compose 需要一个活动组合。在 Compose UI 中,所有初始化都已在 ComposeView 中完成,但对于浏览器环境,需要从头开始创建。
相同的原则也可以应用于不同的平台,因为下面描述的所有组件都存在于 “
common
” 的 Kotlin 代码模块中。
renderComposable
隐藏了组合开始的所有实现细节,提供了一种将可组合元素呈现到 DOM 元素的方式。其中大部分设置都涉及使用正确的时钟和协程上下文初始化 Recomposer:
- 首先,快照系统(负责状态更新)被初始化。GlobalSnapshotManager 被有意地排除在运行时之外,如果目标平台没有提供该类的源码 ,你可以从 Android 源码中拷贝一份。这是目前 runtime 没有提供的唯一部分。
- 接下来,使用 JS 默认值创建 Recomposer 的协程上下文。浏览器的默认 MonotonicClock 由 requestAnimationFrame 控制(如果使用 JetBrains 实现),Dispatchers.Main 引用了 JS 操作的唯一线程。此上下文用于稍后运行重组。
- 创建 一个 Composition 组合。它的创建方式与上面的矢量图示例相同,但现在 Recomposer 被用作组合的父项(recomposer 必须始终是最上层组合的父项)。
- 然后,设置组合内容(
setContent
)。所有对此组合的更新都应在提供的 Composable 内完成,因为新的renderComposable
调用会从头开始重新创建所有内容。 - 最后一部分是启动重组过程,这是通过启动一个协程
Recomposer.runRecomposeAndApplyChanges
来完成的。在 Android 上,此过程通常与Activity/View
生命周期相关联。可以通过调用recomposer.cancel()
来停止重组过程。这里,组合的生命周期与页面的生命周期绑定,因此不需要取消。
上面的基本元素现在可以组合在一起来呈现 HTML 页面的内容:
创建静态内容可以通过更简单的方法来实现,而 Compose 最初的目的是为了实现交互性。在大多数情况下,我们希望在点击按钮时发生某些事情,而在 DOM 中,可以通过类似于 Android 视图的点击监听器来实现。
在 Compose UI 中,许多监听器是通过
Modifier
扩展定义的,但它们的实现对于LayoutNode
是特定的,因此不适用于这个示例 Web 库。你可以尝试从 Compose UI 中复制Modifier
的行为,并调整此处使用的节点以通过修饰符更好地集成事件监听器。。
现在,每个 Tag 都可以定义一个点击监听器作为 lambda 参数,该参数通过为所有 HTMLElement
定义的 onclick
属性传播到 DOM 节点。有了这个补充,可以通过将 onClick
参数传递给 Tag 组合来处理点击:
这里有多种玩法可以扩展这个玩具库,添加对 CSS、更多事件和元素的支持等等。JetBrains 团队目前正在尝试更高级的 Compose for Web 版本。它基于我们在此探索的玩具版本的相同原则,但在许多方面更为先进,可以支持在Web上构建的各种功能。
总结
在本文中,我们探讨了如何使用核心的 Compose 概念来构建 Compose UI 之外的系统。自定义组合在实际中很难遇到,但如果您已经在 Kotlin / Compose 环境中工作,那么它们是非常有用的工具。
矢量图形组合是将自定义可组合树集成到 Compose UI 的很好的例子。相同的原理可以用于创建其他自定义元素,这些元素可以轻松地与 UI 组合中的状态/动画/组合局部进行交互。
在所有Kotlin平台上创建独立的组合也是可能的!我们通过 Kotlin/JS 的强大功能,在浏览器中基于Compose runtime 制作了一个玩具版本的 DOM 管理库。类似地,Compose runtime 已经被用于一些 Android 之外的项目中来操作 UI 树 。
相关文章:
Jetpack Compose 深入探索系列六:Compose runtime 高级用例
Compose runtime vs Compose UI 在深入讨论之前,非常重要的一点是要区分 Compose UI 和 Compose runtime。Compose UI 是 Android 的新 UI 工具包,具有 LayoutNodes 的树形结构,它们稍后在画布上绘制其内容。Compose runtime 提供底层机制和…...
23.3.2 Codeforces Round #834 (Div. 3) A~E
FG明天补 A-Yes-Yes? 题面翻译 给定 ttt 个字符串,请判定这些字符串是否分别是 YesYesYesYes…\texttt{YesYesYesYes\dots}YesYesYesYes… 的子串。是则输出 YES,否则输出 NO(YES 和 NO 大小写不定)。 Translated by JYqwq …...
一次失败的面试经历:我只想找个工作,你却用面试题羞辱我!
金三银四近在咫尺,即将又是一波求职月,面对跳槽的高峰期,很多软件测试人员都希望能拿一个满意的高薪offer,但是随着招聘职位的不断增多,面试的难度也随之加大,而面试官更是会择优录取小王最近为面试已经焦头…...
java版工程管理系统 Spring Cloud+Spring Boot+Mybatis实现工程管理系统源码
java版工程管理系统Spring CloudSpring BootMybatis实现工程管理系统 工程项目各模块及其功能点清单 一、系统管理 1、数据字典:实现对数据字典标签的增删改查操作 2、编码管理:实现对系统编码的增删改查操作 3、用户管理:管理和…...
附录3-大事件项目后端-项目准备工作,config.js,一些库的简易用法,main.js
目录 1 一些注意 2 创建数据库 3 项目结构 4 配置文件 config.js 5 参数规则包 hapi/joi与escook/express-joi 5.1 安装 5.2 文档中的demo 5.2.1 定义规则 5.2.2 使用规则 5.3 项目中的使用 5.3.1 定义信息规则 5.3.2 使用规则 6 密码加密包 bcrypt.…...
并发编程-线程
并发编程-线程 一个进程是操作系统中运行的一个任务,进程独立拥有CPU、内存等资源一个线程是一个进程中运行的一个任务,线程之间共享CPU、内存等资源,进程里的每一个任务都是线程。 线程创建 创建线程:使用threading模块中的Th…...
图解LeetCode——剑指 Offer 34. 二叉树中和为某一值的路径
一、题目 给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。叶子节点 是指没有子节点的节点。 二、示例 2.1> 示例 1: 【输入】root [5,4,8,11,null,13,4,7,2,null,null,5,1], t…...
使用Python免费试用最新Openai API
一、背景介绍 3月2日凌晨,OpenAI放出了真正的ChatGPT API,不是背后的GPT-3.5大模型,是ChatGPT的本体模型!ChatGPT API价格为1k tokens/$0.002,等于每输出100万个单词,价格才2.7美金(约18元人民…...
04、启动 SVN 服务器端程序
启动 SVN 服务器端程序1 概述2 用命令行单项目启动2.1 采用 svnserve 命令2.2 验证服务是否启动2.3 命令行方式的缺陷3 注册Windows服务3.1 注册服务的命令3.2 命令说明3.3 启动服务1 概述 SVN 服务器和 Tomcat 服务器,Nexus 服务器一样, 必须处于运行状态才能响应…...
jsp船舶引航计费网站Myeclipse开发mysql数据库web结构java编程计算机网页项目
一、源码特点 JSP船舶引航计费网站是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0&…...
Allegro如何画半圆形的线操作指导
Allegro如何画半圆形的线操作指导 在用Allegro设计PCB的时候,在某些应用场合会需要画半圆形,如下图 如何画半圆形,具体操作如下 点击Add点击Arc w/Radius...
【强烈建议收藏:MySQL面试必问系列之SQL语句执行专题】
一.知识回顾 之前的文章我们一起学习了MySQL面试必问系列之事务专题、锁专题,没有学习的小伙伴可以直接通过该链接地址直接访问,MYSQL你真的了解吗专栏的文章,接下来我们就一起来学习一下MySQL中SQL语句的执行流程,看看你掌握的怎…...
详解Linux下的环境变量以及C++库文件和头文件、python库的配置
目录 Linux环境变量配置基本步骤 1.查看环境变量 2.设置环境变量 3.永久性设置环境变量 4.使用环境变量 C 库文件和头文件环境变量配置 1.配置so库文件的环境变量 2.配置头文件的环境变量 Python库环境变量配置 Linux配置执行文件环境变量 我们都习惯在Windows 上配置…...
企业级分布式数据库 - GaussDB介绍
目录 什么是GaussDB 简介 应用场景 产品架构 产品优势 安全 责任共担 身份认证与访问控制 数据保护技术 审计与日志 监控安全风险 故障恢复 认证证书 GaussDB与其他服务的关系 约束与限制 计费模式 什么是GaussDB …...
Linux I2C 驱动实验
目录 一、Linux I2C 驱动简介 1、I2C 总线驱动 2、I2C 设备驱动 1、 i2c_client 结构体 2、 i2c_driver 结构体 二、硬件分析 三、设备树编写 1、pinctrl_i2c1 2、在 i2c1 节点追加 ap3216c 子节点 3、验证 四、 代码编写 1、makefile 2、ap3216c.h 3、ap3216c.c …...
DC-DC模块电源隔离直流升压高压稳压输出5v12v24v转60v100v110v150v220v250v300v400v500v
特点效率高达80%以上1*1英寸标准封装单电压输出稳压输出工作温度: -40℃~85℃阻燃封装,满足UL94-V0 要求温度特性好可直接焊在PCB 上应用HRB 0.2~10W 系列模块电源是一种DC-DC升压变换器。该模块电源的输入电压分为:4.5~9V、9~18V、及18~36VDC标准&#…...
EF有几种模式,EF的三种模式分别是什么?
EF有几种模式,EF的三种模式分别是什么? 第一种:DataBase First DataBase First传统的表驱动方式创建EDM,然后通过EDM生成模型和数据层代码。除生成实体模型和自跟踪实现模型,还支持生成轻型DbContext。 解释…...
数据可视化展示:打工人常见职业病,颈腰椎病占比最高达66.51%
身体健康才是一切的根本。只有身体健健康康才能更好的去享受世间的美好,无论是谁都应当注重身体健康,而不是无度的挥霍它! 良好的身体,释放给工作,健壮的体魄,享受美好生活,良好的心态ÿ…...
【食品图像识别】Large Scale Visual Food Recognition
1 引言 视觉智能部与中科院计算所于2020-2021年度展开了《细粒度菜品图像识别和检索》科研课题合作,本文系双方联合在IEEE T-PAMI2023发布论文《Large Scale Visual Food Recognition》 (Weiqing Min, Zhiling Wang, Yuxin Liu, Mengjiang Luo, Liping Kang, Xiaom…...
RAN-in-the-Cloud:为 5G RAN 提供云经济性
RAN-in-the-Cloud:为 5G RAN 提供云经济性 5G 部署在全球范围内一直在加速。 许多电信运营商已经推出了5G服务并正在快速扩张。 除了电信运营商之外,企业也对使用 5G 建立私有网络产生了浓厚的兴趣,这些私有网络利用了更高的带宽、更低的延迟…...
vector、list、queue
引用:windows程序员面试指南 vector vector 类似于C语言中的数组 vector 支持随机访问,访问某个元素的时间复杂度 O(1) vector 插入和删除元素效率较低,时间复杂度O(n) vector 是连续存储,没有内存碎片,空间利用率高…...
操作系统面经
进程与线程区别 1.进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位) 2.进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数…...
一天约了4个面试,复盘一下面试题和薪资福利
除了最新的面经分享,还有字节大佬的求职面试答疑,告诉你关键问题是什么?少走弯路。**另外本文也汇总了6份大厂面试题:字节、腾讯、小米、腾讯云、滴滴、小米游戏。**希望对大家有帮助。 前言 昨天我的交流群里,有位宝…...
详解单链表(内有精美图示哦)
全文目录引言链表链表的定义与结构链表的分类单链表的实现及对数据的操作单链表的创建与销毁创建销毁单链表的打印单链表的头插与头删头插头删单链表的尾插与尾删尾插尾删单链表的查找单链表在pos位置后插入/删除插入删除单链表在pos位置插入/删除插入删除总结引言 在上一篇文…...
csdn文章导航
这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…...
【Spring】掌握 Spring Validation 数据校验
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ Spring Validation 数据校验一、什么是 Spring…...
定语 从句
回顾能作定语的成分 形容词:She is a responsible girl.她是一个负责任的姑娘。(前置定语) The girl responsible was expelled.对此负责的姑娘被开除了。(后置定语) 代词:Whose f…...
【数据可视化工具】浅谈 DataEase 和 FineBI 支持的数据源
前言最近对市面上比较火热的数据可视化工具 DataEase 和 FineBI 进行了调研,在支持的数据源方面感觉不太一样,所以就有了这篇文章,话不多说,我们一起来看一下吧!以下的内容,大多来自两个工具的官方文档&…...
100种思维模型之上帝视角思维模型-025
惊奇、愤怒、郁闷,我们觉得生活不精彩,事情乱作一团,但这仅仅是视角问题而已。 换个视角,可以看到不同的世界。 “上帝视角思维模型”,即以一个更高、更客观、更理性的角度来看问题,从而做出理性的决策。 …...
从这5个方面,总结我当PM的第一年
以下5个方面(学习、思考、沟通、执行、产品)的分享,都是我站在巨人的肩膀上,结合自己所学所做总结而来;同时,我也继续学习,不断完善这些知识。如有不当,欢迎大家指正~一、学习&#…...
网站建设三网合一/代运营公司
原 Linux 修改 IP地址 和 网关https://blog.csdn.net/tianlesoftware/article/details/5312646版权声明: https://blog.csdn.net/tianlesoftware/article/details/5312646 修改IP地址和网关是很常见的操作,在做相关实验的时候,如果没有设置…...
网站的网页声明怎么做/seo怎么刷关键词排名
java中的异常处理机制 异常在java中以类和对象的形式存在,那么异常的继承结构是怎样的?我们可以使用UML图来描述以下继承结构 画UML图的工具:Rational Rose、starUML等 Object下有Throwable(可抛出的) Throwable下有两…...
自己做网站教程/seo的形式有哪些
1.send 函数 int send( SOCKET s, const char FAR *buf, int len, int flags ); 不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。 该函数的第一…...
长沙建设网站企业/网店培训
(一)apache 介绍Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,Apache也叫万维网,www服务器, web服务器主要功能是提供网上信息浏览服务。Apache可以在大多数计算机操作系统中运行&…...
wordpress文章meta/自己怎么做引流推广
一个朋友是前阿里人,37岁,离职后就职美团。以前投一个面一个,今年想跳槽,但没想到投十个能有两个面试机会就不错了,最后索性又回了阿里做架构。 他在面试的时候,碰见比自己大的面试官,态度和善&…...
德宏企业网站建设公司6/房地产新闻最新消息
C#中的快捷键,可以更方便的编写代码//CTRL SHIFT B 生成解决方案//CTRL F7 生成编译//CTRL O 打开文件//CTRL SHIFT O 打开项目//CTRL SHIFT C 显示类视图窗口//F4 显示属性窗口//SHIFT F4 显示项目属性窗口//CTRL SHIFT E 显示资源视图//F12 转到定义//…...