如何检查应用程序是否从 iPhone 上的网页安装

我想创建一个网页,如果 iPhone 没有安装应用程序,这个网页会将 iPhone 重定向到 应用程序商店,但是如果 iPhone 已经安装了应用程序,我希望它打开应用程序。

我已经在 iPhone 应用程序中实现了一个自定义 URL,所以应用程序的 URL 类似于:

myapp://

如果这个 URL 无效,我希望页面重定向到应用程序商店。这有可能吗?

如果我没有在手机上安装应用程序并在 Safari 中编写 myapp://URL,我得到的只是一条错误消息。

即使存在使用 JavaScript 的丑陋的黑客行为,我真的很想知道。

258485 次浏览

据我所知,你不能,从浏览器,检查应用程序是否安装。

但是你可以尝试重定向手机到应用程序,如果没有发生任何事情,重定向手机到一个指定的页面,像这样:

setTimeout(function () { window.location = "https://itunes.apple.com/appdir"; }, 25);
window.location = "appname://";

如果第二行代码给出一个结果,那么第一行将永远不会执行。

类似的问题:

为了进一步得到公认的答案,有时需要添加额外的代码来处理在启动应用程序后返回浏览器的用户—— setTimeout 函数将随时运行。所以,我做了这样的事情:

var now = new Date().valueOf();
setTimeout(function () {
if (new Date().valueOf() - now > 100) return;
window.location = "https://itunes.apple.com/appdir";
}, 25);
window.location = "appname://";

这样,如果代码执行中出现冻结(比如,应用程序切换) ,它就不会运行。

日期解决方案比其他方案要好得多。我不得不像那样把时间增加到50。

这是一个 Twitter 的例子:

// On click of your event handler...
var twMessage = "Your Message to share";
var now = new Date().valueOf();
setTimeout(function () {
if (new Date().valueOf() - now > 100)
return;
var twitterUrl = "https://twitter.com/share?text=" + twMessage;
window.open(twitterUrl, '_blank');
}, 50);
window.location = "twitter://post?message=" + twMessage;

在移动 iOS Safari 上唯一的问题是当你没有在设备上安装应用程序时,Safari 会显示一个警报,当新的 URL 被打开时会自动解除警报。无论如何,这是一个很好的解决方案!

我需要做这样的事情,我最终采用了下面的解决方案。

我有一个特定的网站 URL,将打开一个页面与两个按钮

  1. 按钮 去网站

  2. 按钮 转到应用程序(iPhone/Android 手机/平板电脑)。如果没有安装应用程序(比如另一个 URL 或应用程序商店) ,你可以从这里回到默认位置

  3. Cookie 来记住用户的选择

     <head>
    <title>Mobile Router Example </title>
    
    
    
    
    <script type="text/javascript">
    function set_cookie(name,value)
    {
    // JavaScript code to write a cookie
    }
    function read_cookie(name) {
    // JavaScript code to read a cookie
    }
    
    
    function goToApp(appLocation) {
    setTimeout(function() {
    window.location = appLocation;
    // This is a fallback if the app is not installed.
    // It could direct to an app store or a website
    // telling user how to get the app
    }, 25);
    window.location = "custom-uri://AppShouldListenForThis";
    }
    
    
    function goToWeb(webLocation) {
    window.location = webLocation;
    }
    
    
    if (readCookie('appLinkIgnoreWeb') == 'true' ) {
    goToWeb('http://somewebsite');
    
    
    }
    else if (readCookie('appLinkIgnoreApp') == 'true') {
    goToApp('http://fallbackLocation');
    }
    
    
    </script>
    </head>
    
    
    <body>
    <div class="iphone_table_padding">
    <table border="0" cellspacing="0" cellpadding="0" style="width:100%;">
    <tr>
    <td class="iphone_table_leftRight">&nbsp;</td>
    <td>
    <!-- Intro -->
    <span class="iphone_copy_intro">Check out our new app or go to website</span>
    </td>
    <td class="iphone_table_leftRight">&nbsp;</td>
    </tr>
    <tr>
    <td class="iphone_table_leftRight">&nbsp;</td>
    <td>
    <div class="iphone_btn_padding">
    
    
    <!-- Get iPhone app button -->
    <table border="0" cellspacing="0" cellpadding="0" class="iphone_btn" onclick="set_cookie('appLinkIgnoreApp',document.getElementById('chkDontShow').checked);goToApp('http://getappfallback')">
    <tr>
    <td class="iphone_btn_on_left">&nbsp;</td>
    <td class="iphone_btn_on_mid">
    <span class="iphone_copy_btn">
    Get The Mobile Applications
    </span>
    </td>
    <td class="iphone_btn_on_right">&nbsp;</td>
    </tr>
    </table>
    
    
    </div>
    </td>
    <td class="iphone_table_leftRight">&nbsp;</td>
    </tr>
    <tr>
    <td class="iphone_table_leftRight">&nbsp;</td>
    <td>
    <div class="iphone_btn_padding">
    
    
    <table border="0" cellspacing="0" cellpadding="0" class="iphone_btn"  onclick="set_cookie('appLinkIgnoreWeb',document.getElementById('chkDontShow').checked);goToWeb('http://www.website.com')">
    <tr>
    <td class="iphone_btn_left">&nbsp;</td>
    <td class="iphone_btn_mid">
    <span class="iphone_copy_btn">
    Visit Website.com
    </span>
    </td>
    <td class="iphone_btn_right">&nbsp;</td>
    </tr>
    </table>
    
    
    </div>
    </td>
    <td class="iphone_table_leftRight">&nbsp;</td>
    </tr>
    <tr>
    <td class="iphone_table_leftRight">&nbsp;</td>
    <td>
    <div class="iphone_chk_padding">
    
    
    <!-- Check box -->
    <table border="0" cellspacing="0" cellpadding="0">
    <tr>
    <td><input type="checkbox" id="chkDontShow" /></td>
    <td>
    <span class="iphone_copy_chk">
    <label for="chkDontShow">&nbsp;Don&rsquo;t show this screen again.</label>
    </span>
    </td>
    </tr>
    </table>
    
    
    </div>
    </td>
    <td class="iphone_table_leftRight">&nbsp;</td>
    </tr>
    </table>
    
    
    </div>
    
    
    </body>
    
    
    </html>
    

