移动端 H5 页面唤起 APP、微信小程序方案

983 阅读6分钟

前言

移动端 H5 页面通常承担的主要责任就是引流,利用 Web 页面轻量便于传播的特性通过搜索引擎或者社交软件进行传播,然后在页面内引导用户打开 APP 进行消费,引流主要有两种形式:

  1. 拉活:引导已安装 APP 用户打开 APP 进行消费,提升用户粘性和 APP 日活数据。
  2. 拉新:引导未安装 APP 用户安装 APP,增加 APP 的用户量。

唤醒APP方案

H5 页面唤起 APP 的主要技术方案有以下三种:

  • 方案一:URL Scheme(通用)
  • 方案二:Universal Link(IOS)
  • 方案三:wx-open-launch-app(微信)

方案一和方案二属于系统级别解决方案,方案三是在微信内的解决方案,虽然不是一个级别但是重要性却一点都不低。

URL Scheme 方案

手机系统中有很多私密信息,为了保证信息的安全,系统设置沙箱机制,应用只能访问自己沙箱内的资源。沙箱机制保证了数据的安全,同时也阻碍了应用直接的信息共享,URL Scheme 是一种页面跳转协议,跳转同时可以通过参数传递数据。利用这一特性可以实现 H5 页面呼起 APP 功能。

