当 Android 自定义 URL 方案未处理时如何回退到市场?

发布于 2024-12-02 07:42:40 字数 1274 浏览 1 评论 0原文

我们有一个处理自定义 URL 方案 (vstream://) 的应用程序。当有人访问包含 vstream:// 内容的网页时,如果他们没有安装我们的应用程序,我们需要将他们重定向到商店。

在 iOS 中,我们这样做:

setTimeout(function() {
  window.location =
    "itms://itunes.apple.com/us/app/kaon-v-stream/id378890806?mt=8&uo=4";
}, 25);

window.location = "vstream:view?code=...stuff...";

如果 window.location 分配失败,则在对话框出现之前超时会跳过 App Store。 (我在这里找到了这项技术:是否可以为 iPhone 应用程序(例如 YouTube 和地图)注册基于 http+ 域的 URL 方案? .)

不幸的是,这个技巧在 Android 中不起作用。我们检测设备服务器端并编写此代码而不是 itms: 行:

"market://details?id=com.kaon.android.vstream";

问题是,当您转到未处理的 url 方案时,iOS 会抛出错误,而 Android 会转到生成的页面。因此,超时永远没有机会运行。

网页上是否有某种方法可以显式测试是否处理自定义 URL 方案,或者有人可以建议像这样的黑客可以在 Android 中使用吗? (当然,我想我需要一个无论他们使用什么浏览器都可以工作的黑客,这可能是一个艰巨的任务......)

更新: 下面的方法在 Nexus 7 上的 Jelly Bean 中不起作用。新的 Chrome 浏览器不会转到生成的页面(因此不需要 iFrame),但似乎没有任何方法可以知道 URL 方案是否是处理。如果是,超时无论如何都会触发。如果没有处理,就会引发超时。如果我使用 onload 处理程序和 iframe,onload 处理程序永远不会触发(无论应用程序是否安装)。如果我弄清楚如何知道该方案是否已处理,我会更新...

我已经删除了之前解决方案中的“已解决”,因为它不再起作用。

更新2: 我现在有一个很好的跨平台解决方案,可以在 iOS、带有 Chrome 的 Android 4.1 和 Chrome 之前的 Android 上运行。请参阅下文...

更新 3: 谷歌再次用意图打破了一切。查看我在 amit_saxena 接受的非常好的解决方案/

We have an app that handles a custom URL scheme (vstream://). When someone comes to a web page that has some vstream:// content, we need to redirect them to the store if they don't have our app installed.

In iOS, we do this:

setTimeout(function() {
  window.location =
    "itms://itunes.apple.com/us/app/kaon-v-stream/id378890806?mt=8&uo=4";
}, 25);

window.location = "vstream:view?code=...stuff...";

If the window.location assignment fails, the timeout jumps over the App Store before the dialog box comes up. (I found this technique here: Is it possible to register a http+domain-based URL Scheme for iPhone apps, like YouTube and Maps? .)

Unfortunately, this trick is not working in Android. We detect the device server side and wrote this instead of the itms: line:

"market://details?id=com.kaon.android.vstream";

Trouble is, whereas iOS throws an error when you go to an unhandled url scheme, Android goes to a generated page. Therefore, the timeout never gets a chance to run.

Is there some way on a web page to explicitly test for whether a custom URL scheme is handled, or can someone suggest a hack like this one that will work in Android? (Of course, I suppose I need a hack that's going to work no matter what browser they are using, which is probably a tall order...)

UPDATE:
The approaches below do not work in Jelly Bean on a Nexus 7. The new Chrome browser does not go to a generated page (so the iFrame is not needed), but there does not appear to be any way to know whether the URL scheme was handled. If it was, the timeout fires anyway. If it wasn't handled the timeout fires. If I use an onload handler and an iframe, the onload handler never fires (whether the app is installed or not). I'll update if I ever figure out how to know whether the scheme was handled...

I've removed my "Solved" on my previous solution, since it doesn't work any more.

UPDATE 2:
I have a good cross-platform solution now that works on iOS, Android 4.1 with Chrome, and Android pre-Chrome. See below...

Update 3:
Google broke everything again with intents. Check out the VERY nice solution I've accepted by amit_saxena down there someplace /

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

梦途 2024-12-09 07:42:40

更新:谷歌打破了这一点。请参阅新接受的答案。

事实证明,关键是 document.webkitHidden 属性。当您将 window.location 设置为自定义 URL 方案并打开它时,浏览器将继续运行,但该属性将变为 false。因此您可以对其进行测试以确定是否处理了自定义 URL 方案。

这是一个示例,您可以实时查看

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Starting App...</title>
<script>

var URL = "kaonkaon://product.html#malvern;6";
var MARKET = "market://details?id=com.kaon.android.lepton.kaon3d";
var ITUNES = "itms://itunes.apple.com/us/app/kaon-interactive-3d-product/id525051513?mt=8&uo=4";
var QR = "http://goo.gl/gz07g"; // this should be a shortened link back to this page

function onLoad() {

    if (navigator.userAgent.match(/Android/)) {

        if (navigator.userAgent.match(/Chrome/)) {

            // Jelly Bean with Chrome browser
            setTimeout(function() {
                if (!document.webkitHidden)
                    window.location = MARKET;
            }, 1000);

            window.location = URL;

        } else {

            // Older Android browser
            var iframe = document.createElement("iframe");
            iframe.style.border = "none";
            iframe.style.width = "1px";
            iframe.style.height = "1px";
            var t = setTimeout(function() {
                window.location = MARKET;
            }, 1000);
            iframe.onload = function () { clearTimeout(t) };
            iframe.src = URL;
            document.body.appendChild(iframe);

        }

     } else if (navigator.userAgent.match(/iPhone|iPad|iPod/)) {

         // IOS
         setTimeout(function() {
             if (!document.webkitHidden)
                 window.location = ITUNES;
         }, 25);

         window.location = URL;

     } else {

         // Not mobile
         var img = document.createElement("img");
         img.src = "https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl="+encodeURIComponent(QR);
         document.body.appendChild(img);
     }
}
</script>
  </head>
  <body onload="onLoad()">
  </body>
</html>

UPDATE: Google broke this. See the new accepted answer instead.

The key, it turns out, is the document.webkitHidden property. When you set window.location to a custom URL scheme and it opens, the browser keeps running, but that property goes to false. So you can test it to determine whether the custom URL scheme was handled.

Here's a sample, which you can view live

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Starting App...</title>
<script>

var URL = "kaonkaon://product.html#malvern;6";
var MARKET = "market://details?id=com.kaon.android.lepton.kaon3d";
var ITUNES = "itms://itunes.apple.com/us/app/kaon-interactive-3d-product/id525051513?mt=8&uo=4";
var QR = "http://goo.gl/gz07g"; // this should be a shortened link back to this page

function onLoad() {

    if (navigator.userAgent.match(/Android/)) {

        if (navigator.userAgent.match(/Chrome/)) {

            // Jelly Bean with Chrome browser
            setTimeout(function() {
                if (!document.webkitHidden)
                    window.location = MARKET;
            }, 1000);

            window.location = URL;

        } else {

            // Older Android browser
            var iframe = document.createElement("iframe");
            iframe.style.border = "none";
            iframe.style.width = "1px";
            iframe.style.height = "1px";
            var t = setTimeout(function() {
                window.location = MARKET;
            }, 1000);
            iframe.onload = function () { clearTimeout(t) };
            iframe.src = URL;
            document.body.appendChild(iframe);

        }

     } else if (navigator.userAgent.match(/iPhone|iPad|iPod/)) {

         // IOS
         setTimeout(function() {
             if (!document.webkitHidden)
                 window.location = ITUNES;
         }, 25);

         window.location = URL;

     } else {

         // Not mobile
         var img = document.createElement("img");
         img.src = "https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl="+encodeURIComponent(QR);
         document.body.appendChild(img);
     }
}
</script>
  </head>
  <body onload="onLoad()">
  </body>
</html>
猥琐帝 2024-12-09 07:42:40

下面是大多数 Android 浏览器的工作代码片段:

<script type="text/javascript">
    var custom = "myapp://custom_url";
    var alt = "http://mywebsite.com/alternate/content";
    var g_intent = "intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end";
    var timer;
    var heartbeat;
    var iframe_timer;

    function clearTimers() {
        clearTimeout(timer);
        clearTimeout(heartbeat);
        clearTimeout(iframe_timer);
    }

    function intervalHeartbeat() {
        if (document.webkitHidden || document.hidden) {
            clearTimers();
        }
    }

    function tryIframeApproach() {
        var iframe = document.createElement("iframe");
        iframe.style.border = "none";
        iframe.style.width = "1px";
        iframe.style.height = "1px";
        iframe.onload = function () {
            document.location = alt;
        };
        iframe.src = custom;
        document.body.appendChild(iframe);
    }

    function tryWebkitApproach() {
        document.location = custom;
        timer = setTimeout(function () {
            document.location = alt;
        }, 2500);
    }

    function useIntent() {
        document.location = g_intent;
    }

    function launch_app_or_alt_url(el) {
        heartbeat = setInterval(intervalHeartbeat, 200);
        if (navigator.userAgent.match(/Chrome/)) {
            useIntent();
        } else if (navigator.userAgent.match(/Firefox/)) {
            tryWebkitApproach();
            iframe_timer = setTimeout(function () {
                tryIframeApproach();
            }, 1500);
        } else {
            tryIframeApproach();
        }
    }

    $(".source_url").click(function (event) {
        launch_app_or_alt_url($(this));
        event.preventDefault();
    });
</script>

您需要将 source_url 类添加到锚标记。

我在这里写了更多关于它的博客:

http://aawaara。 com/post/88310470252/smallest-piece-of-code-thats-going-to-change-the

Below is a working code snippet for most of the android browsers:

<script type="text/javascript">
    var custom = "myapp://custom_url";
    var alt = "http://mywebsite.com/alternate/content";
    var g_intent = "intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end";
    var timer;
    var heartbeat;
    var iframe_timer;

    function clearTimers() {
        clearTimeout(timer);
        clearTimeout(heartbeat);
        clearTimeout(iframe_timer);
    }

    function intervalHeartbeat() {
        if (document.webkitHidden || document.hidden) {
            clearTimers();
        }
    }

    function tryIframeApproach() {
        var iframe = document.createElement("iframe");
        iframe.style.border = "none";
        iframe.style.width = "1px";
        iframe.style.height = "1px";
        iframe.onload = function () {
            document.location = alt;
        };
        iframe.src = custom;
        document.body.appendChild(iframe);
    }

    function tryWebkitApproach() {
        document.location = custom;
        timer = setTimeout(function () {
            document.location = alt;
        }, 2500);
    }

    function useIntent() {
        document.location = g_intent;
    }

    function launch_app_or_alt_url(el) {
        heartbeat = setInterval(intervalHeartbeat, 200);
        if (navigator.userAgent.match(/Chrome/)) {
            useIntent();
        } else if (navigator.userAgent.match(/Firefox/)) {
            tryWebkitApproach();
            iframe_timer = setTimeout(function () {
                tryIframeApproach();
            }, 1500);
        } else {
            tryIframeApproach();
        }
    }

    $(".source_url").click(function (event) {
        launch_app_or_alt_url($(this));
        event.preventDefault();
    });
</script>

You need to add source_url class to the anchor tag.

I have blogged more about it here:

http://aawaara.com/post/88310470252/smallest-piece-of-code-thats-going-to-change-the

撑一把青伞 2024-12-09 07:42:40

这就是拯救你们所有人的答案!

https://developers.google.com/chrome/mobile/docs/intents

<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end"> Take a QR code </a>

如果应用程序已安装,它将通过您的 url schestart 我启动,否则它将在指定的包中启动市场

This is the answer who will save you all !

https://developers.google.com/chrome/mobile/docs/intents

<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;end"> Take a QR code </a>

It will your url schestart me if the app is installed ortherwise it will start the market at the indicated package

心凉怎暖 2024-12-09 07:42:40

@jesmith,这是一个干净的版本,修复了 Android 上的双重操作。

if (navigator.appVersion.indexOf('iPhone') > -1) {
  setTimeout(function noapp() { window.location="http://itunes.apple.com/app/id378890806?mt=8"; }, 25);
  window.location = 'vstream:';
}
else if (navigator.userAgent.indexOf('Android') > -1) {
  var iframe = document.createElement('iframe');
  iframe.style.visibility = 'hidden';
  iframe.src = 'vstream:';
  iframe.onload = function noapp() { window.location="market://details?id=com.kaon.android.vstream"; };
  document.body.appendChild(iframe);
}

@jesmith, this is a clean version that fixes double action on Android.

if (navigator.appVersion.indexOf('iPhone') > -1) {
  setTimeout(function noapp() { window.location="http://itunes.apple.com/app/id378890806?mt=8"; }, 25);
  window.location = 'vstream:';
}
else if (navigator.userAgent.indexOf('Android') > -1) {
  var iframe = document.createElement('iframe');
  iframe.style.visibility = 'hidden';
  iframe.src = 'vstream:';
  iframe.onload = function noapp() { window.location="market://details?id=com.kaon.android.vstream"; };
  document.body.appendChild(iframe);
}
活雷疯 2024-12-09 07:42:40

解决了!诀窍是在 IFRAME 中打开我的应用程序,而不是设置位置:

setTimeout(function() {
  window.location =
    "market://details?id=com.kaon.android.vstream";
}, 1000);

document.write('<iframe style="border:none; width:1px; height:1px;" src="vstream:view?code='+code+'"></iframe>');

请注意,我将超时增加到 1000,因为 Android 实际上在每种情况下都会执行这两种操作(不理想,但也不糟糕),并且需要更大的超时确保市场最终不会成为我安装后用户看到的东西。

(是的,当然使用 document.write 已经是上个世纪的事了,但我是老派了:)

Solved! The trick is to open my app in an IFRAME, instead of setting the location:

setTimeout(function() {
  window.location =
    "market://details?id=com.kaon.android.vstream";
}, 1000);

document.write('<iframe style="border:none; width:1px; height:1px;" src="vstream:view?code='+code+'"></iframe>');

Notice that I increased the timeout to 1000, because Android actually does both actions in every case (not ideal, but not awful), and this larger timeout is needed to make sure that Market doesn't end up being the thing the user sees when I'm already installed.

(And yes, of course using document.write is so last-century, but I'm old school that way :)

你列表最软的妹 2024-12-09 07:42:40

由于某些原因,最终的解决方案在 Android 上不适用于我(只有我吗?!!)。关键是 iframe.onload 函数在您的应用程序安装时不会执行,而在您的应用程序未安装时执行。

解决方案实际上变得更简单一些。以下是“旧版 Android 浏览器”部分的片段:

    } else {

        // Older Android browser
        var iframe = document.createElement("iframe");
        iframe.style.border = "none";
        iframe.style.width = "1px";
        iframe.style.height = "1px";
        iframe.onload = function () { window.location = MARKET; };
        iframe.src = URL;
        document.body.appendChild(iframe);

    }

For some reasons, the final solution does not work for me on android (Is it just me?!!). The key is that iframe.onload function is NOT executed when your app is installed, and it IS executed when your app is NOT installed.

The solution becomes a little simpler actually. Here is the segment for "Older Android Browser" part:

    } else {

        // Older Android browser
        var iframe = document.createElement("iframe");
        iframe.style.border = "none";
        iframe.style.width = "1px";
        iframe.style.height = "1px";
        iframe.onload = function () { window.location = MARKET; };
        iframe.src = URL;
        document.body.appendChild(iframe);

    }
少女的英雄梦 2024-12-09 07:42:40

在您的应用程序中嵌入一个 http 服务器作为服务,监听本地端口(高于 1024,例如:8080),从浏览器发送请求到 127.0.0.1:8080。如果您的应用程序已安装(并且服务正在运行),请启动它,如果请求失败,请转到 google play。

Embed a http server in your app as a Service, listen to a local port(higher than 1024, ex: 8080), send a request from Browser to 127.0.0.1:8080. If your app installed(and service running), start it, if request fail, goto google play.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文