目录

  • 1.珊瑚海介绍
  • 2.与其他框架的对比
  • 3.客户端引擎
  • 4.管理后台
  • 5.低代码前端
  • 6.打造更易用的组件库
  • 7.生态规划

1. 珊瑚海介绍

CoralSea官网: http://doc.58corp.com/CoralSea

珊瑚海是安居客发起,58无线团队参与共建的一站式动态布局框架,支持 Android、iOS、小程序、H5. 包含引擎框架、DSL 管理后台、可拖拽低代码前端、JS 开发框架等全套基础能力。适用于 UI 交互、动画复杂性较低、布局动态要求高的页面。

一站式工具链:

  • 轻量级,快速接入

接入改造成本低,仅需少量的代码进行接入,包大小只增加 100k

  • 跨平台

底层使用 Yoga,支持 Android、iOS、小程序、H5,高度的 UI 一致性

  • 性能优

高性能,Yoga 底层 C++ 实现,可用于首页、混合页面,列表滑动丢帧率低

  • 替换客户端列表、卡片开发模式

新的列表、卡片样式,可以内置在 App 中,提前编译成原生代码(低代码+跨端+性能),无 DSL 加载解析过程,扁平化性能更优

  • 丰富的基础组件、事件,支持自定义

基础组件、事件丰富,支持注册自定义组件、事件

  • 支持卡片级别的动态化,高可复用性

支持基于卡片级别的动态化,DSL 文件更小。高复用性,一套卡片可用于不同页面。DSL 前端提供卡片仓库,开发、上线更快捷

  • 支持页面的动态化

少量代码即可让整页面作为布局动态化载体

  • DSL 管理后台一站式上线发布

提供 DSL、模版创建、修改、发布的一站式服务

  • 低代码前端支持拖拽、sketch 导入

低代码前端操作便捷,适用于各种角色

  • 低代码前端支持 RN、Flutter 对接

打造大前端 DSL 生态

1.1 58同城预接入效果

DSL 管理后台 + 低代码前端:

效果视频

App:

效果视频

描边卡片为珊瑚海卡片:

1.2 安居客落地业务

珊瑚海房价卡片页面:

  • 一套DSL跨Android/iOS,跨58App/安居客,开发成本节省 50%
  • 体验与原生一致,具备 RN 的热更新能力,性能优于 RN 30%

房产909项目经纪人门店单页

  • 使用珊瑚海 + BFF,9月上线以来热修复问题1次,覆盖率 100%

整改需求- 删除当前APP的服务痕迹

  • 跨 Android & iOS,表单展示类页面,0.5人/日快速开发完成

1.3 后期落地计划

# 业务 解决的痛点
1 58同城厂商包 1. 快速响应厂商问题 2. 优化包大小
2 58同城、58本地版市场包 1. 瀑布流卡片动态化,为多元化场景赋能 2. 高效率编译期 DSL,提供一种全新的 UI 开发模式

2. 与其他框架的对比

2.1 布局动态化你要理解的一些概念

目前市面上的跨端框架层出不穷,RN、Hybrid、Flutter、小程序,除了需要具备跨端的能力,在动态性、多端一致性、性能、包大小等方面要求也越来越高。每种框架都有自己独特的优势和不足,不同公司、不同业务、不同时期在这些框架的选择上都存在不同。

在评价跨端框架上,我们一般会从几个方面来看:

  • 研发效率
  • 动态化
  • 多端一致性
  • 性能体验
  • 生态

那么为什么会衍生出布局动态化这种框架呢?以 58同城 APP 为例,产品需要快速验证 UI 效果,运营需要经常上线各种活动。

主流的跨端框架大多过重,无论是 H5 还是 RN、Flutter 类的方案,它们动态化能力的关键在于随时可发布代码,是面向开发的动态化方案,发布代码意味着开发、测试、灰度等一系列流程都要完整跑通,显然难以满足产品和运营的诉求。

同时在现有工程中使用,对于包大小、接入成本、性能等有着严格地限制,RN 当前的版本依托于 Bridge,无法用于首页 (最新版使用 JSI、Hermes,但内存占用是问题),Flutter 与混合工程对接、动态化、包大小、运行内存一直被诟病,H5 性能更差,冗长的栅格化绘制流程慢的让人抓狂。

当然每种框架都有特定适用的场景,一般大型 APP 都是多套框架并存。

