唤醒APP的那些事
移动互联时代,很多互联网服务都会同时具备网站以及移动客户端,很多人认为APP的能帮助建立更稳固的用户关系,于是经常会接到各种从浏览器、webview、短信、甚至是在其他APP中唤醒APP的运营需求。
运营推广场景
-
微信、QQ等 -> 唤醒APP
用户通过某APP分享了一条链接至微信或QQ,用户B点开该链接后,会引导用户B打开该APP或者下载该APP。 -
浏览器 -> 唤醒APP
用户A通过浏览器打开了某APP的M站或者官网,如果检测到A来自手机端,则会引导用户打开该APP或者下载该APP。 -
短信、邮件、二维码等 -> 唤醒APP
用户A打开了某APP的推广短信,邮件或者扫描二维码等,会引导用户打开该APP或者下载该APP。 -
其他APP -> 唤醒APP
用户A通过第三方APP分享了(任何可以分享信息的品台或工具:IM或者短信等)一条链接至用户B,用户B点开该链接后,链接会引导用户B打开指定APP或者下载指定APP。
APP服务化理念
所谓APP的服务化就是利用唤醒功能将APP的特定页面做为一个单独的服务或者内容,通过一定的渠道和载体传播出去,并且能够像传统的网页链接那样被一键唤醒。
更多关于APP服务化理念,推荐大家看看这篇文章。
那么移动平台提供了哪些唤醒APP的方法呢?
如何唤醒APP
目前常见的唤醒APP方式有几种:
- URL Scheme
URL Scheme
是iOS,Android平台都支持,只需要原生APP开发时注册scheme
, 那么用户点击到此类链接时,会自动唤醒APP,借助于URL Router
机制,则还可以跳转至指定页面。比如:
<!-- 唤醒APP并跳转至指定的path页面 -->
<a href="<scheme>://<path>?<params>=<value>">打开APP</a>
<!-- JS设置iframe src跳转至指定的path页面 -->
//创建一个隐藏的iframe
var ifr = document.createElement('iframe');
ifr.src = '<scheme>://<path>?<params>=<value>';
ifr.style.display = 'none';
document.body.appendChild(ifr);
//记录唤醒时间
var openTime = +new Date();
window.setTimeout(function(){
document.body.removeChild(ifr);
//如果setTimeout 回调超过2500ms,则弹出下载
if( (+new Date()) - openTime > 2500 ){
window.location = '指定的下载页面';
}
},2000)
这种方式是当期使用最广泛,也是最简单的,但是需要手机,APP支持URL Scheme
。
优点: 开发成本低,绝大多数都支持,web-native协议制定也简单。
缺点: 错误处理情况因平台不同,难以统一处理,部分APP会直接跳错误页(比如Android Chrome/41,iOS中老版的Lofter);也有的停留在原页面,但弹出提示“无法打开网页”(比如iOS7);iOS8以及最新的Android Chrome/43 目前都是直接停留在当前页,不会跳出错误提示。
支持情况: iOS在实际使用中,腾讯系的微信,QQ明确禁止使用,iOS9以后Safari不再支持通过js,iframe等来触发scheme跳转,并且还加入了确认机制,使得通过js超时机制来自动唤醒APP的方式基本不可用;Android平台则各个app厂商差异很大,比如Chrome从25及以后就同Safari情况一样。
- Android intent
这是Android平台独有的,使用方式如下:
intent:
HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string];
scheme=[string];
end;
这里的HOST/URI-path, 与普通http URL 的host/path书写方式相同, package是Android APP的包名,其它参数如action、category、component不是很理解, 有兴趣可以去了解官方文档。代码如下:
<!-- 唤醒APP并跳转至指定的path页面 -->
<a href="intent://<role>/<path>#Intent;scheme=<scheme>;package=com. domain;end"">打开APP</a>
如果手机能匹配到相应的APP,则会直接打开;如没有安装,则会跳到手机默认的应用商店,比如Google原生系统Nexus 5,将会直接跳到Google Play,对于国内各厂商定制过的系统,则跳转到各自的默认应用商店,或者弹出商店供选择。intent
比scheme
相对完善的一点是,提供一个打开失败去向URL的选项,可以通过指定参数S.browser_fallback_url
来指定去向URL。比如打开APP动作,如果打开失败,则跳转到APP下载页,这对于国内的特殊网络环境,还是挺有用的。
- Safari内置APP广告条
在页面Head
中增加如下meta
, 添加智能App广告条,可以自动判断是否已安装应用,只能用于Safari,在第三方应用中就不行了。
<meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"
- Android Chrome内置APP安装提示
这个是Mobile Chrome 43 beta新加入的特性,在用户浏览某一个网站多次后,如果Chrome发现该站点有原生APP,则会提示用户下载原生APP,此项特性开发者无法干预,完全是Google的推荐行为。
- Universal Links
在2015年的WWDC大会上,Apple推出了iOS 9的一个功能:Universal Links通用链接。如果你的App支持Universal Links,那就可以访问HTTP/HTTPS链接直接唤起APP进入具体页面,不需要其他额外判断;如果未安装App,访问此通用链接时,可以一个自定义网页。
优点:
- 唯一性:不像自定义的scheme,因为它使用标准的HTTP/HTTPS链接到你的web站点,所以它不会被其它的app所声明.另外,Custom URL scheme 因为是自定义的协议,所以在没有安装 app 的情况下是无法直接打开的,而Universal Links本身是一个HTTP/HTTPS链接,所以有更好的兼容性;
- 安全:当用户的手机上安装了你的app,那么iOS将去你的网站上去下载你上传上去的说明文件(这个说明文件声明了APP可以打开哪些类型的http链接)。因为只有你自己才能上传文件到你网站的根目录,所以你的网站和你的app之间的关联是安全的;
- 可变:当用户手机上没有安装你的app的时候,Universal Links也能够工作。如果你愿意,在没有安装APP的时候,用户点击链接,会在safari中展示你网站的内容;
- 简单:一个URL链接,可以同时作用于网站和app,可以定义统一的web-native协议;
- 私有:其它APP可以在不需要知道是否安装了的情况下和你的APP相互通信;
缺点:
- 只支持iOS9及以上系统;当使用Universal Link打开APP之后,状态栏右上角会出现链接地址,点击它会取消Universal Link,需引导用户重新使用Safari再次打开该链接,弹出Safari内置APP广告条,再点击打开重新开启Universal Link。
iOS9开启Universal Links
首先,你必须有一个域名,且这个域名的网站需要支持https,然后拥有网站的上传到.well-known目录的权限(这个权限是为了上传一个Apple指定的文件apple-app-site-association
),有了这个先决条件才能够继续下面的步骤:
- 创建一个json格式的命名为
apple-app-site-association
文件,注意这个文件必须没有后缀名,文件名必须为```apple-app-site-association``!!!
{
"applinks": {
"apps": [],
"details": [
{
"appID": "9JA89QQLNQ.com.apple.wwdc",
"paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]
},
{
"appID": "ABCD1234.com.apple.wwdc",
"paths": [ "*" ]
}
]
}
}
说明: appID = teamId.yourapp's bundle identifier
paths = APP支持的路径列表,只有这些指定的路径的链接,才能被APP所处理,大小写敏感。举个例子,如果你的网站是www.domain.com
,你的path写的是"/support/*",那么当用户点击www.domain.com/support/<path>?<params>=<value>
,就可以唤醒APP了,相反www.domain.com/other
就不会。此外Apple为了方便开发者,提供了一个网址来验证我们编写的这个apple-app-site-association
是否合法有效。
-
激活Xcode工程中的
Associated Domains
能力,在其中的Domains中填入你想支持的域名(这里不是随便填的,是可以支持你需要的Universal Links的域名), 必须以applinks:
为前缀,例如:applinks:www.domain.com
Apple将会在合适的时候,从这个域名请求apple-app-site-association
文件。注意:当你打开Associated Domains
后,Xcode会在你的工程中添加.entitlements
文件,并且登录开发者中心,可以看到Associated Domains
处于Enable状态。 -
在
AppDelegate
里实现如下代理方法:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
// NSUserActivityTypeBrowsingWeb 由Universal Links唤醒的APP
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSURL *webpageURL = userActivity.webpageURL;
NSString *host = webpageURL.host;
if ([host isEqualToString:@"yohunl.com"]) {
//进行我们需要的处理
} else {
[[UIApplication sharedApplication]openURL:webpageURL];
}
}
return YES;
}
至此APP已经开启Universal Links
,可以通过链接唤醒APP,并跳转至指定页面了。
- Android App Links
在2015年的Google I/O大会上,Android M宣布了一个新特性:App Links让用户在点击一个普通web链接的时候可以打开指定APP的指定页面,前提是这个APP已经安装并且经过了验证,否则会显示一个打开确认选项的弹出框。在推动deep linking上Google和Apple可谓英雄所见略同,优缺点也大致相同,只支持Android M以上系统。
Android M开启Universal Links
开启Android App Links
的方式也大致同iOS一致:
先决条件:
- 注册一个域名
-
域名的SSL通道
-
具有上传JSON文件到域名的能力
-
Android Studio 1.3 Preview 及以上
-
Gradle 版本 — com.android.tools.build:gradle:1.3.0-beta3 及以上
-
设置 compileSdkVersion 为 android-MNC 及以上
-
buildToolsVersion — 23.0.0 rc2 及以上
-
创建一个json格式的web-app关联文件,如
assetlinks.json
,上传到web端
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "example.com.puppies.app",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "example.com.monkeys.app",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]
其中
package_name
: manifest中声明的包名。
sha256_cert_fingerprints
: 可以使用如下命令生成APP的sha256指纹签名
// keystore中持有app release keys的app路径。
// 这个路径依赖于项目设置,因此不同的app是不同的。
keytool -list -v -keystore my-release-key.keystore
上传这个文件到服务器的.well-known/assetlinks.json
,为了避免今后每个app链接请求都访问网络,安卓只会在app安装的时候检查这个文件。
- 创建一个处理
App Links
的activity,这个activity的目的是为了实现一种这样的机制:负责捕获与解析深度链接,同时转发用户到正确的视图。同时配置激活App Links
能力,如下所示:
<activity
android:name="com.your.app.activity.ParseDeepLinkActivity"
android:alwaysRetainTaskState="true"
android:launchMode="singleTask"
android:noHistory="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
// 此处激活 App Links
<intent-filter android:autoVerify="true">
// 注意yourdomain.com 与 www.yourdomain.com 被看成两个不同的域名,因此你需要为每个域名添加一对http和https
<data android:scheme="http" android:host="yourdomain.com" />
<data android:scheme="https" android:host="yourdomain.com" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
- 实现
App Links
activity的处理逻辑
public class ParseDeepLinkActivity extends Activity {
public static final String PRODUCTS_DEEP_LINK = "/products";
public static final String XMAS_DEEP_LINK = "/campaigns/xmas";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Extrapolates the deeplink data
Intent intent = getIntent();
Uri deeplink = intent.getData();
// Parse the deeplink and take the adequate action
if (deeplink != null) {
parseDeepLink(deeplink);
}
}
private void parseDeepLink(Uri deeplink) {
// The path of the deep link, e.g. '/products/123?coupon=save90'
String path = deeplink.getPath();
if (path.startsWith(PRODUCTS_DEEP_LINK)) {
// Handles a product deep link
Intent intent = new Intent(this, ProductActivity.class);
intent.putExtra("id", deeplink.getLastPathSegment()); // 123
intent.putExtra("coupon", deeplink.getQueryParameter("coupon")); // save90
startActivity(intent);
} else if (XMAS_DEEP_LINK.equals(path)) {
// Handles a special xmas deep link
startActivity(new Intent(this, XmasCampaign.class));
} else {
// Fall back to the main activity
startActivity(new Intent(context, MainActivity.class));
}
}
}
至此APP已经开启App Links
,可以通过链接唤醒APP,并跳转至指定页面了。
后记
总结以上各种方案,唤醒能力似乎都不是很完美,从长远技术趋势来看都是Deep Links,都需要
- 一个支持HTTPS的web站
但面对移动互联网浪潮中海量APP的唤醒能力需求,一定会有创业公司来做这件事,比如国外的HoKoLinks,国内的魔窗,是自己造轮子,还是用轮子,各有利弊。