在应用(微信/浏览器)中拉起手机QQ

场景是:当我们做一些活动用来拉新等等时,通常会限制用户只在手Q中打开。比如用户通过微信扫码到了我们H5活动页面。那么我们就可以通过伪协议将手Q拉起,并且将H5页面打开。用户到达的途径有很多,比如通过浏览器,通过微信,通过其他APP应用。


正解代码

先不看原理的话,代码应该是像下面的。今天我也参照了许多别人的代码,但大多数不是这个不兼容,就是微信拉不起来。或者是 IOS 拉起正常,但是 Android 就是不行。这通常是由于代码没有完备造成的。更主要是因为我们的浏览器什么的都在时时更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* 检测浏览器UA
* @type {Function}
*/
var condition = (function() {
var ua = navigator.userAgent,
chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/);

//利用正则获得匹配 安卓 UA
var getAndroidVersion = function() {
var ua = navigator.userAgent.toLowerCase(),
version = ua.match(/android\s([0-9\.]*)/);
return version ? version[1].split('.')[0] : false;
};
return {
chrome: chrome, //true or false
androidVersion: getAndroidVersion() //version
}
});

/**
* 获得伪协议
* @param jumpURL
* @returns {string}
*/
function getURL( normal ) {
if(normal) {
return 'mqqapi://forward/url?src_type=internal&version=1&url_prefix='+ btoa(location.href);
}

return "intent://forward/url?src_type=web&style=default&=1&version=1&url_prefix=" + btoa(location.href) + "#Intent;scheme=mqqapi;package=com.tencent.mobileqq;end";
}

/**
* 检测是否安装了QQ,如果没有安装要引导用户安装
*/
var checkIfInstallQQ = function() {

WeixinJSBridge.invoke("getInstallState", {
"packageUrl": "mqq://", //ios
"packageName": "com.tencent.mobileqq" //android

}, function(res) {

if(/^get_install_state:yes/.test(res.err_msg)) {
window.open( getURL( true ), '_self' );

window.setTimeout(function() {
WeixinJSBridge.invoke("closeWindow");
}, 1500);

} else if(/^get_install_state:no$/.test(res.err_msg)) {
if(confirm('您还没有安装手Q,现在去下载安装?')) {
window.location.replace('http://im.qq.com/mobileqq/touch/index.html');
}
} else {
Alert.show({
showCancel:false,
msg: "err:" + res.err_msg
});
}

});
};

/**
* 判断是否在手q中打开,如果不是,则判断是否在微信打开
* 如果在微信打开,调用微信的 JSBridge
*/
if( !U.ua.QQ ){
Alert.show({
showCancel:false,
msg: "请在手q中打开此页面",
onConfirm : function () {
//如果在微信中打开
if(U.ua.weixin) {
if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {
checkIfInstallQQ();
} else {
if (document.addEventListener) {
document.addEventListener("WeixinJSBridgeReady", checkIfInstallQQ, false);
} else if (document.attachEvent) {
document.attachEvent("WeixinJSBridgeReady", checkIfInstallQQ);
document.attachEvent("onWeixinJSBridgeReady", checkIfInstallQQ);
}
}
}else{
//这里也可以判断下是否安装了手Q
if(U.ua.android && condition.chrome && condition.androidVersion() >= 5) {
window.open( getURL(false) );
}else{
window.open( getURL(true) );
}
}
}
});

return;
}

代码分析

正常情况下我们拉起手q,利用伪协议就可以了。伪协议是形如上面的不是http,https,ftp,之类的协议。例如:

1
2
3
mqqapi://forward/url?src_type=internal&version=1&url_prefix='+ btoa(location.href)

"intent://forward/url?src_type=web&style=default&=1&version=1&url_prefix=" + btoa(location.href) + "#Intent;scheme=mqqapi;package=com.tencent.mobileqq;end"

mqqapi是专门掉起手Q的, 后面的 btoa(location.href)是由于我们需要在手q打开我们的H5,所以这里要利用 location.href, 并且需要 base64的编码。这里的编码我们是利用的 window.btoa 函数。比起自己去编写 base64encode, 这个方法给我们提供了便利。

那么我们调用这一个 mqqapi 就可以了,为什么还要去判断一个 intent 呢?原因是:

Android 4.4 以上将原生的浏览器换成了chrome,而Android 5.开始的chrome不再允许传统的scheme拉起手q。 所以当打开的浏览器是 Android5 的时候,我们就要去调用这个 intent 新协议,而不是原来的 mqqapi协议。但是也要注意,这里的拉起app是需要用户操作的,需要用户确定(点击确认按钮)拉起才可以。所以不要用JS定时器了。

另外要注意 IOS 9.0 safari ,IOS 9.0以后,原生的safari不支持iframe 拉起 scheme 了。可以用 location.href/top.location.href拉起。

那么下面这些代码是什么呢?

1
2
3
4
5
6
7
 WeixinJSBridge.invoke("getInstallState", {
"packageUrl": "mqq://", //ios
"packageName": "com.tencent.mobileqq" //android

}, function(res) {
//......
}

WeixinJSBridge是微信浏览器内置的一个对象。JS API 建立在内置的这个对象中。但是有一个坑要注意。 WeixinJSBridge 不是我们一打开一个 WebView 就可以了的。我们需要在客户端初始化这个对象。当这个对象 ready 的时候,我们去监听,也就是这个 WeixinJSBridgeReady 事件。所以我们在调用这个 JS API 的时候,一定要判断下 WeixinJSBridge 是否存在。也就是

1
2
3
typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function"

document.addEventListener("WeixinJSBridgeReady", checkIfInstallQQ, false);

这两句话的作用。

WeixinJSBridge.invoke 是 WeixinJSBridge 的一个方法,用来唤起相关的事件。这里我们唤起的是 getInstallState 这个事件。用来判断是否用户安装了 手机QQ。

当然还有很多别的 API,比如

1
2
3
4
document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
// 通过下面这个API隐藏底部导航栏,‘showToolbar’是显示导航栏
WeixinJSBridge.call('hideToolbar');
});

延伸和扩展

这里去学习了下下面这些的区别。是可能会碰到的坑。

  1. “top.location.href”是最外层的页面跳转
  2. “window.location.href”、”location.href”是本页面跳转 = self.location.href
  3. “parent.location.href”是上一层页面跳转.

其他方法

上面我们使用 window.open 打开的,其实我们还可以用 ifame 打开。如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ios 9 后,safari 打不开,可以用 location.href 或者 top.location.href 处理
if(mqq.IOS){
location.href = getURL(true);
}else{
//利用 iframe 处理
var iframe = document.createElement("iframe");
if(U.ua.android && condition.chrome&& condition.androidVersion() >= 5){
iframe.src = getURL(false);
}else{
iframe.src = getURL(true);
}
iframe.onload = function () {
setTimeout(function() {
document.body.appendChild(iframe);
}, 0);
}
}

主要这里也是一个简略的写法。可以更加完善的。