揭秘手淘召唤术| 帮助千万级用户直达手淘的黑科技

avatar
前端 @阿里巴巴

本文由淘系用户增长前端团队-猫橘分享

对笔者所在的淘系用户增长团队来说,把用户引导到手淘 app 里是很重要的一个环节,本文会分享在手机浏览器或其他 app 的 H5 页面里是怎么把手淘「召唤」出来的,这里就涉及到「唤端」这个概念。

什么是唤端


这里说的唤端,是指在手机上从一个 app 跳转到另一个 app,也就是 app 间的跳转。以手淘为例,在很多场景里,用户并不一定能直接通过手淘打开对应的商品详情页或者会场活动页,例如投放到媒体的广告、用户分享、线下扫码等,这些场景里用户首先打开的不是手淘,而可能是抖音、头条这些外部媒体,微博、钉钉这些社交分享渠道,或者手机自带浏览器这些扫码工具,这时我们就需要在用户打开网页或点击链接后,引导用户从这些 app 跳转到手淘。如果用户没有安装手淘则引导到下载页下载。这里首先就会涉及到通过什么方式实现 app 间的互相唤起。

唤端基本原理

URL Scheme


对于技术同学,URL Scheme 应该不陌生,常见的 http / https / ftp 就属于 scheme,简单来说, URL 是统一资源定位符(通俗说就是「网址」),其中 scheme 就是标识资源的访问方式,在 iOS 和安卓里,就可以简单理解为用哪一个 app 打开这个 URL。

例如我们页面中写一个 a 标签:

<a href="taobao://m.taobao.com">打开淘宝</a>


如果安装了手淘,在其他 APP 打开页面点击这个链接,就会拉起手淘,如果没有安装,点击后就没有任何反应。

假如把链接换成 alipays://xxxx,就可以唤起支付宝,所以taobao:// alipays:// 就是淘宝和支付宝的 scheme。当然,一个 APP 可能不止一个 scheme,手淘和支付宝都有多个 scheme 以应对不同的场景。

利用 scheme 我们还可以做很多事情,比如在 iOS 里用 tel:// 这个 scheme 可以直接唤起拨号程序:

<a href="tel://13888888888">拨打电话</a>


结合 iOS 的“捷径”可以搭配出不少有意思的功能,这里就不展开了。

iOS 和安卓都支持这种唤起方式,那么问题来了,系统如何知道碰到 taobao:// 就用手淘打开而不是用别的 app 打开呢?

Scheme 注册


要想让自己的 app 能通过 Scheme 的方式唤起,需要在 app 里事先向系统注册自己的 Scheme。安卓可以在manifest 里通过 intent-filter 配置,iOS 则可以在 info.plist 文件中添加 URL types 来注册一个 Scheme。系统只负责根据 Scheme 唤起对应的 app,至于打开 app 之后做什么,就需要  app 自己去实现解析 URL 参数,并作出相应处理的逻辑。

Intent


在安卓的 Chrome 中,不支持通过普通 URL Scheme 的方式唤起其他 APP,只支持通过 Intent 协议唤起,本质上与 URL Scheme 没有太大区别,只是在构造链接的时候有一些不同,文档在这里

适用性


URL Scheme 这种方式兼容性好,无论安卓或者 iOS 都能支持,是目前「召唤」手淘最常用的方式。但它也有一些比较明显的缺点:

  1. 无法准确判断是否唤起成功,因为本质上这种方式就是打开一个链接,并且还不是普通的 http 链接,所以如果用户没有安装对应的 APP,那么尝试跳转后在浏览器中会没有任何反应,甚至在一些 webview 里还会跳到一个类似「无法打开 taobao://xxxx」这样的错误页或者错误弹框,体验不够好;
  2. 在很多浏览器和 webview 中会有一个弹窗提示你是否打开对应 APP,可能会导致用户流失;
  3. 有 URL Scheme 劫持风险,比如某不知名 app 也向系统注册了 taobao:// 这个 scheme ,唤起流量可能就会被劫持到这个 app 里;
  4. 很容易被屏蔽,app 很轻松就可以拦截掉通过 URL Scheme 发起的跳转。