URL Scheme 一般由协议名、路径、参数组,模式如下:[scheme:][//authority][path][?query][#fragment]。APP 安装后会向操作系统注册一个 Scheme,操作系统拦截到请求就会调起匹配的程序进行处理。

我们常规的请求发送基本都可以用来触发 APP Scheme,例:

  1. <a>标签链接跳转
  2. 直接通过window.location.href触发
  3. 通过 iframe 触发

例:唤起通话呼叫 10086window.location.href = 'tel:10086';

URL Scheme 方案优点:

  1. 兼容性好,Android 和 IOS 系统都支持
  2. 不要求用户主动触发,可以使用 JS 触发

URL Scheme 方案缺点:

  1. 体验问题,需要用户二次确认,呼起失败处理有延迟
  2. 容易被拦截,在微信、百度等 APP 主要浏览入口都有白名单机制,非白名单内的 Scheme 都会被拦截无法呼起 APP

体验问题一:使用 URL Scheme 呼起 APP 系统会弹出确认框要求用户进行二次确认

URL Scheme呼起APP

体验问题二:使用 URL Scheme 呼起 APP,唤起失败处理有延迟

Web 页面无法判断用户是否安装 APP 也无法感知用户的二次确认是否通过,通常我们都希望引导未安装 APP 用户到 APP 下载页面安装 APP。因为无法感知用户是否安装 APP 和用户的二次确认结果,通常的解决方案是通过visibilitychange配合定时器来判断,在 APP 呼起触发一段时间后如果页面仍是可见状态,那么就认为 APP 呼起失败,失败后通常会引导用户到下载页。

未安装用户唤起效果:

用户不允许唤起 APP 效果:

核心代码:

let timer = null;
const jumpAppInH5 = () => {
  // 呼起APP
  window.location.href = 'APP Scheme 地址';

  // 3s之后跳转到APP下载页
  if (timer) clearTimeout(timer);
  timer = setTimeout(() => {
      window.location.href = 'APP下载页面地址';
  }, 3000);

  // 跳转app之后禁止再跳转中间页
  document.addEventListener('visibilitychange', () => {
    clearTimeout(timer);
  });
};

体验问题三:呼起 APP 二次弹窗,用户长时间未进行确认,这时即使用户同意打开 APP 页面也仍然会任务呼起失败跳转到 APP 下载页。

用户长时间未确认示例:

产生这个问题的原因是浏览器的 Event Loop 机制,二次确认弹窗虽然阻塞主进程,但是定时器并不在主进程执行,定时器执行完成之后会将回调函数添加到任务队列,一旦用户进行二次确认主进程在空闲后会立即处理任务队列中的任务跳转到 APP 下载页。

Universal Link 方案

Universal Link 是 IOS9(2015 年 6 月发布) 中新增的功。它可允许接通过 https 协议的链接来打开 APP。

和 URL Scheme 方案类似,在 APP 中注册自己要支持的域名,系统拦截到请求后会直接呼起匹配的 APP,如果没有匹配的 APP 则直接使用浏览器打开。

优点:从系统层面用户体验更好

  • 唤端时没有弹窗提示是否打开,提升用户体验
  • 对未安装 APP 的用户也会使用浏览器打开页面,不影响用户浏览体验

缺点:

  • 只支持 IOS 系统
  • 需要用户主动触发(例如点击),不能通过 JS 触发
  • 不能实现呼端失败后引导到 APP 下载页面,因为用户触发后一定会跳离当前页面

Android 提出了类似 Universal Link 的 App Link 方案 和 Chrome Intents 方案,在国内支持情况较差应用较少,感兴趣可以自行查找资料了解。

wx-open-launch-app 方案

因为微信的白名单机制 URL Scheme 唤起请求会被拦截,但是微信在国内环境是绝对的头部流量入口,是不能够放弃的。微信提供开放标签wx-open-launch-app方案可以实现在微信内通过 H5 唤起 APP。详见开放标签说明文档

微信开放标签唤起 APP 示例:

使用wx-open-launch-app需要满足两个前置条件:

  • 条件一:页面接入 JS-SDK
    • 步骤一:域名绑定
      • 配置入口:微信公众平台-设置与开发-公众号设置-功能设置-JS 接口安全域名
      • 需要在配置的域名服务器部署平台提供的验证文件
    • 步骤二:引入 JS-SDK 文件,在页面使用 SDK 之前需要先引入
    • 步骤三:通过wx.config接口注入权限验证配置
      • 注意使用前端路径实现的 SPA 应用需要在 URL 变化后重新验证配置
      • 权限验证需要签名,需要后端能力
    • 监听wx.ready()wx.error()回调实现后续业务功能
  • 条件二:App 必须接入微信 OpenSDK,详见接入指南

微信开放标签wx-open-launch-app核心代码

<script src="//res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script>
  wx.config({
    debug: false, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印。
    appId: "xxxxxxxxxxxx",
    timestamp, // 必填,生成签名的时间戳
    nonceStr, // 必填,生成签名的随机串
    signature, // 必填,签名
    jsApiList: ["onMenuShareTimeline", "onMenuShareAppMessage"], // 注册的方法
    openTagList: ["wx-open-launch-app", "wx-open-launch-weapp"], // 注册的开放标签
  });
</script>

<wx-open-launch-app id="launch-btn" appid="your-appid" extinfo="your-extinfo">
  <script type="text/wxtag-template">
    <style>.btn { padding: 12px }</style>
    <button class="btn">App内查看</button>
  </script>
</wx-open-launch-app>

<script>
  // 监听APP拉取回调,实现业务逻辑处理
  var btn = document.getElementById('launch-btn');
  btn.addEventListener('launch', function (e) {
    console.log('success');
  });
  btn.addEventListener('error', function (e) {
    console.log('fail', e.detail);
  });
</script>

wx-open-launch-app使用注意事项:

  1. 必须是通过卡片形式进入页面的页面才会生效
  2. SPA 页面路径:调用wx.config注入配置信息只在当前页面生效,同一个 url 仅需调用一次,对于变化 url 的 SPA 的 web app 需要在每次 url 变化时进行调用
  3. 开放标签依赖 Web Components 方案,极少部分 Android 系统可能由于版本太低而不支持

生产常用APP 唤醒方案

常见方式主要采用 URL Scheme 和wx-open-launch-app方案结合实现客户端唤起。 举例,投放渠道是通过微信和手机短信,需要同时支持浏览器和微信环境内 APP 呼起,APP 唤起失败需要自动跳转到 APP 下载页面。

我们对页面运行环境检测、微信开放标签初始化和唤起失败处理等非业务逻辑进行封装,简化日常业务逻辑开发。 组件使用插槽和高阶组件的思想封装,自动判断当前运行环境调用合适的方式进行客户端唤起,组件名称OpenLaunchApp

组件使用(Vue):

<OpenLaunchApp :launchHandler="handleOpenAppClick">
    <div class="btn-open-app" @click="handleOpenAppClick">打开APP查看更多</div>
</OpenLaunchApp>
<script>
  export default {
    methods: {
      // 呼起回调,可以用来处理数据上报等逻辑
      handleOpenAppClick() {}
    }
  }
</script>
<style>
.btn-open-app {
    width: 100%;
    height: 90px;
    margin: 15px 0px 15px;
    line-height: 90px;
    border-radius: 12px;
    font-size: 32px;
    font-weight: 500;
    text-align: center;
    position: relative;
    color: rgba(255, 255, 255, 1);
    background-color: rgba(0, 195, 168, 1);
}
</style>

组件核心代码(Vue):

<template>
    <div class="root-wx-launch" v-if="isAvailLaunchApp && initWxConfigSuccess">
        <!-- 使用插槽 插入按钮 -->
        <slot />
        <wx-open-launch-app
            class="launch-btn"
            appid="xxxxxxxxxxxxxxx"
            @ready="handleReady"
            @error="handleError"
            @launch="handleLaunch"
            :extinfo="extinfo"
        >
            <component v-bind:is="'script'" type="text/wxtag-template">
                <!-- 元素必须有尺寸,否则无法触发呼端,但不要求元素被点击才能触发 -->
                <div class="wx-btn" style="width: 100%; height: 50px"></div>
            </component>
        </wx-open-launch-app>
    </div>
    <div v-else @click="jumpAppInH5"><slot /></div>
</template>
<script setup>
import { ref } from 'vue';

import { isAvailLaunchApp as isAvailLaunchAppCheck } from '@/utils/osUtil';
import launchAppUtil from '@/utils/launchAppUtil';

const props = defineProps({
    launchUrl: {
        type: String,
        required: false,
        default: window.location.href,
    },
    // 呼端成功回调,可以用啦处理埋点等需求
    launchHandler: {
        type: Function,
        required: false,
    },
});

const isAvailLaunchApp = isAvailLaunchAppCheck();
const initWxConfigSuccess = ref(true);

const {
    extinfo,
    handleReady,
    jumpAppInH5,
    handleDownloadApp,
    handleError: defaultErrorHandler,
} = launchAppUtil(
    props.launchUrl,
    () => {
        initWxConfigSuccess.value = false;
    },
    // 避免SPA前端路由调整导致微信配置验证失败
    location.href.split('#')[0],
);

const handleLaunch = (e) => {
    console.log('launch success', e.detail);
    if (typeof props.launchHandler === 'function') {
        props.launchHandler();
    }
};

const handleError = (error) => {
    handleDownloadApp();
};
</script>
<style scoped>
/* 根元素相对定位,根元素尺寸又通过插槽传入的元素撑起 */
.root-wx-launch {
  position: relative;
}
/* 通过绝对定位,使wx-open-launch-app尺寸覆盖插入元素 */
.launch-btn {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
    z-index: 99;
    overflow: hidden;
}
</style>

唤醒APP场景总结

1、移动浏览器h5打开APP

      URL Scheme(通用)、Universal Link(IOS)

2、微信公众号h5打开APP

      使用微信开放标签wx-open-launch-app,走官方的中间页a.app.qq.com打开浏览器,再拉起APP

3、微信小程序h5打开APP

4、微信小程序原生页面打开APP

第一种:

  • 只有当小程序通过特定场景值(如1069)被打开时,才具备返回App的能力
  • 使用<button open-type="launchApp">组件实现,可设置app-parameter传递参数
  • 需要监听binderror事件处理可能的错误
  • 小程序不能主动打开任意App,只能返回到启动它的App

    参考:打开 App

第二种:

    参考:打开第三方 App

5、移动端APP打开第三方APP

6、短信打开APP

唤醒微信小程序方案

wx-open-launch-weapp方案

微信开放标签唤起 微信小程序 示例:

使用wx-open-launch-weapp需要满足前置条件:

  • 条件一:页面接入 JS-SDK
    • 步骤一:域名绑定
      • 配置入口:微信公众平台-设置与开发-公众号设置-功能设置-JS 接口安全域名
      • 需要在配置的域名服务器部署平台提供的验证文件
    • 步骤二:引入 JS-SDK 文件,在页面使用 SDK 之前需要先引入
    • 步骤三:通过wx.config接口注入权限验证配置
      • 注意使用前端路径实现的 SPA 应用需要在 URL 变化后重新验证配置
      • 权限验证需要签名,需要后端能力
    • 监听wx.ready()wx.error()回调实现后续业务功能

微信开放标签wx-open-launch-weapp核心代码:

/** * 动态生成标签打开微信小程序 */

export function wxappLaunch(info: any, { successCallback, failCallback }) {

    const wxOpenLaunchId = `wxOpenLaunch${Math.random().toString(16).slice(2)}`

    const script = document.createElement('script') 

    script.type = 'text/wxtag-template' 

    script.text = info.content 

    const wxappopen: any = document.getElementById(info.eleId) 

    const html = `<wx-open-launch-weapp username="${info.appid}" 
id="${wxOpenLaunchId}" path="${info.path}">${script.outerHTML}</wx-open-launch-weapp>`
    
    wxappopen.innerHTML = html 

    const wxOpenBtn: any = document.getElementById(wxOpenLaunchId)

    wxOpenBtn.addEventListener('launch', function () {

        console.log('launch-btn success')

        successCallback && successCallback()

    })


    wxOpenBtn.addEventListener('error', function (e: any) {
        console.log(`launch-btn error =  ${JSON.stringify(e)}`, e.detail)

        failCallback && failCallback()

    })

    return wxOpenBtn

}

import styles from './index.module.less'
import { onMount } from 'solid-js'
import { isProductEnv } from '@/utils/config'


// 自定义html内容
const content = `

<style>

.weapp-btn {

    background-color: #dc1e32;

    border-radius: 55px;

    color: white;

    font-size: 16px;

    width: 220px;

    height: 40px;

    padding: 8px;

    border: none;

    outline: none;

    opacity: 1;

    z-index: 10;

}

</style>

<button class="weapp-btn">立即前往小程序-微信标签</button>`


function WxOpenLaunchWxapp(props: any) {

    const { config = {}, launchSuccess = () => {}, launchError = () => {} } = props

    let wxOpenLaunchRef

    const launchParams = {
        eleId: 'launch-btn-wxapp', // 元素id
        appid: isProductEnv ? '生产小程序appid' : '测试小程序appid',
        path: config.schemeParams || '', // 跳转小程序的页面路径
        content // 自定义的html内容
     }

    onMount(() => {
        init()
    })

    const init = () => {

        wxappLaunch(launchParams, {

            successCallback: launchSuccess,

            failCallback: launchError

        })

    }

    // 点击跳转小程序标签

    const goToWxApp = () => {

        props.onClick && props.onClick()

    }

    return (

        <div class={styles.appEnterBox} ref={wxOpenLaunchRef} onClick={goToWxApp}>

            {props.children}

            <div class={styles.wxopen} id={launchParams.eleId}></div>

        </div>

    )

}

export default WxOpenLaunchWxapp

组件样式:

.appEnterBox {

    position: relative;

    display: inline-block;

    text-align: center;

    width: 100%;

}

.wxopen {

    position: absolute;

    left: 0;

    top: 0;

    bottom: 0;

    right: 0;

    z-index: 2;

    overflow: hidden;

    opacity: 0;

}

使用封装好的组件:

const wxLaunchConfig = {schemeParams: `pages/main/my/home`}

<WxOpenLaunchWxapp config={wxLaunchConfig} onClick={goSend}>
      <div class={styles.textContainer}>
           <text class={styles.btnText}>跳转小程序</text>
      </div>
</WxOpenLaunchWxapp>

样式(注意:textContainer必须设置position: unset;):

.textContainer {

    height: 48px;

    display: flex;

    align-items: center;

    justify-content: center;

    position: unset;

}

.btnText {

    font-size: 16px;

    font-weight: 500;

    font-family: 'PingFang SC';

    color: rgba(255, 255, 255, 1);

}

唤醒微信小程序场景总结

1、移动浏览器h5打开微信小程序

2、微信公众号h5打开微信小程序页面

      使用微信开放标签wx-open-launch-weapp

3、微信小程序h5打开微信小程序页面

      使用微信js-sdk提供的方法,wx.miniprogram.navigateTo()

4、微信小程序原生页面打开第三方微信小程序

      微信官方的方法,带上目标小程序appid跳转

5、移动端APP打开微信小程序

     App打开下程序有两种方式

     5-1、通过App分享小程序卡片到微信,然后在微信上点击小程序卡片打开小程序

     • 第一步:你需要到微信开放平台 将你的app 关联上你的小程序!

     • 第二步:你工程中需要导入有 wechatOpenSDK 

     • 第三步: 分享小程序卡片类型

     官方文档:​ 分享图片到微信

     5-2、App放置小程序链接,点击链接主动打开小程序

     举例:在小红书发帖,文案内容中嵌入链接,点击链接可以打开微信小程序。

     官方文档:获取小程序链接

5-3、点击App上按钮,打开小程序

举例:美团App点击打开小程序按钮,打开美团微信小程序。

官方文档:App 拉起小程序

6、移动端App内嵌h5打开微信小程序

     App提供打开微信小程序的方法,h5通过JsBridge调用App提供的方法(参考上面第5点)

7、短信打开微信小程序

      调用微信提供的接口生成的短链(建议使用URL Link)

      官方文档:

      短信打开小程序

      获取加密URLLink

8、微信公众号推送打开微信小程序

      微信官方提供的配置

9、微信服务号推送打开微信小程序

      微信官方提供的配置

参考:

移动端 H5 页面唤起 APP 方案

APP 跳转微信小程序和回调