唤端技术如何助力双11站外用户转化
NO.1
概要
随着电商大战的持续白热化,用户增长相关的技术建设已经变成各个技术团队都必不可少的部分。对于 App 新用户获取以及流失用户的找回,很大一部分来自于站外的广告导流,这其中有很多场景,我们会通过投放 H5 页面来向 App 导流,而这时就会用到 Web 唤端相关技术。
唤端技术的门槛很低,简单到你只需要拼一个类似 URL 的唤端协议就可以触发唤端;
唤端技术又很复杂,这是因为:
不同的渠道,不同的OS,不同的 App 都有可能针对唤端协议有限制或是要求,这其中有各种各样的兼容性问题;
唤端链路中不同业务可能都有自己的业务定制需求,例如某个业务希望透传业务参数;某业务又希望自己能够定制唤端失败的逻辑;
唤端链路的效率更是被业务关注的核心点,不同场景不同业务在效率上也可能都不一样,因此我们还需要考虑建设有效的唤端效果监测和对比唤端优化效果。
如果用户没有客户端,我们还需要如何引导和帮助用户安装和还原他在站外的场景。
……还有很多需要考虑的细节
今年,我们从手机淘宝的业务出发,针对唤端体系进行了重新梳理并进行了再一次升级,减少对接成本,提升开发效率。而从本次双 11 的效果看,通过新版升级优化,在我们接入的双 11 外投会场和淘宝 H5 版详情的唤端效率(唤端成功率)了均得到不少的提升(相对提升了25~40%不等)。
接下来我们就来详细说说我们这次针对唤端架构的重新设计的考虑和改进。如果你之前没有接触过用户增长领域,也没有关系,我们会由浅入深来说。
NO.2
Web 唤端的链路介绍
唤端链路
唤端就是把用户从客户端外的环境引入到客户端内的过程。
Web 唤端路径
在这个链路上,要唤起客户端,主要就是通过唤端协议,而唤端协议的类型主要有两种:
唤端协议的种类
1. 自定义协议
自定义协议是由 App 开发者自定义的唤端协议,类似于 myapp://path/to/content
。自定义协议方案相对比较成熟。但它的缺点在于如果用户没有安装 App,协议本身没法做降级处理。开发者需要自己设计降级逻辑(例如: 跳转到下载页),但 JS 脚本并不能感知到 App 是否安装和成功唤起,因此我们往往会自定义一个 timeout 时间作为唤端是否成功唤起的口径。
2. 平台标准协议
iOS(Universal Links): 苹果手机在 iOS 9 中引入了 Universal Links,相比自定义协议方案,Universal Links 的形式是一个 Web 链接 (
http://domain.com/xxx
),指向 App 内的某个内容。因此当用户打开 Universal Links 时,iOS 会检查该域中是否注册了任何已安装的设备。如果已安装,App 将立即启动,而不会再加载网页。如果未安装 App,则将在 Safari 浏览器中加载对应的 Web 页面,这样保证了比较好的用户体验。Android(App Links): Android 的 App Links 方案是 Android 6.0 之后推出的 App 唤起方案,相当于 iOS Universal Links 的 Android 版本,二者本质都是一个 Web 链接。
Android Intent 协议: Android 的原生谷歌浏览器自从 chrome 25 版本开始对于唤端功能做了一些变化,使用自定义协议无法再启动 Android 应用。这时我们需要用 Intent 协议包装一下自定义 URI,进而打开 App。
再多说一个概念,我们的业务往往希望用户通过站外 Web 页面不仅仅能唤起 App,还可以进入到 App 的特定页面,这样可以与用户在站外的访问体验串联起来。因此,我们的协议里面往往还会有一些参数(例如:myapp://detail/123
或是 http://domain.com/detail/123
),客户端在识别匹配了这些参数后,会在启动 App 后进入对应的页面(例如:客户端识别到 detail/123
会进入 id 为 123 的详情页)。
我们往往也把这一类可以把用户引导到具体 App 内页的唤端协议链接称为 Deep Link(深度链接) 。
下文的表述中主要以唤端协议 来代表以上这些协议内容。
调用唤端协议的方法
有了唤端协议,我们还需要用户主动的操作或是对应的 JavaScript 代码调用来触发这些协议,进而唤起 App。
这里大概有几种方式:
创建 iframe 进行唤端协议跳转
用户点击协议A链接(JS 模拟协议A链接)
location.href 跳转协议地址
从业务逻辑的视角,我们的唤端主要有两种交互形式。
自动唤端(页面加载唤端 SDK 后,SDK 自动执行唤端,唤起当前页面)
手动唤端(开发者监听用户操作,并在回调中调用唤端 SDK的 API 进行唤端)
具体唤端方法在调用时还有很多的兼容性问题(可以说是迷之兼容)。例如: 使用 iframe 跳转唤端,在 iOS 9 之后的版本就被 iOS 系统禁用了。因此在 iOS 9 以上版本,就需要使用模拟 A 链接点击或是 location.href 跳转调用唤端协议。
简单来说,不同的唤端协议和唤端方法之间具有一定的组合关系,同时不同 App、系统版本也对唤端协议和方法有所约束。对我们 SDK 来说,要做到最高效的唤端,就需要针对不同的环境(App,系统)选择最合适唤端协议和唤端方法。
唤端协议和对应的调用方式组合方式很多
不同的唤端交互形式也对唤端协议和唤端方式有一定的选择要求,例如,iOS 下,无法使用 Universal Link 进行自动唤端,因为业务自动唤端逻辑不希望在唤端失败时,跳转页面,而 Universal Link 会在唤端失败时跳转页面。
唤端漏斗
整个唤端链路上,用户从看到站外广告,到点击广告,打开 H5 页面,再到唤端 SDK 执行,最终唤端。
这一整条路径上,都存在用户跳失,就像一个漏斗,每一次用户操作或是逻辑执行,都会有一部分用户衰减。
作为唤端技术团队,我们需要尽可能优化用户转化的漏斗,从而提升唤端的效果。
而对于唤端链路漏斗,可控且唯独能控的就是站外的 H5 页面,用户在 H5 页面这一步可能会以很多种方式流失。
唤端漏斗
从上表中可以看到用户在 H5 页面到唤起客户端之间有各自各样的原因产生跳失失(实际情况比表格里的还会多)。
作为负责唤端技术的同学,需要深入分析跳失漏斗和原因,优化效果,这也正是技术在唤端链路上的价值体现。
为了应对唤端的复杂需求并能够灵活的优化唤端效率,我们建立了淘系唤端体系,这次双 11,这套唤端体系也投入了作战,下面我们就来看看这套体系,并着重说说我们前端侧的设计和思考。
NO.3
淘系唤端体系概述
目前我们的唤端体系主要包括了JS SDK、客户端 SDK以及唤端控制平台三部分,这三部分各自其职,密不可分:
JS SDK - 通过业务侧 Web 页面引入实现站外的唤端功能。
客户端 SDK - 针对站外场景在目标 App 内提供还原功能。
唤端控制平台 - 结合 JS SDK 通过唤端场景 id 关联控制 Web 页面的唤端策略以及相应的 App 内还原页面等配置,同时业务也可以通过平台数据化的方式运营,调整策略以不断优化自己的唤端场景。
在这个体系中,我们从前端侧主要建设的就是 Web 唤端 SDK 以及对应的平台配置能力,接下来我们就主要说说这次我们在平台侧的建设以及双十一唤端 SDK 的设计考虑和实践。
NO.4
唤端控制平台
唤端控制平台包含拉承计划、唤端配置、APP管理及唤端数据四大模块。
【拉承计划】主要用于对投放的页面创建唤端策略以及端内落地页
【唤端配置】允许业务对自己的场景单独配置一份唤端的规则,并开放了一些唤端能力(例如:全局唤端、自动唤端、声明式唤端以及在站外弹出唤端 Pop),业务可以利用这些唤端能力,快速调整自己的线上唤端业务效果。
【APP管理】主要用于目标唤起APP的注册,该项用于H5接入新的APP唤端时使用,为APP一次性配置,如手淘特价版、饿了么等APP需在H5内唤端时接入。
【唤端数据】提供唤端相关的多维度数据查看和下钻分析;
介绍完唤端平台我们再来看看唤端 SDK。
NO.5
唤端 SDK 的实践
SDK 整体设计原则(要求)分析
我们在文章之初提及了唤端业务的复杂性,这里在重新过一遍:
不同的App环境存在唤端方式兼容性问题;
唤端链路中不同业务可能都有自己的业务定制需求;
唤端需要考虑用户没有端的降级逻辑;
唤端链路的效率需要通过数据的方式被衡量,进而可以有针对的优化;
同时,我们阅读了之前旧版 JS SDK 代码了解了业务现状,并结合日常的 SDK 维护需求以及上述唤端业务的复杂性,制定了 SDK 六大设计要求:
一、唤端 SDK作为二/三方JS,代码体积要小,调用方式要精简。
二、SDK 需符合安全生产规范,具备灰度和错误监控能力。
三、SDK 中的业务逻辑可以被有效管理
四、唤端 SDK 可快速支持新 App 唤端
五、唤端方式可以进行持续优化,进而解决唤端兼容问题
六、建立唤端采集数据标准,并在 SDK 中实现对应的数据采集
一. 做体积小,调用方式精简的 SDK
由于是开发 SDK,我们优先选择了 Rollup 进行打包构建,在工程上规定 SDK 不依赖任何的 npm 模块,所有内部逻辑都由原生 js 实现。
虽然要求 SDK 体积小,但我们依然要对源码仓库进行了目录划分,确保对应的功能都在各自文件目录中放置,代码结构划分大致如下:
-src
|-utils // SDK 执行过程工具类或是工具函数
|- URLParams.js
|- env.js
|- getScript.js
|- ...
|-methods // 不同唤端方式的实现
|-callapp-with-alink.js // A 链接点击
|-callapp-with-iframe.js // iframe 模拟跳转
|-callapp-with-location.js // location.href
|- ...后续可继续扩展
|-biz // 业务逻辑,一个文件对应一个独立的逻辑功能
|- biz-rule-evoke-param.js // 处理唤端参数逻辑
|- biz-rule-logger.js // 处理数据打点逻辑
|- biz-rule-evoke-fail.js // 业务唤端失败通用处理逻辑
|- ...后续可继续扩展
|-index.js // 代码入口
而在业务接入方式上,业务方只需要在 html 中引入 SDK 并配置业务 id 即可,SDK 在引入后会自执行,并且后续业务对 SDK 的升级迭代无感(前提是 SDK 要持续保持向下兼容):
如果业务还有特殊的定制逻辑,则可以再通过调用 SDK 的 API 的方式来进行逻辑定制。
二. 发布可灰度管控
我们在 SDK 内部,对核心的逻辑进行了try catch 和容错处理。另外,通过接入淘系灰度发布流程,新的 SDK 也增加了灰度发布的能力。在灰度过程中通过监控 SDK 在各个核心页面上的报错情况以及其变化幅度和趋势来判断 SDK 新上线后是否有负面影响。
在对接监控平台时,我们并没有依赖监控平台的 sdk,而是使用最精简的 new Image() 的方式按照监控平台的数据上报规范来进行监控数据的上报。
三. 有效管理和组织其中的功能逻辑
我们希望做到 SDK 在持续迭代的过程中,“不会因为新需求的开发,代码的管理变得‘失控’”,我们希望唤端业务逻辑和唤端核心执行的流程可以有清晰的界限(两类代码不耦合)。
为此,我们除了设计源码的组织结构,还针对 SDK 的运行时逻辑进行了梳理,如下便我们梳理的唤端 JS SDK 的执行逻辑图。
唤端 SDK 执行逻辑示意图
为了方便在 SDK 中快速加入业务定制需求,我们设计了一些 SDK 执行生命周期(如下图的数字标识),SDK 开发者或是业务开发者可以利用这些生命周期函数回调编写自己的功能逻辑注入到对应的生命周期中,当 SDK 执行到这些生命周期节点时,将会触发执行对应的逻辑。
SDK 生命周期
初始化 -
"start"
发起配置请求 -
"sendRequest"
获取唤端配置 -
"getResponse"
组装唤端参数 -
"genParams"
执行唤端(包含手动唤端和自动唤端)-
"evoke"
唤端失败 -
"evokeFailed"
其中由于 JS SDK 在一个页面只加载一次,因此 start
, sendRequest
, getResponse
这几个生命周期函数只会执行一次,而唤端在一个页面可以执行多次,因此genParams
,evoke
,evokeFailed
这几个生命周期函数会在每一次唤端执行都执行。
如果此时我们有一个业务需求: 需要在唤端时将增加自己的业务标识通过唤端协议带进客户端,那么可以在 biz 文件夹中增加一个 biz-rule-xxx.js 业务逻辑文件,将业务逻辑植入到 SDK 执行过程中。
biz-rule-xxx.js 代码示意:
export default function() {
// 这里 EVOKE_SDK 是唤端 SDK 暴露在全局的实例
// 利用 genParams 的生命周期切面,插入自定义业务逻辑
window.EVOKE_SDK.on('genParams', function(params) {
console.log('原始的唤端参数', params);
// 在 params 中增加自己的业务参数 myBizParam。
params.myBizParam = 'xxx';
// 返回的 params 会被 SDK 作为最终唤端的参数,并进行唤端协议的组装。
return params;
});
}
这里我们将业务处理逻辑通过 EVOKE_SDK.on
函数注入到 SDK 的运行时中,同时,归属同一个业务逻辑的 on 函数又都集中定义在一个 xxx-rule.js 的文件中,像这样的业务逻辑代码,就都统一放在 biz 目录下,每一个文件包含一个独立的业务逻辑。
通过这样的方式,业务可以快速自定义自己的业务唤端逻辑,同时业务和业务间的业务唤端逻辑又有 rule 文件被物理隔离开,从而保持个各自逻辑代码的干净。
业务逻辑和内部逻辑区分对待
这里 EVOKE_SDK.on
还有一个设计细节,EVOKE_SDK.on
这个 API 既被 SDK 开发者用来实现 SDK 内部的逻辑,同时也开放给业务开发者用户实现业务层定制逻辑。
但是在 SDK 内部调用 EVOKE_SDK.on
和 SDK 外部由业务开发者调用的 EVOKE_SDK.on
其实是有区别的。
我们希望 SDK 内部调用的 on
函数相对权限是高的,可以控制完整的 SDK 生命周期;而对于业务开发者,我们希望 EVOKE_SDK.on
只有一定的逻辑修改权限,业务开发者不能越权修改 SDK 不开放的属性;另外一个区别是 SDK 外部调用 EVOKE_SDK.on
所能控制的生命周期范围更有限,只能控制 genParams
,evoke
,evokeFailed
这几个生命周期,业务侧逻辑要保证不能修改和影响到 SDK 内部初始化逻辑的生命周期。
为此我们做了相应的设计,当 SDK 内部定义的 EVOKE_SDK.on
函数回调在 SDK 内部逻辑执行后,EVOKE_SDK.on
函数会被覆写一次,而业务开发者调用的是覆写后的 EVOKE_SDK.on
函数,这样保证业务开发者调用的 EVOKE_SDK.on
逻辑可以被单独管控。
SDK 内外逻辑隔离设计(同一个 API,不同的调用权限)
这个 on 机制的实现底层其实不复杂,只需要基于简单事件订阅模式来实现。
另外,为了便于后续扩展唤端协议和唤端方法,我们还对唤端协议的拼装和调用逻辑进行了抽象和标准化。
唤端失败的处理与定制逻辑
另外我们在 SDK 中也定义了默认的唤端失败逻辑(当然也允许业务进行覆盖自定义),用户在唤端失败后会进入下载和安装 App 还原站外内容的流程,这个能力在业界叫做 Deferred Deep Link,这里我们先不展开讨论。
四. 唤端 SDK 可快速支持 App
我们发现大多数 App 的唤端协议大同小异,一个协议大致分为几部分。
以自定义 URI 协议为例,整个协议大致就这几部分:myapp://path/to/content?key1=value1&key2=value2。
因此针对一个 App 的协议我们可以通过 JSON 配置进行规范。
{
"name": "appName",
"protocol": "myapp",
"path": "path/to/content",
"params": {
"key1": "value1",
"key2": "value2"
}
}
我们再定义一个协议模板
const URITemplate = `${protocol}://${path}${params}`;
只需要把生成协议 JSON 配置和对应的协议模板进行组装,最后就生成自定义 URI 协议。
对于 Universal Links 和 App Links 以及 Intent 协议也是类似,我们只需要 JSON 的字段,并准备对应的协议模板就可以实现对任意 App 唤端协议的生成。
{
"name": "appName",
"protocol": "myapp",
"path": "path/to/content",
"universalLink": {
"path": 'path/of/ulk'
},
"appLink": {
"path": 'path/of/applink'
},
"params": {
"key1": "value1",
"key2": "value2"
}
}
后续如果我们要对接新的 App 唤端,只需要基于这个定义的规范配置该 App 的唤端 JSON 配置即可。
五. 唤端方式可持续优化
前面我们说了在不同的 App、手机品牌、系统版本下进行唤端,可能会具有兼容性问题。
前期我们在 SDK 开发时专门预留了一个 js 策略配置文件,作为策略控制层,控制唤端方式,在执行唤端时,根据不同的 App、OS版本、手机浏览器信息特征结合策略文件,来选择最佳的唤端方式。
渐渐地我们在实践中发现,这种兼容问题越来越多,也越来越细碎,通过 JS 策略文件来管理,将会是很大的一个数据配置,因此我们考虑针对唤端方式专门建一层策略转换层,输入 UA 便可以知道最优的唤端方式,并且这一个策略数据库也可以持续的更新,适应最新的唤端情况。而同时前端 SDK 则可以进一步的变轻量。
唤端流程大图中,唤端策略的放大细节
六. 建立唤端采集数据标准
最后,唤端效果的好坏主要是看
在唤端过程中,我们采集的数据是来自于多个地方,用户发起唤端的数据来自于站外 Web 页面的日志,而唤起成功的数据来自于 App 的日志,因此我们需要有唯一标识用于串联整个数据链路。
如下是唤端的数据口径和数据采集来源参考:
当然除了唤端成功率,我们还会关注发起唤端的漏斗
这里我们会在每一次唤端的时候,带上一个唤端的唯一标识,它的作用是用于标识每一个用户的每一次唤端行为。唤端唯一标识的形式可以是 用户唯一标识 + 时间戳,通过将唯一标识从 Web 页面带入 App,便可以串联起一个用户在整个唤端链路上的所有数据。
这样我们可以根据用户唯一标识来进行端内外的用户去重,得到 UV 级别的唤端数据,也可以从整体 PV 级别看到整体的唤端情况。
要注意:唤端唯一标识,必须在 SDK 初始化就创建,且不能被篡改,并一直带入到 APP 中,这样才能准确的产出唤端相关的效果数据。
以上便是我们目前在唤端 JS SDK 设计上的考虑。
NO.6
阶段成果
本次双11,我们在几个核心的业务以及双十一外投业务场景升级了唤端能力,通过 A/B 数据的对比,新版 SDK 整体带来的唤端成功率有了不少提升,在部分场景唤端成功率相对提升达 40%,
我们 SDK 在新的架构下,通过简化复用 SDK 逻辑,清理不必要的逻辑,代码体积相比之前也大大减少(26K->8K gzip),在加载时间上快了近一倍(500ms-> 200ms),这些都大大提前了唤端执行的时机,进而也提升了唤端的成功率。
在 SDK 能力上,我们还增加了声明式唤端,全局 A 链接唤端等方式,简化了页面增加唤端位置的对接成本,增加了很多的手动唤端的点位,尤其是会场我们通过全局 A 链接点击唤端,可以更高效的将用户从站外会场浏览导入到 App 内进行浏览。
另外在业务的覆盖上,除了覆盖淘宝自己的业务,我们也向饿了么、支付宝等一些业务场景输出了唤端能力。
NO.7
总结和展望
唤端体系以及对应的 SDK 的建设是目前移动 App 用户增长领域必不可少的一块,要做好唤端效果,我们需要一个良好的架构设计(便于我们的业务需求的快速达成),同时也要有一套可以持续进行维护优化的技术方案和数据检测体系。
当然还有很多工作我们需要继续优化
无端还原
在无端还原链路上,目前业界像 appsflyer,branch.io 都有做。如何做好还原,准确的定位需要还原的用户,这方便我们还需要进一步和数据团队合作做深。
唤端数据展示
我们希望接入唤端的业务都可以拥有独立的数据看板,从而可以通过数据的反馈,进行唤端策略的调整。
唤端调试体验
目前我们作为唤端平台技术提供方,遇到最多的问题是业务找过来咨询为什么在 xx 环境下,唤端失效了或是唤端成功率低。但其实影响唤端的方式很多,因此对我们还要建立一个日常的唤端问题排查链路,便于平台方排查以及业务自查问题。
希望未来我们的唤端技术可以通过不断优化,达到对每个业务唤端效率的最大化,同时可以覆盖更多的业务场景,服务更多的用户。
欢迎关注东半球最大的前端团队
喜欢就点这里