172

我想创建一个网页,如果 iPhone 没有安装应用程序,该页面会将 iPhone 重定向到App Store ,但如果 iPhone 安装了应用程序,我希望它打开应用程序。

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

myapp://

如果这个 URL 无效,我希望页面重定向到 App Store。这可能吗?

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

即使 JavaScript 存在丑陋的 hack,我也很想知道。

4

12 回答 12

232

据我所知,您无法通过浏览器检查是否安装了应用程序。

但是您可以尝试将手机重定向到应用程序,如果没有任何反应,将手机重定向到指定页面,如下所示:

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

如果第二行代码给出结果,则第一行永远不会执行。

类似的问题:

于 2012-11-02T14:17:36.460 回答
173

为了进一步接受答案,您有时需要添加额外的代码来处理启动应用程序后返回浏览器的人 - 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://";

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

于 2013-05-23T17:16:39.640 回答
28

iOS Safari 有一项功能,允许您向网页添加“智能”横幅,该横幅将链接到您的应用(如果已安装)或 App Store。

您可以通过向meta页面添加标签来做到这一点。如果您希望应用程序在加载时执行一些特殊操作,您甚至可以指定详细的应用程序 URL。

详细信息在 Apple 的使用智能应用横幅页面推广应用。

该机制的优点是简单易行,呈现标准化的旗帜。缺点是您无法控制外观或位置。此外,如果该页面是在 Safari 以外的浏览器中查看的,那么所有的赌注都将被取消。

于 2014-08-18T16:17:04.067 回答
23

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

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

  • 使用 appScheme:// 发送电子邮件将无法正常工作,因为这些链接将在 Gmail 中被过滤

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

  • 您现在可以在没有 appScheme:// 的情况下进行深度链接,这会更好,但它需要一个现代平台和额外的设置。安卓 iOS


值得注意的是,其他人已经对此进行了深入思考。如果您查看 Slack 如何实现他的“魔术链接”功能,您会注意到:

  • 它发送带有常规 HTTP 链接的电子邮件(可以使用 Gmail)
  • 该网页有一个链接到 appScheme:// 的大按钮(可以使用 Chrome)
于 2017-06-21T08:43:48.663 回答
11

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

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

与其使用任意时间差阈值来确定我们是否返回浏览器,不如检测“pagehide”和“pageshow”事件。

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

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

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

(注意:这仅适用于 Safari。对于其他浏览器(如 Chrome),您必须为 pagehide/show-equivalent 事件安装处理程序)。

更新:正如@Mikko 在评论中指出的那样,我们正在使用的 pageshow/pagehide 事件显然在 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>
于 2014-07-30T22:11:37.343 回答
9

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

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

于 2014-11-19T03:06:52.677 回答
2

我需要做这样的事情,我最终选择了以下解决方案。

我有一个特定的网站 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>
    
于 2013-09-17T19:50:08.287 回答
2

在编译了一些答案后,我想出了以下代码。令我惊讶的是,计时器不会PC(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小提琴

于 2016-11-30T12:32:28.463 回答
1

以下答案仍然有效,在 iOS 10 到 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>
于 2021-08-04T15:10:48.813 回答
0

我一直试图在 iOS15 的 Safari 扩展中实现相同的目标。似乎所有以前的策略都失败了——“打开方式”对话框和“无效地址”对话框完全相等,都是非阻塞的,因此基于计时器的解决方案提供不一致的结果,具体取决于加载页面所需的时间.

我的解决方法是在模态弹出窗口中创建一个应用商店重定向消息,该消息模仿系统提示的外观,将其隐藏在系统提示后面,并在选项卡失去焦点时使用事件侦听器将其关闭。UX 还存在两个问题:

  1. 没有办法抑制“无效地址”提示。我们所能做的(如果我们不走通用链接路径)就是在之后用我们自己的提示来解释它。
  2. 如果用户从“打开方式”提示中选择“取消”,他或她仍会看到我们的重定向提示。

下面的代码受益于上面的答案和这个用于创建模式弹出窗口的 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>

于 2021-10-07T21:42:49.147 回答
-2

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

这是一个推特示例:

// 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 时自动关闭。无论如何,这是一个很好的解决方案!

于 2013-05-30T11:06:48.103 回答
-6

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

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

您也可以使用 Ajax 调用。如果有 404 响应,则表示未安装该应用程序。

于 2015-11-29T02:00:31.547 回答