Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

H5 唤起 APP 指南 #1

Open
suanmei opened this issue Aug 24, 2018 · 37 comments
Open

H5 唤起 APP 指南 #1

suanmei opened this issue Aug 24, 2018 · 37 comments

Comments

@suanmei
Copy link
Owner

suanmei commented Aug 24, 2018

前段时间在做一些H5页面,落地页占比较大,落地页承担的职责就是引流。引流有两种形式,同时也是我们对唤端的定义:引导已下载用户打开APP,引导未下载用户下载APP。

引导已下载用户打开APP,从数据上说用户停留在APP中的时间更多了,是在提高用户粘性;从体验上说,APP体验是要比H5好的。引导未下载用户下载APP,可以增加我们的用户量。

上面其实分别解释了 什么是唤端 以及 为什么要唤端,也就是 3W法则 中的 What 和 Why,那么接下来我们就要聊一聊 How 了,也就是 如何唤端

我们先来看看常见的唤端方式以及他们适用的场景:

唤端媒介

URL Scheme

来源

我们的手机上有许多私密信息,联系方式、照片、银行卡信息...我们不希望这些信息可以被手机应用随意获取到,信息泄露的危害甚大。所以,如何保证个人信息在设备所有者知情并允许的情况下被使用,是智能设备的核心安全问题。

对此,苹果使用了名为 沙盒 的机制:应用只能访问它声明可能访问的资源。但沙盒也阻碍了应用间合理的信息共享,某种程度上限制了应用的能力。

因此,我们急需要一个辅助工具来帮助我们实现应用通信, URL Scheme 就是这个工具。

URL Scheme 是什么

我们来看一下 URL 的组成:

<scheme>://<host>:<port>[path][?query][#fragment]

我们拿 https://www.baidu.com 来举例,scheme 自然就是 https 了。

就像给服务器资源分配一个 URL,以便我们去访问它一样,我们同样也可以给手机APP分配一个特殊格式的 URL,用来访问这个APP或者这个APP中的某个功能(来实现通信)。APP得有一个标识,好让我们可以定位到它,它就是 URL 的 Scheme 部分。

常用APP的 URL Scheme

APP 微信 支付宝 淘宝 微博 QQ 知乎 短信
URL Scheme weixin:// alipay:// taobao:// sinaweibo:// mqq:// zhihu:// sms://

URL Scheme 语法

上面表格中都是最简单的用于打开 APP 的 URL Scheme,下面才是我们常用的 URL Scheme 格式:

              行为(应用的某个功能)
                      |
scheme://host:port/[path][?query]
   |                         |
应用标识                 功能需要的参数

Intent

安卓的原生谷歌浏览器自从 chrome25 版本开始对于唤端功能做了一些变化,URL Scheme 无法再启动Android应用。 例如,通过 iframe 指向 weixin://,即使用户安装了微信也无法打开。所以,APP需要实现谷歌官方提供的 intent: 语法,或者实现让用户通过自定义手势来打开APP,当然这就是题外话了。

Intent 语法

intent:
   HOST/URI-path // Optional host 
   #Intent; 
      package=[string]; 
      action=[string]; 
      category=[string]; 
      component=[string]; 
      scheme=[string]; 
   end;

如果用户未安装 APP,则会跳转到系统默认商店。当然,如果你想要指定一个唤起失败的跳转地址,添加下面的字符串在 end; 前就可以了:

S.browser_fallback_url=[encoded_full_url]

示例

下面是打开 Zxing 二维码扫描 APP 的 intent。

intent:
   //scan/
   #Intent; 
      package=com.google.zxing.client.android; 
      scheme=zxing; 
   end; 

打开这个 APP ,可以通过如下的方式:

 <a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end"> Take a QR code </a>

Universal Link

Universal Link 是什么

Universal Link 是苹果在 WWDC2015 上为 iOS9 引入的新功能,通过传统的 HTTP 链接即可打开 APP。如果用户未安装 APP,则会跳转到该链接所对应的页面。

为什么要使用 Universal Link

传统的 Scheme 链接有以下几个痛点:

  • 在 ios 上会有确认弹窗提示用户是否打开,对于用户来说唤端,多出了一步操作。若用户未安装 APP ,也会有一个提示窗,告知我们 “打不开该网页,因为网址无效”
  • 传统 Scheme 跳转无法得知唤端是否成功,Universal Link 唤端失败可以直接打开此链接对应的页面
  • Scheme 在微信、微博、QQ浏览器、手百中都已经被禁止使用,使用 Universal Link 可以避开它们的屏蔽( 截止到 18年8月21日,微信和QQ浏览器已经禁止了 Universal Link,其他主流APP未发现有禁止 )

