程序员首页投稿APP开发经验总结安卓xuexi

Android Virtualview:淘宝、天猫又开源了一个动

2018-02-24  本文已影响7464人  Carson带你学安卓

前言

电商图

若还不了解Tangram模型 和 vlayout,具体请看文章

  1. Android Tangram模型:连淘宝、天猫都在用的UI框架模型你一定要懂
  2. Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!

Virtualview的Github地址


目录

示意图

1. 为什么要向 Tangram模型 加入 VirtualView

即 为什么要更新 Tangram2.0版本

  1. 提升组件动态性,实现动态更新
  2. 提升了组件的渲染性能

而上述解决方案的承载方案,则是 VirtualView

VirtualView的Github地址


2. VirtualView介绍

示意图

3. 实现原理

3.1 核心思路

根据Tangram v1.0中 出现的问题:UI组件无法动态更新 & 加载性能低,VirtualView的具体解决方案如下

示意图

3.2 实现方案

示意图

之所以称为虚拟化,是因为Canvas绘制的视图不存在一一对应的实体View

3.3 总结

从上可知,VirtualView的创新在于:

  1. 通过 XML 模板实现组件的动态性
  2. 通过 虚拟化技术(本质 = Canvas)开发组件,提升了组件的渲染性能

4. 工作流程

4.1 流程概述

示意图

4.2 流程详细分析

下面我将对每个流程的原理 & 过程详细分析

流程1:创建UI组件

示意图

有2种创建方式:使用框架内置(封装好)的UI组件 / 自定义

1.1 使用框架内置(封装好)的UI组件

注:
a. 自定义组件应继承基础组件
b. 系统封装UI组件的原理 同 “自定义UI组件,下面将具体讲解

示意图

1.2 自定义UI组件

若框架内置的UI组件无法满足需求,则开发者可自定义UI组件

即 上述则是虚拟化创建组件的过程

  1. 实现基础组件需遵循一个接口的规范:定义了渲染过程中所需的3个流程:测量尺寸阶段、布局阶段 & 绘制阶段

a. 定义这3个阶段是为了符合Android系统的使用,即View绘制的三大流程:measure过程、layout过程、draw过程。若不了解,请看文章
(2)自定义View Measure过程 - 最易懂的自定义View原理系列
(3)自定义View Layout过程 - 最易懂的自定义View原理系列
(4)自定义View Draw过程- 最易懂的自定义View原理系列
b. 在 iOS 平台下也需按照本方案的规范去处理

  1. 这3个过程具体如下:(与Android View绘制的三大流程相似)
    示意图

不论是虚拟 / 原生组件,都采用上述模型 & 流程定义
a. 对于虚拟组件:在这些接口里实现相关逻辑 / 通过封装原生组件实现
b. 对于原生组件:在这些接口的实现里 调用原生组件的对应逻辑
结论:可混合使用虚拟控件 & 实体控件

至此,对于宿主的布局容器来说,包装在内部的组件不分虚拟化 /
原生,暴露在外的接口相同,只要将宿主容器像普通的 View 一样添加到的视图界面上,就可在后续的渲染过程中显示出来。

如下所示的组件:
a. 普通的原生开发:2层(宿主容器层 + 图片组件层)
b. 虚拟化开发:采用虚拟化开发后,最终呈现的 View层级只有一个宿主容器(实际上,图片组件被绘制在Canvas里了)

示意图

1.3 总结

创建UI组件有2种方式:

  1. 直接使用框架内置的UI组件
  2. 自定义组件:通过封装好的Canvas流程,按照指定接口协议实现绘制逻辑 / 封装原生组件

流程2:创建界面模板 & 下发

示意图

2.1 创建XML界面模板

注:需使用专门的工具virtualview_tools编写,其
使用说明见文章virtualview_tools使用指南

// 引用的组件通过流程1中获取
// 动态数据通过表达式从 JSON 数据里获取

<?xml version="1.0" encoding="utf-8"?>
<VHLayout
        flag="flag_exposure|flag_clickable"
        orientation="H"
        layoutWidth="match_parent"
        layoutHeight="wrap_content">
    <NImage
            id="1"
            src="${logoUrl}"
            layoutMarginLeft="8"
            layoutMarginRight="8"
            layoutMarginTop="8"
            layoutMarginBottom="8"
            layoutWidth="32"
            layoutHeight="32"/>
    <NText
            id="2"
            text="${title}"
            layoutGravity="v_center"
            gravity="${style.text-align}"
            textSize="${style.font-size}"
            textColor="${style.color}"
            layoutWidth="match_parent"
            layoutHeight="wrap_content"/>
</VHLayout>


// JSON数据
{
  "style": {
    "text-align": "h_center",
    "font-size": "20",
    "color": "#FF5000"
  },
  "title": "超高性 99.9% 的用户觉得很快",
  "logoUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png"
}


2.2 编译成二进制数据

2.2.1 具体描述

使用专门的工具virtualview_tools将编写好的XML界面模板编译成二进制数据,编译后的文件的后缀名是.out

示意图

使用说明见文章virtualview_tools使用指南

