场景
业务上有这样一个需求:内部对象链接在第三方 H5 页面(移动浏览器、微信、钉钉等)打开后,跳转到一个 H5 中转页面, 用户可以通过点击页面上的 “打开 APP 查看” 按钮来唤起 APP 并进入 APP 内的目标对象。
思路
- 实现 H5 页面唤起手机 APP,需要借助于 APP 的 URL Schema 协议(应用间的通信方式),这个是由 APP 端开发同事提供;
- 我们判断运行环境来区分是 Android / iOS 端,调用对应的 Schema 协议来唤起 APP(有的公司两端提供的 Schema 格式相同);
- 由于技术上无法监测 Schema 是否成功唤起了 APP,可通过计时器在指定时间后跳转至下载界面,指引用户去下载(视为唤起失败、本地未安装此 APP);
- 另外,比如第三方如 在微信浏览器下使用 Schema 会不起作用(可能微信现在提供了相关标签可以实现),因此需要判断微信环境,去提示指引用户点击右上角”...“在默认浏览器打开。
URL Schema 协议格式
URL Schema 是一种页面内跳转协议,通过这个协议可以比较方便的跳转到 APP 某一个对象页面。
一个简单的 Schema 协议格式如下:
[scheme]://[path]?[query]
scheme: 协议名称(由开发人员自定义)(必要,其他都是可选)
path: 页面路径
query: 请求参数
复制代码
例如,可以这样调用 Schema:
<a href="tenx://file?type=folder&id=ESC">打开 APP 查看</a>
复制代码
应用
1、内部应用(理解为企业员工账户登录的应用)跳到 H5 中转页: 在内部应用可以判断运行环境是否在移动 H5 应用,将内部应用链接作为 query 参数,跳转至 H5 中转页做唤起 APP 处理;
这里我们可以使用高阶组件做处理,比如 React 可以这样使用:
<Route exact path='xxx' render={props => <MobileInterceptor {...props} component={App} />} />
function isRunMobile() {
return /Android|webOS|iPhone|iPad|BlackBerry/i.test(window.navigator.userAgent);
}
const isMobile: boolean = isRunMobile();
type TProps = {
component: React.ComponentType<any>;
} & RouteComponentProps;
const MobileInterceptor = ({ component, ...otherProps }: TProps) => {
if (isMobile) {
const { origin, href } = window.location;
window.location.href = 'H5 唤起APP 页面 url + 当前页面信息作为参数传递';
return null;
}
return React.createElement(component, otherProps);
}
复制代码
2、H5 中转页通过按钮唤起 APP:
当点击按钮时:
- 首先获取运行环境:在微信、钉钉等内置浏览器打开时,指引在手机默认浏览器打开;
- 当处于手机独立浏览器时,首先设置唤起 APP 的加载动画;
- 通过 window.location.href 去访问协议唤起 APP;
- 定义计时器在合适的时间去跳转至下载 APP 界面(视为未唤起成功)。
export function isRunThirdPartyApp() {
const userAgent = navigator.userAgent.toLowerCase();
return {
isWechat: /MicroMessenger/i.test(userAgent),
isQQ: /QQ/i.test(userAgent) && !userAgent.toLowerCase().includes('mqqbrowser'),
isWebo: /WeiBo/i.test(userAgent),
isDing: /DingTalk/i.test(userAgent),
isMail: /Mail/i.test(userAgent),
}
}
const [status, setStatus] = useState<'default' | 'openInBrowser' | 'loading'>('default');
const openApp = () => {
const runEnvs = isRunThirdPartyApp();
let isOpenInBrowser = Object.keys(runEnvs).some(envKey => runEnvs[envKey as keyof typeof runEnvs]);
if (isOpenInBrowser) {
setStatus('openInBrowser');
return;
}
if (status === 'loading') return;
setStatus('loading');
window.location.href = 'Schema url';
setTimeout(() => {
if (!docHidden.current) {
window.location.href = 'Download url';
}
docHidden.current = false;
setStatus('default');
}, 3000);
}
复制代码
需要注意一处:上面我们使用 docHidden.current 来判断是否要跳转,当成功唤起 APP 时,页面会被隐藏,此时视为成功打开 APP,不再跳转至下载界面。
const docHidden = useRef<boolean>(false); // 页面是否切换,假设成功唤起 APP 后,视为页面被隐藏,则不跳转下载页面
useEffect(() => {
const handleVisibilityChange = () => {
setStatus('default');
if (document.hidden) docHidden.current = true;
}
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
}
});
复制代码
问题记录
在 Android 小米手机浏览器内进行唤起 APP 操作时,遇到这样一个问题:
- 当手机后台没有运行 APP 时,在小米浏览器可以正常唤起 APP 并打开目标对象;
- 当手机后台运行了 APP 时,在小米浏览器进行唤起时,会发现没有打开 APP,而是在小米浏览器上新开了一个窗口来打开目标对象;
- 经过排查后发现,相同的调用 Schema 方式,京东的 Schema 唤起没有问题,而我们要唤起的 APP 却存在问题;
- 最后安卓同事那边排查问题,在代码和配置上兼容处理了小米浏览器通过 Schema 发起唤起 APP 的动作支持。