如何让 APP 支持 Universal Link

有大量的文章会详细的告诉我们如何配置,你也可以去看官方文档,我这里简单的写一个12345。

  1. 拥有一个支持 https 的域名
  2. 开发者中心 ,Identifiers 下 AppIDs 找到自己的 App ID,编辑打开 Associated Domains 服务。
  3. 打开工程配置中的 Associated Domains ,在其中的 Domains 中填入你想支持的域名,必须以 applinks: 为前缀
  4. 配置 apple-app-site-association 文件,文件名必须为 apple-app-site-association不带任何后缀
  5. 上传该文件到你的 HTTPS 服务器的 根目录 或者 .well-known 目录下

Universal Link 配置中的坑

这里放一下我们在配置过程中遇到的坑,当然首先你在配置过程中必须得严格按照上面的要求去做,尤其是加粗的地方。

  1. 跨域问题

    IOS 9.2 以后,必须要触发跨域才能支持 Universal Link 唤端。

    IOS 那边有这样一个判断,如果你要打开的 Universal Link 和 当前页面是同一域名,ios 尊重用户最可能的意图,直接打开链接所对应的页面。如果不在同一域名下,则在你的 APP 中打开链接,也就是执行具体的唤端操作。

  2. Universal Link 是空页面

    Universal Link 本质上是个空页面,如果未安装 APP,Universal Link 被当做普通的页面链接,自然会跳到 404 页面,所以我们需要将它绑定到我们的中转页或者下载页。

如何调用三种唤端媒介

通过前面的介绍,我们可以发现,无论是 URL Scheme 还是 Intent 或者 Universal Link ,他们都算是 URL ,只是 URL Scheme 和 Intent 算是特殊的 URL。所以我们可以拿使用 URL 的方法来使用它们。

iframe

<iframe src="sinaweibo://qrcode">

在只有 URL Scheme 的日子里,iframe 是使用最多的了。因为在未安装 app 的情况下,不会去跳转错误页面。但是 iframe 在各个系统以及各个应用中的兼容问题还是挺多的,不能全部使用 URL Scheme。

a 标签

<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end"">扫一扫</a>

前面我们提到 Intent 协议,官方给出的用例使用的就是使用的 a 标签,所以我们跟着一起用就可以了

使用过程中,对于动态生成的 a 标签,使用 dispatch 来模拟触发点击事件,发现很多种 event 传递过去都无效;使用 click() 来模拟触发,部分场景下存在这样的情况,第一次点击过后,回到原先页面,再次点击,点击位置和页面所识别位置有不小的偏移,所以 Intent 协议从 a 标签换成了 window.location。

window.location

URL Scheme 在 ios 9+ 上诸如 safari、UC、QQ浏览器中, iframe 均无法成功唤起 APP,只能通过 window.location 才能成功唤端。

当然,如果我们的 app 支持 Universal Link,ios 9+ 就用不到 URL Scheme 了。而 Universal Link 在使用过程中,我发现在 qq 中,无论是 iframe 导航 还是 a 标签打开 又或者 window.location 都无法成功唤端,一开始我以为是 qq 和微信一样禁止了 Universal Link 唤端的功能,其实不然,百般试验下,通过 top.location 唤端成功了。

判断唤端是否成功

如果唤端失败(APP 未安装),我们总是要做一些处理的,可以是跳转下载页,可以是 ios 下跳转 App Store... 但是Js 并不能提供给我们获取 APP 唤起状态的能力,Android Intent 以及 Universal Link 倒是不用担心,它们俩的自身机制允许它们唤端失败后直接导航至相应的页面,但是 URL Scheme 并不具备这样的能力,所以我们只能通过一些很 hack 的方式来实现 APP 唤起检测功能。

// 一般情况下是 visibilitychange 
const visibilityChangeProperty = getVisibilityChangeProperty();
const timer = setTimeout(() => {
  const hidden = isPageHidden();
  if (!hidden) {
    cb();
  }
}, timeout);

if (visibilityChangeProperty) {
  document.addEventListener(visibilityChangeProperty, () => {
    clearTimeout(timer);
  });

  return;
}

window.addEventListener('pagehide', () => {
  clearTimeout(timer);
});