当然中间我们也做过一些尝试,比如基于 Android App Bundle + Qigsaw 的任意门、Mocha,实现了基于版本级别的线上 AB 测能力:

业务线接入情况:无线、招聘、部落、二手车
使用情况:任意门进行 bug 修复 3次,AB 测 6次
动态包覆盖速度:动态包安装量 7天 能覆盖活跃用户的 90% 以上

任意门、Mocha 性能与原生无异,具备彻底地动态化能力 (dex、so、资源),但其存在一些限制,特别是在纯布局动态化场景下:

  • 只支持 Android
  • 基于版本级别,PM 如果想跨版本连续验证效果,则需要打包、上线多次

2.2 布局动态化框架需要具备哪些能力?

  • DSL 布局描述文件: 一般为 json(简单易用)、xml(直观)、ProtoBuf(性能、大小) 格式。无论是 H5 还是 RN,动态化都需要有描述页面结构的语言。H5 和 RN 为了追求开发模式的完备,各自定义了一套通用编程语言 (GPL),比如 RN 的 JSX.

  • 可视化编辑前端/工具: 用于编辑产出 DSL

  • 管理后台: 用于产品管理、DSL 管理,可快速编辑、发布上线

  • 端侧引擎: 基础的组件、事件,下载、解析、运行 DSL

  • 可扩展能力: 组件、事件等业务可定制扩展

2.3 布局动态化框架间的对比

Tangram、VirtualView

Tangram、VirtualView 是业内比较知名的布局动态化框架,同时其具备其他框架没有的组件动态更新能力,所以这边和它对比一下。

优点:

  • 跨端支持:支持
  • 性能: 性能优,底层 C 实现,同时具备扁平化能力
  • 组件与动态能力:内置布局、组件丰富,开放的 API,同时 VirtualView 具备动态更新组件的能力

缺点:

  • 更新维护:最新更新在 2020/12
  • DSL 前端、管理后台未开源

珊瑚海

除了不支持组件的动态更新之外,在跨端支持、性能、内置的布局组件等方面和 Tangram 相差不大。同时属于内部团队持续维护,具备全套的开发组件 (可拖拽低代码前端、管理后台)

分别查看 Native 原生、Yoga、Tangram 在同一列表样式中的 fps 数据:

丢帧数和丢帧率结果为:

Tangram(3.87%) > Native(2.30%) > Yoga(1.43%)

Native 丢帧率比 Yoga 高的原因为 Native 布局编写层级过深导致,无扁平化处理,在布局、测量方面有性能丢失,合理的布局优化手段可以减少丢帧率

再对比一下 Native 原生、Yoga、Tangram 在同一列表样式中过度绘制数据:

可以看到 Yoga、Tangram 因为都做了扁平化处理,所以在过度绘制方面表现不错。

2.4 混合开发中在原生列表上的表现

与 native 混合开发时,在列表滑动性能上面的表现:

框架 性能 原因
RN RN 几乎无法混嵌在原生列表中(一般也不会这么做,会使用 RN 自身的列表组件),除了内存问题,即使在纯 RN 列表的场景下,频繁地 jsBridge 交互也会导致体验很差,涉及到大量的序列化与反序列化操作
Flutter 和 RN 相似,Flutter 也不会作为独立卡片嵌入在原生列表中,在纯 Flutter 列表场景下,性能仅次于原生,绘制使用 skia 引擎无需与 native 做频繁通信
Tangram 较好 可以混嵌在原生列表中使用,无 bridge 通信,在列表中可进行视图复用
珊瑚海 较好 和 Tangram 类似

2.5 珊瑚海对比 RN

珊瑚海无法与 RN 这种大型成熟的框架做对比,两者的侧重点和使用场景也不同,不过都是基于 Yoga 进行绘制,在这边做一下简单的对比:

