后 iOS 9 时代呼起 App 二三事

Author Avatar
姜希凡 (Ivan) 12月 03, 2016

概述

在移动端的网页开发中,我们有时候需要实现一个 App 的推广 banner,当用户的设备上安装了某 App 后则呼起这个 App 并打开指定页面,未安装则跳转 App Store / 下载页面,在安卓上甚至有可能直接开始下载。比如淘宝 h5 页面上这个“立即领取”按钮。

淘宝 H5 页面上的 App Banner

这个 trick 其实是使用了自定义协议 (Custom URL Scheme) 来实现的,具体的实现比较简单。

1
2
3
4
5
6
let link = "taobao://item.taobao.com/item.htm?id=" + id,
fallback = "itms-apps://itunes.apple.com/cn/app/" + appId;
// 尝试呼起 App
location.href = link;
// 3s 后未能呼起则跳转 App Store
setTimeout(() => location.href = fallback, 3000);

以上这段代码有很多可以优化的地方,比如根据设备运行速度不同,跳转的时间可能会有差异;有时候可以用 iframe 来实现等。这个根据具体业务和个人经验来优化,在此不作过多赘述。

这个让前端爽到飞起的 trick 呢,在 iOS 9.2 之后就不能再用了,因为苹果干掉了这个方法,强推 Universal Links. 如果你试图跳转到一个未注册的 URL Scheme 上,会有一个弹窗提示你“无法打开这页”,用户看到这个弹窗,就知道你要干坏事啦。

ios 9.2 uri scheme error

如果安装了该 App,也会询问用户是否打开。使得之前通过计时来实现的 trick 不生效。

iOS 9.2 Redirect to Twitter

并且这两个弹窗不是我们熟悉的 window.alert,它是不 block UI 的,所以网上流传的一些通过计时器 (setTimeout, setInterval) 来实现的 trick 也是没什么用的。

以上是在浏览器中呼起 App 的实现;当用户在 App 中打开这个网页的时候,情况又不一样了。我们拿微信举例,前端开发者需要通过微信提供的 JS-SDK 来查询用户是否安装并且呼起 App 或跳转 App Store.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let ua = navigator.userAgent;
let inWeChat = ua.includes('MicroMessenger');
if (inWeChat) {
// 请求微信帮忙呼起 App
WeixinJSBridge.invoke('getInstallState', {
'packageName': config.packageName,
'packageUrl' : config.packageUrl
}, function(e) {
if (e.err_msg.includes("get_install_state:yes")) {
// 呼起 App
} else {
// 跳转 App Store
}
});
} else {
// in browsers...
}

但是 iOS 9 同样对 App (此例中的微信) 增加了限制:仅能查询 50 个白名单内的 App 的安装状态。这意味在 iOS 9 往后的设备中,微信仅能通过 canOpenUrl() 这个方法来判断用户是否安装了这 50 个 App. 在实际的测试中,不在白名单内的 App,微信的 JSBridge 会返回 true,意味着微信总是尝试打开这个 App,而不会跳转 App Store.

1
2
3
4
5
6
7
8
9
10
WeixinJSBridge.invoke('getInstallState', {
'packageName': config.packageName,
'packageUrl' : config.packageUrl
}, function(e) {
if (e.err_msg.includes("get_install_state:yes")) {
// 呼起 App. (不在白名单中的 App 总是得到 "yes", 需要做特殊处理)
} else {
// 跳转 App Store
}
});

查看一个 iOS App 支持呼起哪些 App

在刚开始踩这个坑的时候,我直接跑去问微信和手 Q 的开发者,麻烦他们看一下我们的 URL Scheme 是否在微信和手 Q 的白名单中。得到响应很快,但是这显然并不是一个高效的方案。

开发者按照苹果的要求,会将自己 App 的白名单放在 Info.plist 文件中,读取这个文件很简单。我们以微信为例。

  1. 首先获取微信的 ipa 安装包。这个可以在电脑的 iTunes 上获得。比如 Mac 版 iTunes 下载的 ipa 文件路径一般为 ~/iTunes/iTunes Media/Mobile Applications,在里面找到微信,如 WeChat 6.3.16.ipa

  2. 我们知道 ipa 包其实就是一个 zip 文件,将 ipa 文件拷贝一份,然后使用 unzip 命令或直接改后缀名为 .zip 就可以打开了

    1
    2
    3
    4
    5
    6
    /path/to/your/wechat_ipa_unzip
    |--- Payload
    |--- WeChat.app
    |--- Info.plist
    |--- iTunesMetadata.plist
    |--- iTunesArtwork
  3. Payload 文件夹下是 WeChat.app 文件(夹),直接在 finder 中右击“显示包内容”或在命令行中可以直接 cd WeChat.app,然后找到 Info.plist 文件。

  4. 在这个 plist 文件中找到白名单很简单,因为微信已经达到了 50 个的上限,一个很扎眼的 “(50 items)” 的 “Array” 项 LSApplicationQueriesSchemes 就是我们要找的白名单了。我们可以看到诸如腾讯新闻 (qqnews), 腾讯视频 (tenvideo2) 都是在白名单内的。

查看 App 注册的 URL Scheme

App 的 URL Scheme 也写在上文所说的 Info.plist 这个文件中 (URL types/item 0/URL Schemes)。

URL Scheme in Info.plist

我们可以看到,微信注册了 weixin, fb290293790992170(Facebook 的 AppID), wechat, QQ41C152CF(QQ 的 AppID), prefs 这五个 URL Scheme. 根据苹果的开发者文档,系统注册的 URL Scheme 优先级高于第三方 App,所以前四个加上 :// 都可以在浏览器中呼起微信,但是第五个是系统级的所以不能呼起微信;并且第三方 App 之间可能存在 URL 竞争,如果注册了同一个 URL Scheme,是不确定会打开哪一个的,所以 URL Scheme 是不安全的(比如我上架了一个 App,同样注册了 wechat 这个 scheme,然后设计好路由,用户在进行微信支付时很可能跳转到我的 App 内,在我精心设计的界面中输入密码等)。

深入

上文提到,苹果强推 Universal Links. 这个才叫爽到飞起,不信你用微信扫码感受一下 (前提是安装了腾讯视频的 iOS 设备)。

下次撰文写这个功能。