APP 如果被唤起的话,页面就会进入后台运行,会触发页面的 visibilitychange 事件。如果触发了,则表明页面被成功唤起,及时调用 clearTimeout ,清除页面未隐藏时的失败函数(callback)回调。

当然这个事件是有兼容性的,具体的代码实现时做了事件是否需要添加前缀(比如 -webkit- )的校验。如果都不兼容,我们将使用 pagehide 事件来做兜底处理。

没有完美的方案

透过上面的几个点,我们可以发现,无论是 唤端媒介调用唤端媒介 还是 判断唤端结果 都没有一个十全十美的方法,我们在代码层上能做的只是在确保最常用的场景(比如 微信、微博、手百 等)唤端无误的情况下,最大化的兼容剩余的场景。

好的,我们接下来扯一些代码以外的,让我们的 APP 能够在更多的平台唤起。

  • 微信、微博、手百、QQ浏览器等。

    这些应用能阻止唤端是因为它们直接屏蔽掉了 URL Scheme 。接下来可能就有看官疑惑了,微信中是可以打开大众点评的呀,微博里面可以打开优酷呀,那是如何实现的呢?

    它们都各自维护着一个白名单,如果你的域名在白名单内,那这个域名下所有的页面发起的 URL Scheme 就都会被允许。就像微信,如果你是腾讯的“家属”,你就可以加入白名单了,微信的白名单一般只包含着“家属”,除此外很难申请到白名单资质。但是微博之类的都是可以联系他们的渠道童鞋进行申请的,只是条件各不相同,比如微博的就是在你的 APP 中添加打开微博的入口,三个月内唤起超过 100w 次,就可以加入白名单了。

  • 腾讯应用宝直接打开 APP 的某个功能

    刚刚我们说到,如果你不是微信的家属,那你是很难进入白名单的,所以在安卓中我们一般都是直接打开腾讯应用宝,ios 中 直接打开 App Store。点击腾讯应用宝中的“打开”按钮,可以直接唤起我们的 APP,但是无法打开 APP 中的某个功能(就是无法打开指定页面)。

    腾讯应用宝对外开放了一个叫做 APP Link 的申请,只要你申请了 APP Link,就可以通过在打开应用宝的时候在应用宝地址后面添加上 &android_schema={your_scheme} ,来打开指定的页面了。

开箱即用的callapp-lib

信息量很大!各种问题得自己趟坑验证!内心很崩溃!

不用愁,已经为你准备好了药方,只需照方抓药即可😏 —— npm 包 callapp-lib

它能在大部分的环境中成功唤端,而且炒鸡简单啊,拿过去就可以用啊,还支持很多扩展功能啊,快来瞅瞅它的 文档 啊~~~

参考文章

  1. 浏览器中唤起 native app,否则跳转到应用商城下载
  2. h5唤起app
  3. URL Schemes 使用详解
  4. Android Intents with Chrome
  5. 常用URL Scheme
  6. Universal Link 前端部署采坑记
  7. Support Universal Links
  8. Universal Link是个骗子

本文首发于:https://suanmei.github.io/2018/08/23/h5_call_app/

@suanmei suanmei closed this as completed Aug 24, 2018
@suanmei suanmei reopened this Aug 24, 2018
@believeZJP
Copy link

唤醒微信打开指定页面,比如选择一个聊天和唤醒QQ打开发送给好友列表,是要在open方法里传path吗, 应该传什么值,有对应的文档吗@suanmei

@suanmei
Copy link
Owner Author

suanmei commented Oct 12, 2018

@BEL

唤醒微信打开指定页面,比如选择一个聊天和唤醒QQ打开发送给好友列表,是要在open方法里传path吗, 应该传什么值,有对应的文档吗@suanmei

唤醒微信打开指定页面,比如选择一个聊天和唤醒QQ打开发送给好友列表,是要在open方法里传path吗, 应该传什么值,有对应的文档吗@suanmei

不可能让你调这些功能性的东西的,调用聊天窗口之类的都是需要经过微信校验的;不然随便一个钓鱼网站放一个 scheme 引导你点开触发支付就血崩了

@believeZJP
Copy link

image
所以唤醒APP后打开这个页面时做不到的?

@suanmei
Copy link
Owner Author

suanmei commented Oct 13, 2018

image
所以唤醒APP后打开这个页面时做不到的?

嗯,确实不行。因为都是需要通过sdk去做的

@believeZJP
Copy link

好,多谢啦~~

@Sherryer
Copy link