注:为什么通过 XML 编写的业务组件 不直接在客户端里运行使用,而是先进行一次二进制序列化操作?

示意图

2.2.2 二进制文件描述

借鉴了 Android 系统编译模板文件的思路,格式 & 描述具体如下

示意图

2.2.2 编译流程

编译数据 含除内置字符串资源外 它依赖的所有字符串、表达式资源

  1. 原因:当模板在线发布、字符串有变动的情况下,能够不影响原来的字符串资源索引;否则若按照带有顺序约定的协议来分配资源索引,很容易在模板变更时 同一索引值在变更前后指向的资源内容是不一样,影响稳定性和动态性
  2. 序列化的规则如下:
示意图 示意图

2.3 模板数据 下发到客户端

即 客户端获取编译后的二进制数据

获取有2种路径:

  1. 直接将编译后的模板打包到客户端里,开发者通过代码加载
  2. 框架先发布到模板管理后台,客户端在线更新到模板数据(即实现了动态更新)

流程3:客户端加载界面

示意图

3.1 解析模板数据

  1. 如校验版本号,合法性,读取头信息等
  2. 客户端渲染组件 从解析 编译后的模板数据开始

但解析流程只负责提取原始数据 & 组织格式,并无构建出组件对象

示意图

3.2 加载组件视图

示意图

3.3 绑定业务数据

因业务数据是动态的,故从模板创建的组件不含业务数据

  1. 通过表达式解析、访问得到的属性值,会缓存起来,当原始数据引用不变时,每次访问都会获取到缓存值
  2. 此处接收的数据是 JSON 格式
示意图

4.3 总结

示意图

5. 整体架构设计

示意图

模块1:加载模块

模块2:构造模块

此处详细分析 基础组件模型 & 虚拟组件

a. 基础组件模型

含基础组件 & 基础属性,具体如下

注:自定义的基础组件应继承基础定义 & 扩展

示意图

模块3:辅助模块

/**
  * 访问数据属性的表达式
  * 语法说明
  *       a. 以 “${” 开头、以 “}” 结束
  *       b. 对于Map,通过“.”操作符访问
  *       c. 对于 Array  /  List,通过 “[]” 操作符访问
  * 示例如下
  */

  ${benefitImgUrl};
  ${data[0].benefitImgUrl};

/**
  * 条件表达式
  * 作用:根据数据中某个字段 来设置值的属性
  * 语法说明
  *       a. 以 “@{” 开头、以 “}” 结束,
  *       b. 中间部分 = 表达式的具体内容:  条件表达式 ? 结果表达式[1] : 结果表达式[2]
  *           注:1. 当条件表达式成立的时,使用结果表达式[1],否则使用结果表达式[2]
  *              2. 条件表达式支持布尔类型、字符串类型、JSONObject、JSONArray
  *       c. 对于 Array  /  List,通过 “[]” 操作符访问
  * 示例如下
  */
  @{${logoUrl} ? visible : invisible };

模块4:管理模块

模块5:更新模块

配套使用的工具 & 服务

总结

示意图

6. 使用教程

6.1 创建UI组件

从一文可知,创建UI组件有2种方式:

  1. 直接使用框架内置的UI组件
  2. 自定义组件:通过封装好的Canvas流程,按照指定接口协议实现绘制逻辑 / 封装原生组件

此处为方便讲解,直接使用框架内置的UI组件

6.2 创建界面模板

此步骤包括:创建XML界面模板、编译成二进制数据、模板下发

6.2.1 创建XML界面模板

根据业务需求,使用XML编写模板

注:需使用专门的工具virtualview_tools编写,其
使用说明见文章virtualview_tools使用指南

/**
  * 使用说明:
  *     1. 控件引用:通过XML引用控件为方便讲解,XML内引用的VHLayout、NImage、NText 都是框架内置的控件:2个横向线性布局;每个布局 = 1个图 + 1个文本
  *     2. 属性设置:可写死 / 通过表达式绑定一个数据字段(JSON)引用
  * 布局说明:
  *     1. 引用的控件VHLayout、NImage、NText等都是框架内置的控件
  *     2. 整个布局 = 2个横向线性布局,每个布局 = 1个图 + 1个文本
  */

<?xml version="1.0" encoding="utf-8"?>
<VHLayout
        flag="flag_exposure|flag_clickable"
        orientation="V"
        layoutWidth="match_parent"
        layoutHeight="wrap_content">
    <VHLayout
            flag="flag_exposure|flag_clickable"
            orientation="H"
            layoutWidth="match_parent"
            layoutHeight="wrap_content">
        <NImage
                id="1"
                src="${logoUrl}"
                layoutMarginLeft="8"
                layoutMarginRight="8"
                layoutMarginTop="8"
                layoutMarginBottom="8"
                layoutWidth="32"
                layoutHeight="32"/>
        <NText
                id="2"
                text="${title}"
                layoutGravity="v_center"
                gravity="${style.text-align}"
                textSize="${style.font-size}"
                textColor="${style.color}"
                layoutWidth="match_parent"
                layoutHeight="wrap_content"/>
    </VHLayout>
    <VHLayout
            flag="flag_exposure|flag_clickable"
            orientation="H"
            layoutWidth="match_parent"
            layoutHeight="wrap_content">
        <VImage
                id="1"
                src="${logoUrl}"
                layoutMarginLeft="8"
                layoutMarginRight="8"
                layoutMarginTop="8"
                layoutMarginBottom="8"
                layoutWidth="32"
                layoutHeight="32"/>
        <VText
                id="2"
                text="${title}"
                layoutGravity="v_center"
                gravity="${style.text-align}"
                textSize="${style.font-size}"
                textColor="${style.color}"
                layoutWidth="match_parent"
                layoutHeight="wrap_content"/>
    </VHLayout>