Universal Links


Universal Links 是 iOS 9 中引入的功能,使用它可以直接通过 https 协议的链接来打开APP,如果没有安装则打开对应 H5 页面。

例如,如果你安装了手淘,那么页面里点击下面链接就能直接唤起:

<a href="https://b.mashort.cn/">打开手淘</a>


它的原理也比较简单:

  1. 在 APP 中注册自己要支持的域名;
  2. 在自己域名的根目录下配置一个 apple-app-site-associatio 文件即可。


具体可以参考官方文档

相对 URL Scheme,universal links 有一个较大优点是它唤端时没有弹窗提示,可减少一部分流失;对于没有安装应用的用户,点击链接就会直接打开对应的页面,那么我们也可以对这种情况做统一的处理,比如引导到一个中转页,也能一定程度解决 URL Scheme 无法准确判断唤端失败的问题;从整体的体验上说,universal links 要优于 URL Scheme。

但 universal links 也有一些弊端:

  1. 只能在 iOS 上用;
  2. 只能由用户主动触发,比如用浏览器扫码打开页面,就没有办法由页面直接唤起APP,而需要用户手动点击页面按钮才能唤起;

H5 唤端兼容


不管是 URL Scheme 还是 universal links,其本质上都是打开一个 URL,在 H5 中有几种实现方式:

iframe


最常采用的方式是使用 iframe 来做跳转,

方法是动态插入一个隐藏的 iframe,在 iframeonload之后就会触发唤起:

iframe = doc.createElement('iframe');
iframe.frameborder = '0';
iframe.style.cssText = 'display:none;border:0;width:0;height:0;';
doc.body.appendChild(iframe);
iframe.src = 'taobao://m.taobao.com';


这样的好处是即便用户没有安装应用,当前页面也不会跳转到错误的页面。

a 标签


在 iOS 9 以上的 safari 中,不支持 iframe 唤起,可以用生成 a 标签并模拟点击的方式:

var a = document.createElement('a');
a.setAttribute('href', url);
a.style.display = 'none';
document.body.appendChild(a);

var e = document.createEvent('HTMLEvents');
e.initEvent('click', false, false);
a.dispatchEvent(e);

window.location


对于 intent 和 universal links 可以直接使用设置 window.location.href 的方式跳转,因为如果没有安装 app 他们也不会跳到错误的页面。

这三种方式基本可以兼容所有的浏览器。

判断是否唤起成功


不管哪种唤起方式,目前在 H5 页面中都没有办法直接知道是否唤起成功。这也很好理解,唤端本身就是打开一个 URL,即使我们通过 a  标签打开的是一个正常的 http 的链接,作为页面本身同样也无法得知页面是否加载成功。

不过我们可以通过一些其他的办法间接的去判断,主要是利用成功唤起后,当前的 H5 页面会被切到后台这一特性:在触发唤起后,监听 n 秒内是否触发了 blur 或者 visibilitychange 事件,有触发并且 document.hiddentrue说明可能是唤起成功(当然也有可能是用户自己切走了);

 const timer = window.setTimeout(() => {
   // 失败回调
 }, 3000);

const successHandler = () => {
  if (document.hidden) {
    window.clearTimeout(timer);
  }
};
window.addEventListener('blur', successHandler, { once: true });
window.addEventListener('visibilitychange', successHandler, { once: true });

下载还原


实际业务中,通常会希望用户在唤端失败后跳转到到 app 下载页,以继续引导用户进端,但下载一般是通过跳转到应用商店或者直接拉起下载 APK 包,期间是没有办法给安装包传递参数的,所以用户安装好 app 后也不能继续在端内还原下载之前的页面。

为了让用户可以在安装 app 之后继续还原之前的链路,尽可能的减少流失,我们实现了一套「口令还原」的协议:

  1. 在用户点击下载时,把希望在安装后打开的链接按协议约定的格式写入到剪贴板中;
  2. 用户安装 app 并首次激活时,进入端内如果取出来的是约定好的“还原口令”,则根据口令打开对应的页面。