@ Alistair 在 这个答案中指出,有时用户打开应用程序后会返回浏览器。一位评论者对这个答案表示,使用的时间值必须根据 iOS 版本进行更改。

当我们的团队不得不处理这个问题时,我们发现初始超时的时间值和告诉我们是否已经返回到浏览器的时间值必须进行调整,而且通常不适用于所有的用户和设备。

与其使用任意的时差阈值来确定我们是否已经返回到浏览器,检测“ pagehide”和“ pageshow”事件是有意义的。

我开发了以下网页来帮助诊断正在发生的事情。随着事件的展开,它添加了 HTML 诊断,这主要是因为使用控制台日志、警报或 Web 检查器、 jsfiddle.net 等技术在这个工作流中都有缺点。JavaScript 不使用时间阈值,而是计算“ pagehide”和“ pageshow”事件的数量,以查看它们是否已经发生。我发现最健壮的策略是使用1000的初始超时(而不是其他人报告和建议的25、50或100)。

这可以在本地服务器上使用,例如 python -m SimpleHTTPServer,也可以在 iOS Safari 上查看。

要使用它,请按“打开已安装的应用程序”或“应用程序未安装”链接。这些链接应该分别导致地图应用程序或应用程序商店打开。然后您可以返回到 Safari 查看事件的顺序和时间。