Sherryer commented Oct 17, 2018

看大佬源码中的实现和 Issues 中的实现不大一样,Issues 中的是早些版本的嘛?
在源码中换成了 setTimeout,我理解的安卓中 setInterval 被卡住是卡在当前 interval 的下一次 interval 循环,而 setTimeout 只有一次事件循环,会不会无法在安卓下卡住呢。
望指点谢谢大佬

const checkOpen = setInterval(() => {
	count++;
	waitTime = new Date() - initialTime;

	if (waitTime > 2500) {
		clearInterval(checkOpen);
		cb();
	}

	if (counter < 100) return;

	const hide = document.hidden || document.webkitHidden;

	if (!hide) {
		cb(); // 唤端失败的回调函数
	}
}, 20);
export function checkOpen(cb, timeout) {
  const visibilityChangeProperty = getVisibilityChangeProperty();
  const timer = setTimeout(() => {
    const hidden = isPageHidden();
    if (!hidden) {
      cb();
    }
  }, timeout);

  if (visibilityChangeProperty) {
    document.addEventListener(visibilityChangeProperty, () => {
      clearTimeout(timer);
    });

    return;
  }

  window.addEventListener('pagehide', () => {
    clearTimeout(timer);
  });
}

@suanmei
Copy link
Owner Author

suanmei commented Oct 22, 2018

@Sherryer 其实两个版本都是可以的,上面是我很早的时候的写法,后来发现并不需要放在 setInterval 中,setTimeout 同样可以,也省去了 setInterval 的轮询。

setTimeout 并不会被卡住。

setInterval 实现中的

if (waitTime > 2500) {
  clearInterval(checkOpen);
  cb();
}

就是为了应对卡住或者这 2000ms (当然 2000ms是随便设置的) 中有什么其他特别费时的计算占用很长的时间这种场景,去除不必要的轮询

@uyarn
Copy link

uyarn commented Nov 13, 2018

请问可否进入微信之后跳转小程序界面?

@suanmei
Copy link
Owner Author

suanmei commented Nov 13, 2018

请问可否进入微信之后跳转小程序界面?

暂时没有这种场景,不过可以支持哈,我看一下本周实现一下

@shurong-wang
Copy link

shurong-wang commented Nov 14, 2018

if (waitTime > 2500) {
    clearInterval(checkOpen);
    cb();
}
if (!hide) {
    cb(); // 唤端失败的回调函数
}

请教一下,waitTime > 2500 这段代码判定的是唤端成功,还是失败?
看文章描述,像是成功;看下一段代码注释 cb(); // 唤端失败的回调函数,又像是失败

@suanmei
Copy link
Owner Author

suanmei commented Nov 14, 2018

是失败的意思。失败的时候才需要执行失败回调函数。

时间已经过了 设定的 等待时间,页面还没有隐藏,算是失败

@shurong-wang
Copy link

APP 如果被唤起的话,页面就会进入后台运行,setInterval 在 android 中停止运行。
我们的判断条件比预期时间多设置了 500ms,所以如果安卓中 setInterval 内的函数执行 100 次以内所费时间超过 2500ms,则说明 APP 唤起成功,反之则代表失败

根据文章里这段描述,waitTime > 2500 感觉还是呼端成功。看了一些其他的文章和代码,都是按呼端成功判断的。所以,这部分还是比较困惑

https://www.cnblogs.com/caizhenbo/p/6339715.html
https://juejin.im/post/5ac44a9c6fb9a028d82bf98b

@shurong-wang
Copy link

shurong-wang commented Nov 14, 2018

是失败的意思。失败的时候才需要执行失败回调函数。

时间已经过了 设定的 等待时间,页面还没有隐藏,算是失败

可能我没描述清楚,我想确定的是 if (waitTime > 2500) {} 判断下的 cb() 是呼起成功还是失败?不是if (!hide){} 判断下的 cb()

@suanmei
Copy link
Owner Author

suanmei commented Nov 14, 2018

@shurong2199 你的理解是正确的。我在项目中实际并没有使用 waitTime 去判断,因为 visibilitychange 的兼容性足够应对

晚上我会把文章里的代码更新一下

image

@shurong-kavout
Copy link

@shurong2199 你的理解是正确的。我在项目中实际并没有使用 waitTime 去判断,因为 visibilitychange 的兼容性足够应对

晚上我会把文章里的代码更新一下

image

清楚了,感谢~

@FrankCheungDev
Copy link