框架 RN 珊瑚海
交互性能 性能较差,特别是在频繁交互的场景下,需要依靠 JSBridge 做通信,不过 RN 即将发布的新版,使用了 JSI,可以直接在 JS & Native 同步互调,无内存拷贝 使用 Yoga 布局,原生组件绘制,无 bridge 通信,与原生无异
代码运行性能 性能较差,JS 单线程语言,同时 JIT 编译器在运行时通过转换成机器码的方式优化 JS 代码,不过 RN 新版本已支持 hermes 引擎,提前将产物转换成字节码,放弃了 JIT 方式 原生代码执行,对比原生有劣势的地方就是加载、解析 DSL 过程无过多的 AOT 优化
启动性能 性能较差,JIT 需要在启动时预热、RN 引擎初始化较慢,且初始化时所有 module 一次性加载,不过 hermes 引擎与新版的 RN 框架解决了这些问题 原生代码执行,无耗时初始化操作
绘制性能 高性能被 bridge-> Yoga -> 原生组件-> 的链路折损 使用 Yoga 布局,原生组件绘制,与原生无异
内存占用 性能较差,在混合工程中,每个载体页都持有一个 RN 引擎、JSC,业内也有一些解决方案,比如引擎复用,但存在不少的坑 比原生略多,加载、解析会占用一些内存,但无其他过多内存消耗
动态化能力 强,iOS 规定 Webkit 和 JavascriptCore 可以动态执行下发的脚本和文件,其它所有脚本/代码/解释器都必须打包在 APP 内部,但依赖于原生的部分动态化能力较弱 布局动态化能力强,但无法更新基础组件,同时逻辑动态化能力弱
完善程度 强,成熟的框架、社区,完备的开发、调试组件 较完善,目前缺少完备的热部署、即时预览能力

3.客户端引擎

介绍客户端引擎设计时,我们先来看下珊瑚海整体架构:

3.1 客户端引擎架构图

3.2 DSL 设计

  • 基础组件:Text,Image, View,List,ViewPager,Span
  • ⾃定义组件:轮播头图,titleBar
  • 基础 Action:setPropsById,condition, JSEval …
  • ⾃定义 Action:invokeApi, jump, sendLog,getLocalData …

示例:

{"node": {"name": "View","id": "700-003","layout": {"height": "100%","width": "100%","flexDirection": "column","alignItems": "center","justifyContent": "center"},"style": {"backgroundColor": "#ffffff"},"child": [{"name": "View","id": "700-0017","layout": {"width": "100%","height": "50","flexDirection": "column","alignItems": "center","justifyContent": "center"},"style": {},"child": [{"name": "View","id": "700-0031","layout": {"width": "100%","flexDirection": "row","alignItems": "center","justifyContent": "center"},"child": [{"name": "Image","id": "700-0039","layout": {"width": "28","height": "28","marginLeft": "8"},"props": {"url": "https://pic3.58cdn.com.cn/nowater/fangfe/n_v2a5955cca5b41421a87cbc06261fb7290.webp"},"event": [{"name": "onClick","action": [{"type": "Action","value": "goBack","params": [{"type": "String","value": {}}]}]}]}]},"api": [{"name": "communityInfo","method": "GET","url": "https://api.anjuke.com/community/info"}],"lifeCycle": {"onCreate": []}
}

3.3 DSL 引擎

布局

基于 Flexbox 语法(弹性布局,跨端),Yoga(RN 使用的渲染引擎)渲染,高性能,底层 C++;扁平化

数据绑定:

⾃定义 Action

注册到 ActionManager,继承 BaseAction,注册/静态类⽅法

逻辑动态

集成轻量级 QuickJS 引擎,Action: JSEval(script, params…)

3.4 全新的移动端 UI 开发模式

传统的移动端开发 UI 一般分为以下几步:

从流程中可以看出,传统的开发模式存在以下几个问题:

  • Android & iOS 需要对一份 UI 设计在对应平台分别进行开发
  • 单平台开发时,也存在重复输入,每一个 UI 都需要对应编写视图描述文件、数据实体、数据解析、适配器绑定 UI 数据等逻辑

使用布局动态化开发 UI 的方案优点:

  • 跨端支持,支持 Android & iOS,同一份 UI 只需要编写一份 DSL 文件即可在两端运行
  • DSL 文件支持数据绑定能力,无需重复编写视图查找、数据实体、数据解析、绑定的代码
  • 如果不考虑动态更新问题,也可以将 DSL 文件内置在移动端本地来解决本文章提到的问题

但在动态布局方案中,强调的更多是布局的动态化,其加载、解析 DSL 存在部分耗时:

以 500行的 DSL 文件为例,在中高端手机运行时加载 + 解析文件耗时达到 70ms(其实这个过程和原生相差不大,原生的优势在于系统 AOT 的加持)

如果不考虑动态化,只是作为内置型应用场景,如何优化此部分性能呢?

编译期提前将 DSL 文件转换成原生代码,省去加载、解析的时间,同时又可以更多地享受系统 AOT 的加持,缺点就是 dex 变大, 加长 classloader 加载时间

这边转换的是只是 DSL 对应的 JSON 对象的代码,而没有转换 JSON 对应的每个视图的代码,主要是因为更灵活,无需重复编写 DSL 运行时解析视图的代码,且 DSL 解析成 JSON 对象后,对应的构建 View 等操作和原生差异不大。

实验数据,同样的 DSL 文件,JIT 模式优化到 8ms, AOT 模式优化到 1ms.生成的代码示例:

class RevertToJsonTest {/*** output:* {"node":{"layout":{"alignItems":"center","flexDirection":"column","width":"100%","justifyContent":"center","height":"100%"},"name":"View","style":{"backgroundColor":"#ffffff"},"id":"700-003"},"api":[{"method":"GET","name":"communityInfo","url":"https://api.anjuke.com/community/info"}],"lifeCycle":{"onCreate":[]}}** Test on mobile: load and parse json 80ms, revert map to json: JIT 6ms, AOT 1ms, save 92% - 98%*/public static void main(String[] args) {long startTime = System.currentTimeMillis();Map<String, Object> resultMap = new HashMap<>();Map<String, Object> node17645087271206Map = new HashMap<>();resultMap.put("node", node17645087271206Map);Map<String, Object> layout17645087328278Map = new HashMap<>();node17645087271206Map.put("layout", layout17645087328278Map);layout17645087328278Map.put("alignItems", "center");...._17645091636381Map.put("style", style17645091785331Map);style17645091785331Map.put("color", "#0B0F12");...._17645091636381Map.put("id", "700-00244");_17645093309193Map.put("layout", layout17645093317330Map);layout17645093317330Map.put("marginRight", "16");...._17645093309193Map.put("name", "Text");Map<String, Object> style17645093400202Map = new HashMap<>();_17645093309193Map.put("style", style17645093400202Map);style17645093400202Map.put("color", "#0B0F12");style17645093400202Map.put("fontSize", "16");_17645093309193Map.put("id", "700-00389");Map<String, Object> props17645093441338Map = new HashMap<>();_17645093309193Map.put("props", props17645093441338Map);props17645093441338Map.put("text", "区别于组件,模版指的是可复用的DSL代码段,是现有DSL组件能力的组合和复用");Map<String, Object> _17645093462008Map = new HashMap<>();child17645090142087Array[14] = _17645093462008Map;Map<String, Object> layout17645093470302Map = new HashMap<>();_17645093462008Map.put("layout", layout17645093470302Map);layout17645093470302Map.put("marginRight", "16");...._17645093462008Map.put("name", "Text");Map<String, Object> style17645093573580Map = new HashMap<>();_17645093462008Map.put("style", style17645093573580Map);style17645093573580Map.put("color", "#0B0F12");...._17645093462008Map.put("id", "700-00408");Map<String, Object> props17645093625570Map = new HashMap<>();_17645093462008Map.put("props", props17645093625570Map);props17645093625570Map.put("text", "珊瑚海Bundle DSL描述文件");Map<String, Object> _17645093647561Map = new HashMap<>();child17645090142087Array[15] = _17645093647561Map;Map<String, Object> layout17645093656085Map = new HashMap<>();_17645093647561Map.put("layout", layout17645093656085Map);...._17645093647561Map.put("name", "Text");Map<String, Object> style17645093736060Map = new HashMap<>();_17645093647561Map.put("style", style17645093736060Map);style17645093736060Map.put("color", "#0B0F12");style17645093736060Map.put("fontSize", "16");_17645093647561Map.put("id", "700-00430");Map<String, Object> props17645093779067Map = new HashMap<>();_17645093647561Map.put("props", props17645093779067Map);props17645093779067Map.put("text", "指的是包含珊瑚海Bundle DSL描述、版本、依赖视图组件版本等信息的JSON文件,需要随珊瑚海Bundle DSL一起上传平台");Map<String, Object> _17645093807173Map = new HashMap<>();child17645090142087Array[16] = _17645093807173Map;Map<String, Object> layout17645093816175Map = new HashMap<>();_17645093807173Map.put("layout", layout17645093816175Map);layout17645093816175Map.put("marginRight", "16");...._17645093807173Map.put("name", "Text");Map<String, Object> style17645093952708Map = new HashMap<>();_17645093807173Map.put("style", style17645093952708Map);style17645093952708Map.put("color", "#0B0F12");style17645093952708Map.put("fontSize", "16");style17645093952708Map.put("fontWeight", "bold");_17645093807173Map.put("id", "700-00449");Map<String, Object> props17645094058650Map = new HashMap<>();_17645093807173Map.put("props", props17645094058650Map);props17645094058650Map.put("text", "珊瑚海Bundle DSL版本映射表");resultMap.put("lifeCycle", lifeCycle17645094899247Map);Object[] onCreate17645094910216Array = new Object[0];lifeCycle17645094899247Map.put("onCreate", onCreate17645094910216Array);// new JSONObject by mapJSONObject jsonObject = new JSONObject(resultMap);System.out.println("RevertToJsonTest revert cost time: " + (System.currentTimeMillis() - startTime));System.out.println("map revert to json: \r\n: " + jsonObject.toJSONString());}
}

4. 管理后台

4.1 一站式全链路闭环

平台管理 -> 低代码前端 -> 各端引擎,一站式开发部署上线

多平台、多端

打通低代码前端

快速上线发布

4.2 业务服务可配

各业务可接入管理平台 API,部署自己的 DSL 服务,保证高流量、高并发请求。

4.3 一份 DSL,多端复用

基于 DSL 文件级别的复用,项目管理分开,做到复用的核心为多端引擎的对齐,引擎的向下兼容性。

优点:

  • 移动端、小程序、h5 版本节奏不一致,分开管理灵活度高。出现问题方便回滚,不会牵一发动全身。
  • 平台版本、引擎版本、DSL 版本逻辑复杂,揉合在一起系统会非常复杂难以管理。这样设计同时可减少后端复杂度,出现问题易排查。
  • 后台预留了直接上传的入口,开发工作量可节省。

缺点:增加 DSL 管理、上线的工作量。

4.4 插拔式组件库

插拔式组件库,严苛地版本管理

业务可以定制自己的组件库,并进行版本迭代,在编辑 DSL 文件时,会根据版本规则匹配可用的组件库:

低代码前端动态加载对应组件库:

4.5 共建共享型模版

类比应用市场主题,大家可以分享自己的 DSL 模版,一起快速构建美观的视图

5. 低代码前端

5.1 架构设计

其中低代码平台负责:

  • 加载组件库
  • 拖拽、鼠标事件
  • canvas 绘制(布局、位置、margin、padding)
  • 代码编辑
  • 操作历史记录
  • 模版管理
  • 生成 dsl

组件库负责:

  • 拥有哪些组件,每个组件的属性、绘制,和端侧引擎同步迭代

5.2 功能拆解

6.打造更易用的组件库

接入方可以按照规范开发自己的组件库,目前我们厂商业务正在开发一款更通用、更易用的组件库,完成后将共享给更多业务使用:

组件说明:

# 组件名称 描述
1 View 容器
2 Image 图片
3 Link 链接
4 Text 文本
5 TextEditor 文本输入框
6 Span 图文混排
7 RadioButton 单选按钮
8 RadioGroup 单选按钮组
9 CheckBox 复选按钮
10 Switch 状态切换按钮
11 List 列表,需要配合 ListGridItem 使用,支持多 item 样式
12 LoopList 循环列表,需要配合 ListGridItem 使用,适合固定 item 样式
13 GridView 网格视图,需要配合 ListGridItem 使用,支持多 item 样式
14 ListGridItem List/Grid item 视图

7.生态规划

  1. 完成 H5、小程序端引擎
  2. 推广全新的列表/卡片 UI 开发模式
  3. 低代码平台支持开源项目 Fair 的 DSL 编辑,打造大前端生态

珊瑚海 - 一站式跨端动态化布局框架原理相关推荐

  1. 【跨端动态化技术知多少】

    跨端动态化技术知多少 文章摘要:本文对业界跨端动态化技术做了一些归拢,方便志同道合的朋友一起学习交流.开拓视野.附录有收录一些技术专栏,感兴趣的朋友可以收藏. 技术路线一.自渲染 Flutter之上套 ...

  2. 珊瑚海-一站式动态化布局框架

    珊瑚海介绍 CoralSea官网: http://doc.58corp.com/CoralSea 珊瑚海是安居客发起,58无线团队参与共建的一站式动态布局框架,支持 Android.iOS.小程序.H ...

  3. 全自研客户端技术方案:优酷跨端动态模板引擎优酷跨端动态模板引擎

    前言 优酷客户端是一个多平台[Phone.Pad.OTT.MacPC]的文娱生态综合体,为了降低多端产品迭代的开发成本,并提供给用户高性能.一致的产品体验,优酷技术团队在19年底启动了跨平台动态模板引 ...

  4. 【2万字长文】深入浅出主流的几款小程序跨端框架原理

    开发者(KaiFaX) 面向全栈工程师的开发者 专注于前端.Java/Python/Go/PHP的技术社区 作者 | 雾豹 来源 | https://juejin.im/post/6881597846 ...

  5. Taro3跨端跨框架原理初探

    背景 在项目中,我们用到了Taro3进行跨端开发,在这里分享下Taro3的一些跨端跨框架原理,也是希望能帮助自己更好的去理解Taro背后的事情,也能加速定位问题.此文章也是起到一个抛砖引玉的效果,让自 ...

  6. 深入浅出主流的几款小程序跨端框架原理

    目前,小程序在用户规模及商业化方面都取得了极大的成功.微信.支付宝.百度.字节跳动等平台的小程序日活都超过了3亿. 我们在开发小程序时仍然存在诸多痛点:小程序孱弱简陋的原生开发体验,注定会出现小程序增 ...

  7. 前端技术周刊 2019-01-21:跨端开发的三条路线

    2019-01-21 前端快爆 微软 Edge 开发者意图为 Chrome 实现 HTML Modules,该规范用来替代之前的 HTML Imports.其优点是基于 ES Modules,可以避免 ...

  8. harmonyos不用jvm,关于harmonyos:DevEco-Studio-20为跨端应用高效开发设计

    12 月 16 日,万众期待的 HarmonyOS 2.0 手机利用开发者 Beta 版本在北京正式公布.与此同时,作为手机开发者 Beta 版本的配套 IDE 工具,HUAWEI DevEco St ...

  9. 目前主流跨端开发技术一览

    关键词:React Native, uniapp, Flutter ,Tauri, Ionic 和 weex 文章目录 前言 结论 几种常见跨端技术对比 加快ReactNative开发的Expo Ta ...

最新文章

  1. 电子计算机的发展与应用教案,川教版信息技术七上第3课《电子计算机的发展与应用》教案1.doc...
  2. 性能测试Loadrunner与Mysql
  3. 密码学专题 证书和CA指令 申请证书|建立CA|CA操作|使用证书|验证证书
  4. android logcat 根据包名过滤,adb logcat通过包名过滤(dos命令find后跟变量)
  5. Spring Security Oauth2 之密码模式
  6. python类和对象_Python类和对象
  7. Mac下mysql登陆问题
  8. 哪些英语母语者常用的词组对于普通中国大学生来说是生疏的?
  9. javascript 数组操作函数
  10. Python 定时任务的几种实现方式
  11. Powershell 创建NetWork Location(盘符快捷方式)
  12. NTFS,FAT32和exFAT文件系统的区别
  13. C-V2X 技术介绍
  14. 深度学习—利用TensorFlow2实现狗狗品种品种(DenseNet121实现)
  15. 安装TensortFlow并配置到PyCharm中
  16. 子类、父类各种方法的执行顺序
  17. SpringAOP所支持的AspectJ切点指示器
  18. (一)框架搭建,前端路由设置,自定义寻找指定路径(Django+Vue+Mysql,数据库管理数据分析网站)
  19. 魔坊APP项目-25-种植园,植物的状态改动、当果树种植以后在celery的异步任务中调整浇水的状态、客户端通过倒计时判断时间,显示浇水道具
  20. 哈尔滨小学计算机上课时间,哈市中小学各校新学期作息时间调整汇总,看看有没有你的学校!...

热门文章

  1. API跟单社区跟单技术经验
  2. MySQL修改端口号
  3. python基于PHP+MySQL的在线汽车租赁管理系统
  4. confuse(iOS马甲包混淆,上架神器)
  5. 2、mac上安装配置git
  6. linux误删表空间文件,不小心删除了表空间数据文件   如何处理
  7. 3D数学-裁剪空间与透视投影矩阵的推导
  8. 【小程序】使用WXSS编写样式介绍以及与CSS的区别
  9. (转)IOS 7 Xcode 5 免IDP证书 真机调试
  10. X射线衍射特点有什么?