(注意: 这只适用于 Safari。对于其他浏览器(比如 Chrome) ,你必须为 pagehide/show 等效事件安装处理程序。

更新: 正如@Mikko 在评论中指出的,我们使用的页面显示/页面隐藏事件显然不再受 iOS8的支持。

<html>
<head>
</head>


<body>
<a href="maps://" onclick="clickHandler()">Open an installed app</a>
<br/><br/>
<a href="xmapsx://" onclick="clickHandler()">App not installed</a>
<br/>


<script>
var hideShowCount = 0 ;
window.addEventListener("pagehide", function() {
hideShowCount++;
showEventTime('pagehide');
});


window.addEventListener("pageshow", function() {
hideShowCount++;
showEventTime('pageshow');
});


function clickHandler(){
var hideShowCountAtClick = hideShowCount;
showEventTime('click');
setTimeout(function () {
showEventTime('timeout function ' + (hideShowCount-hideShowCountAtClick) + ' hide/show events');
if (hideShowCount == hideShowCountAtClick){
// app is not installed, go to App Store
window.location = 'http://itunes.apple.com/app';
}
}, 1000);
}


function currentTime()
{
return Date.now()/1000;
}


function showEventTime(event){
var time = currentTime() ;
document.body.appendChild(document.createElement('br'));
document.body.appendChild(document.createTextNode(time + ' ' + event));
}
</script>
</body>


</html>

IOS Safari 有一个功能,允许你添加一个“智能”横幅到你的网页,将链接到你的应用程序,如果它是安装的,或到应用程序商店。

可以通过向页面添加 meta标记来实现这一点。你甚至可以指定一个详细的应用程序 URL,如果你想让应用程序在加载时做一些特殊的事情。

详情见苹果 用智能应用程序横幅推广应用程序页面。

该机构具有操作简单、旗帜标准化等优点。缺点是你不能很好地控制外观和位置。另外,如果页面是在 Safari 以外的浏览器中浏览的,那么所有的赌注都会被取消。

您可以查看这个试图解决这个问题的插件。它基于 Missemisa 和 Alastair 等描述的相同方法,但是使用了一个隐藏的 iframe。

Https://github.com/hampusohlsson/browser-deeplink

我没有阅读所有这些答案,但您可能使用了一个 iframe,并将源代码添加到“ my app://whatever”。

然后定期检查页面的设定间隔是否为404。

你也可以使用一个 Ajax 调用。如果有一个404响应,那么应用程序没有安装。

在编译了一些答案之后,我得出了以下代码。令我惊讶的是,计时器 没有在个人电脑(Chrome 和 Firefox)或 Android Chrome 上会被冻结——触发器在后台工作,而可见性检查是唯一可靠的信息。

var timestamp        = new Date().getTime();
var timerDelay       = 5000;
var processingBuffer = 2000;


var redirect = function(url) {
//window.location = url;
log('ts: ' + timestamp + '; redirecting to: ' + url);
}


var isPageHidden = function() {
var browserSpecificProps = {hidden:1, mozHidden:1, msHidden:1, webkitHidden:1};
for (var p in browserSpecificProps) {
if(typeof document[p] !== "undefined"){
return document[p];
}
}
return false; // Actually inconclusive, assuming not
}
var elapsedMoreTimeThanTimerSet = function(){
var elapsed = new Date().getTime() - timestamp;
log('elapsed: ' + elapsed);
return timerDelay + processingBuffer < elapsed;
}


var redirectToFallbackIfBrowserStillActive = function() {
var elapsedMore = elapsedMoreTimeThanTimerSet();
log('hidden:' + isPageHidden() + '; time: ' + elapsedMore);
if (isPageHidden() || elapsedMore) {
log('not redirecting');
}else{
redirect('appStoreUrl');
}
}


var log = function(msg){
document.getElementById('log').innerHTML += msg + "<br>";
}


setTimeout(redirectToFallbackIfBrowserStillActive, timerDelay);
redirect('nativeApp://');

JS 小提琴

截至2017年,似乎还没有可靠的方法来检测应用程序是否安装,而且重定向技巧也不会在任何地方奏效。

对于那些像我一样需要直接与电子邮件进行深度链接的人(这很常见) ,值得注意以下几点:

  • 使用 appScheme://发送电子邮件不能正常工作,因为链接将在 Gmail 中过滤

  • 自动重定向到 appScheme://被 Chrome 阻止: 我怀疑 Chrome 需要重定向与用户交互同步(比如点击)

  • 您现在可以不使用 appScheme://进行深度链接,这样更好,但是需要一个现代化的平台和额外的设置。IOS 操作系统


值得注意的是,其他人已经深入思考过这个问题。如果你看看 Slack 是如何实现他的“魔法链接”功能的,你会发现:

  • 它发送一封带有常规 HTTP 链接的电子邮件(Gmail 也可以)
  • 该网页有一个大按钮,可以链接到 appScheme://(可以使用 Chrome 浏览器)

以下的答案仍然可以在 iOS10到14上测试。它建立在早期答案的基础上。我添加了 window.close(),以消除重定向或页面返回后留在浏览器中的空选项卡窗口。如果修复了4个场景中的2个,那么空白标签将被留下... ... 也许其他人可以修复第3和第4个

<script>
var now = new Date().valueOf();
setTimeout(function () {
// time stamp comaprison prevents redirecting to app store a 2nd time
if (new Date().valueOf() - now > 100) {
window.close() ;  // scenario #4
// old way - "return" - but this would just leave a blank page in users browser
//return;
}
if (isIOS == 1) {
// still can't avoid the "invalid address" safari pops up
// but at least we can explain it to users
var msg = "'invalid address' = MyApp NOT DETECTED.\n\nREDIRECTING TO APP STORE" ;
} else {
var msg = "MyApp NOT DETECTED\n\nREDIRECTING TO APP STORE" ;
}
if (window.confirm(msg)) {
window.location = "<?=$storeUrl?>";
// scenario #2 - will leave a blank tab in browser
} else {
window.close() ;  // scenario #3
}
}, 50);
window.location = "<?=$mobileUrl?>";
// scenario #1 - this will leave a blank tab
</script>

我一直试图在 iOS15的 Safari 扩展中达到同样的效果。似乎以前的所有策略都失败了——“ Open in”对话框和“ Invalidaddress”对话框是完全相等的,都是非阻塞的,所以基于计时器的解决方案提供了不一致的结果,这取决于加载页面所需的时间。

我的工作方法是在一个模式弹出窗口中创建一个应用程序商店重定向消息,该弹出窗口模仿系统提示符的外观,将其隐藏在系统提示符后面,并在选项卡失去焦点时用事件侦听器将其解除。用户体验还有两个问题:

  1. 无法禁止显示“无效地址”提示。我们所能做的(如果我们不走通用链接的路径)就是事后用我们自己的提示解释它。
  2. 如果用户从“ Open in”提示符中选择“ Cancel”,那么仍然会出现我们的重定向提示符。

下面的代码受益于上面的答案和 这个 SO 代码创建模态弹出窗口。

// Change the following vars to suit your needs
var my_app_name = "My App";
var my_app_id = "id1438151717"
var my_app_scheme = "myapp://do.this"


function toggleModal(isModal, inputs, elems, msg) {
for (const input of inputs) input.disabled = isModal;
modal.style.display = isModal ? "block" : "none";
elems[0].textContent = isModal ? msg : "";
}


function myConfirm(msg) {
const inputs = [...document.querySelectorAll("input, textarea, select")].filter(input => !input.disabled);
const modal = document.getElementById("modal");
const elems = modal.children[0].children;


return new Promise((resolve) => {
toggleModal(true, inputs, elems, msg);
elems[3].onclick = () => resolve(true);
elems[4].onclick = () => resolve(false);
}).then(result => {
toggleModal(false, inputs, elems, msg);
return result;
});
}


function redirectMessage() {
var r = myConfirm("To download " + my_app_name + ", tap OK.");
return r.then(ok => {
if (ok) {
console.log("Redirecting to the App Store...");
window.location = "itms-apps://itunes.apple.com/app/" + my_app_id;
} else {
console.log("User cancelled redirect to the App Store");
}
return ok;
});
}


function prepareListener() {
document.addEventListener("visibilitychange", function() {
const inputs = [...document.querySelectorAll("input, textarea, select")].filter(input => !input.disabled);
const modal = document.getElementById("modal");
const elems = modal.children[0].children;


if (!document.hasFocus()) {
console.log("User left tab. Closing modal popup")
toggleModal(false, inputs, elems, "");
}
});
}


function onTap() {
setTimeout(function() {
// We can't avoid the "invalid address" Safari popup,
// but at least we can explain it to users.
// We will create a modal popup behind it, which the
// event listener will close automatically if the app
// opens and we leave the tab


redirectMessage()


}, 50);
window.location = my_app_scheme;
}


prepareListener()
#modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background: rgb(0, 0, 0);
background: rgba(0, 0, 0, 0.4);
font-family: "ms sans serif", arial, sans-serif;
font-size: medium;
border-radius: 15px;
}


#modal>div {
position: relative;
padding: 10px;
width: 320px;
height: 60px;
margin: 0 auto;
top: 50%;
margin-top: -45px;
background: white;
border: 2px outset;
border-radius: 15px;
}


#cancel_button {
position: fixed;
right: 50%;
margin-right: -95px;
bottom: 50%;
margin-bottom: -32px;
padding: 0;
border: none;
background: none;
color: rgb(0, 122, 255);
font-size: medium;
font-weight: normal;
}


#ok_button {
position: fixed;
right: 50%;
margin-right: -140px;
bottom: 50%;
margin-bottom: -32px;
padding: 0;
border: none;
background: none;
color: rgb(0, 122, 255);
font-size: medium;
font-weight: semi-bold;
}
<div id="modal">
<div>
<div></div><br><br>
<button id="ok_button">OK</button>
<button id="cancel_button">Cancel</button>
</div>
</div>


<p><a href="#" onclick="onTap();"> Tap here to open app </a></p>