通过这种方式,即便用户唤端失败,也可能最后能进到端内的承接页,一定程度上把原本在下载 app 后无法继续承接的用户捞了回来。这种方式也并不完美:

  1. 写到剪贴板的口令可能会被劫持,所以端内目前只支持在白名单里的链接进行还原;
  2. 如果剪贴板中的口令被覆盖,依然没法还原成功;
  3. 用户安装后首次打开 app 时会有很多初始化的加载,此时打开承接页会使得整体性能表现更差;
  4. 如果是比较习惯用剪贴板的用户,这个方式无疑污染了用户的剪贴板;
  5. 浏览器限制前端写剪贴板的动作只能在用户触发的同步事件中触发。


这种方式能尽可能的减少用户流失和让用户获得更好的体验,尽管存在一些缺点,也不失为用户唤端失败之后一种较好的处理。

手淘的唤端解决方案


谈完唤端原理,我们再从手淘业务的角度来看,手淘里需要唤端的场景和页面非常多,总结下来大概分三类:

  1. 自动唤端:页面打开即自动发起唤端;
  2. UI唤端:页面透出浮标、banner 等 UI,点击后发起唤端;
  3. 页面行动点唤端:页面中点击某些链接后唤起 app 打开。


手淘里我们将唤端能力封装为一个前端的 SDK,H5 页面只需要通过 script 标签的方式将脚本引入即可,在 SDK 里,主要实现唤端协议和唤端策略控制。

唤端协议


在唤起 app 之后,我们想象一下几种场景:

  1. 用户访问一个站外的 H5 活动页,唤端到站内打开同样的页面;
  2. 用户访问一个商品的 H5 详情页,唤端到站内需要打开 native 的商品详情页;
  3. 用户点击一个广告链接,唤端到站内之后根据用户的人群标签特征,打开不同的页面承接。


也就是说,用户唤端之后,并不是简单的只需要在端内打开同样的链接,而是需要做很多个性化处理的,这时候我们就需要去解析唤起 URL 里的参数并做端内的路由分发。

在手淘里,我们所有进端流量都会经过一个叫「流量海关」的 SDK,它实现了一套协议使得我们只要按协议拼装参数,就可以在唤起 app 后打开对应的承接页。

这个协议大致形如:

[scheme]://[host][path]?[...省略各种参数]&h5Url=[唤起后打开的淘内链接]


在前端 SDK 里,我们封装了协议的拼装过程,页面只需要传入简单的参数就可以实现唤起。

唤端策略


在淘系除了手淘之外,我们还有手机天猫、淘宝特价版等 app,具体到一个 H5 页面里,当前应当唤起哪个 app,应当使用哪种唤起方法,是否页面打开即唤起,是否展示唤端 UI,这些东西构成了一个页面的唤端策略。

在手淘里我们提供了一套唤端策略服务,通过当前页面所处的环境(UA、URL 等能用于识别环境的字段)来下发页面唤端所用的策略配置:

  1. 唤端目标 app:根据服务端下发的唤端目标 app,前端 SDK 里选取对应的 scheme 或者 universal links 的链接进行唤端协议拼装;
  2. 唤端方式:应该用 URL Schemes 的方式还是 Universal Links 的方式;
  3. 是否自动唤起:可动态配置页面是否打开即唤起;
  4. 是否展示唤端 UI:配置页面上唤端 UI 的样式(浮标、弹窗、banner 等)和是否展示。


也就是说页面只要引入了前端 SDK,就可以通过唤端配置后台做精细化的唤端控制,这也构成了手淘用户增长重要的一环。
整个 app 的唤起流程可以总结到下图:
20200616200042.jpg

结语


事实上在唤端这件事情上未来还可以做很多,比如提供更精细化的策略配置能力,根据地域人群等信息做更精准的唤端 UI 展示、下载落地页跳转,通过结合 A/B 测试探索最优的进端链路等,如果感兴趣可以加入我们。