</VHLayout>


{
  "style": {
    "text-align": "h_center",
    "font-size": "20",
    "color": "#FF5000"
  },
  "title": "超高性 99.9% 的用户觉得很快",
  "logoUrl": "https://gw.alicdn.com/tfs/TB1yGIdkb_I8KJjy1XaXXbsxpXa-72-72.png"
}

6.2.2 编译成二进制数据

使用专门的工具virtualview_tools将编写好的XML界面模板编译成二进制数据,编译后的文件的后缀名是.out

示意图

使用说明见文章virtualview_tools使用指南

6.2.3 模板下发到客户端

有2种路径:

  1. 直接将编译后的模板打包到客户端里,开发者通过代码加载
  2. 框架先发布到模板管理后台,客户端在线更新到模板数据(即实现了动态更新)

此处选择方式1

6.3 客户端解析 & 加载界面模板

具体使用如下

// 1. 初始化图片加载器
    VafContext.loadImageLoader(mContext.getApplicationContext());

// 2. 初始化 ViewManager 对象
    ViewManager viewManager = vafContext.getViewManager();
    viewManager.init(mContext.getApplicationContext());

// 3. 加载编译后的模板数据(二进制文件)
     // 方式1:直接加载二进制字节数组(推荐使用)
     viewManager.loadBinBufferSync(TMALLCOMPONENT1.BIN);
     viewManager.loadBinBufferSync(TMALLCOMPONENT2.BIN);
     // 方式2:通过二进制文件路径加载
     viewManager.loadBinFileSync(TMALLCOMPONENT1_PATH);
     viewManager.loadBinFileSync(TMALLCOMPONENT2_PATH);

// 4. 注册事件处理器,如常用的点击、曝光处理
    vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {

        @Override
        public boolean process(EventData data) {
            //handle here
            return true;
        }
    });
    vafContext.getEventManager().register(EventManager.TYPE_Exposure, new IEventProcessor() {

        @Override
        public boolean process(EventData data) {
            //handle here
            return true;
        }
    });

// 5. 通过组件名参数 name 生成组件实例
    View container = vafContext.getContainerService().getContainer(name, true);
    mLinearLayout.addView(container);

// 6. 为组件绑定真实的数据
    // 假若您在组件模板里写了数据绑定的表达式
    IContainer iContainer = (IContainer)container;
    JSONObject json = getJSONDataFromAsset(data);
    if (json != null) {
        iContainer.getVirtualView().setVData(json);
    }

下图展示的“超高性 99.9% 的用户觉得很快”即为VirtualView的展示效果

示意图

至此,关于VirtualView的使用讲解完毕

更加详细使用,请参考文章VirtualView的使用文档


7.VirtualView 的意义

对于个人的看法,VirtualView的补充其重大意义在于2个方面:对于 阿里Tangram 模型 & 整个原生开发技术(Android、iOS)

7.1 对于 Tangram 模型

VirtualView的解决的问题 在于:

  1. 实现组件的动态性:可在端上绑定动态下发的 XML 界面模板 & 数据
  2. 提升了组件的渲染性能:通过 虚拟化技术(本质 = Canvas)开发组件

7.2 对于整个原生开发技术(Android、iOS)

VirtualView的创新在于:解决了 原生开发中一直被诟病 而 常被叫喧会被 前端、RN技术取代的问题

  1. 开发周期长 & 成本大
    VirtualView 采用XML描述视图,XML界面模板具备跨平台使用的特性

  2. 无法热更新
    VirtualView可在端上绑定动态下发的 XML 界面模板 & 数据,从而实现热更新

7.3 呼吁


8. 总结

  1. Android Tangram模型:连淘宝、天猫都在用的UI框架模型你一定要懂
  2. Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!

请点赞!因为你的鼓励是我写作的最大动力!

相关文章阅读
Android开发:最全面、最易懂的Android屏幕适配解决方案
Android事件分发机制详解:史上最全面、最易懂
Android开发:史上最全的Android消息推送解决方案
Android开发:最全面、最易懂的Webview详解
Android开发:JSON简介及最全面解析方法!
Android四大组件:Service服务史上最全面解析
Android四大组件:BroadcastReceiver史上最全面解析


欢迎关注Carson_Ho的简书!

不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度

参考文章:
https://juejin.im/post/5a2a7160f265da432c23c4ca
http://tangram.pingguohe.net/docs/virtualview/about-virtualview

上一篇 下一篇

猜你喜欢

热点阅读