你好,请教一下,andorid系统下,对于配置了host的情况来说,generateScheme所生成的链接中并没有能够带上host,会造成无法唤起app的问题。如果我将host拼接到path传入,是否会影响到ios系统下app的唤起?谢谢

@suanmei
Copy link
Owner Author

suanmei commented Dec 18, 2018

@FrankCheungCN 配置了host是你设置了代理?安卓和ios你们的scheme是不一样的吗

@FrankCheungDev
Copy link

FrankCheungDev commented Dec 18, 2018

@FrankCheungCN 配置了host是你设置了代理?安卓和ios你们的scheme是不一样的吗

配置了host是指安卓APP的intent filter配置了host,如
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="myhost" android:scheme="myscheme" /> </intent-filter>
按照https://developer.android.com/guide/topics/manifest/data-element , url scheme应该是由以下及部分组成:
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
我看到generateScheme生成的链接中,只有path和param,并没有前面的host和port,是这个问题。
所以现在我直接用生成的链接无法唤起APP,不知道有什么方法,谢谢

@suanmei
Copy link
Owner Author

suanmei commented Dec 18, 2018

@FrankCheungCN
很少有带host或者port的🤣

我可以给加上,这样在 iphone 上生成的 scheme 同样会带有 host ,你们 android 和 ios 的 scheme 应该是统一的吧

@FrankCheungDev
Copy link

@FrankCheungCN
很少有带host或者port的🤣

我可以给加上,这样在 iphone 上生成的 scheme 同样会带有 host ,你们 android 和 ios 的 scheme 应该是统一的吧

是的,统一的。谢谢噢

@suanmei
Copy link
Owner Author

suanmei commented Dec 20, 2018

@FrankCheungCN 支持 scheme host以及port的功能会在周五的2.0.0版本中发布

@FrankCheungDev
Copy link

@FrankCheungCN 支持 scheme host以及port的功能会在周五的2.0.0版本中发布

好的,感谢。我到时候更新一下

@suanmei
Copy link
Owner Author

suanmei commented Dec 23, 2018

@FrankCheungCN 2.0.0 已更新,API 的修改可以参照 README.md 或者 releases

@wood-liu92
Copy link

请问有没有碰到过某些android机型无法自动唤起app必须手动点击的情况呢

@suanmei
Copy link
Owner Author

suanmei commented May 9, 2019

@lww555 参照#15

@haishengXie0712
Copy link

@suanmei 我扫你提供的二维码,在UC浏览器,华为浏览器发现并不可以正常吊起App.

@suanmei
Copy link
Owner Author

suanmei commented May 17, 2019

@haishengXie0712 唤端配置中国有个单词写错了,已修复 02bbd7b

@haishengXie0712
Copy link

@suanmei 我刚测试了一下确实可以,我这两天看了您的文章,然后自己类似造了个轮子.发现在搜狗浏览器里面会调不起APP.我刚拿了你的测试,也是不行.现在有点卡在这里没有了头绪了.

@suanmei
Copy link
Owner Author

suanmei commented May 17, 2019

搜狗里用的是intent还是scheme

@haishengXie0712
Copy link

用的是indent

@suanmei
Copy link
Owner Author

suanmei commented May 17, 2019

有测试过 scheme是否可行吗

@haishengXie0712
Copy link

我尝试过也是不行的

@suanmei
Copy link
Owner Author

suanmei commented May 18, 2019

那就是真的不行了。可能搜狗也有类似白名单的这种机制,这种就不是技术可以绕过去得了

@haishengXie0712
Copy link

@suanmei 你好,我还想问一下个问题.就是我希望页面进来直接唤起APP,不经过按钮点击的.就是说我直接模拟点击a标签.但是发现在UC浏览器和谷歌浏览器下并不支持.我想问一下,有遇过类似的情况吗?

@suanmei
Copy link
Owner Author

suanmei commented May 24, 2019

可以参考一下 #15 , 打开直接唤起app 的兼容性不好

@cllgeek
Copy link

cllgeek commented Jun 3, 2019

楼主能给个用这个库唤起淘宝的例子吗,感谢

@suanmei
Copy link
Owner Author

suanmei commented Jun 4, 2019

@cllgeek 我不知道淘宝的scheme、universalLink都是什么;我们的场景都是调用自己公司的app,如果是使用其他app的三方服务,得调sdk,还有问题,提新的issue,这个issue有点过大了

Repository owner locked as too heated and limited conversation to collaborators Jun 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants