在应用(微信/浏览器)中拉起手机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);
    }
    }

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