从浏览器或者Webview 中唤醒APP

本文来自网易云社区


作者:刘新奇

移动互联时代,很多互联网服务都会同时具备网站以及移动客户端,很多人认为APP的能帮助建立更稳固的用户关系,于是经常会接到各种从浏览器、webview中唤醒APP的需求,显然,这对于前端开发人员来说,是一件很纠结的事。

唤醒APP

目前常见的主动唤醒APP方式有几种:

Url scheme

Url scheme是iOS,Android平台都支持,只需要原生APP开发时注册scheme, 那么用户点击到此类链接时,会自动跳到APP。比如

<!-- 打开考拉APP首页 --><a href="kaola://www.kaola.com">打开APP</a><!-- 呼叫号码 --><a href="tel://13788889999">打开拨号</a>

如果配置scheme的路径,并在app中识别,则可以直接打开APP特定页面,如下:

<!-- 打开考拉APP商品详情 --><a href="kaola://www.kaola.com/product/8342.html">打开APP商品详情</a>

上述的链接,需要考虑手机是否支持此Scheme: 支持:弹出相应程序; 不支持:错误处理情况因平台而异,部分app会直接跳错误页(比如Android Chrome/41,iOS中老版的Lofter); 也有的停留在原页面,但弹出提示“无法打开网页”(比如iOS7);iOS8以及最新的Android Chrome/43 目前都是直接停留在当前页,不会跳出错误提示。 总体来看, iOS的支持程度比Android好,iOS在实际使用中,除非明确禁止的(比如微信),很少碰到不支持的情况;Android平台则各个app厂商差异很大,比如Chrome从25及以后就不再支持通过js触发(非用户点击),设置iframe src地址等来触发scheme跳转

Android intent

这是Android平台独有的,使用方式如下

intent:
   HOST/URI-path // Optional host 
   #Intent; 
      package=[string]; 
      action=[string]; 
      category=[string]; 
      component=[string]; 
      scheme=[string]; 
   end;

这里的HOST/URI-path, 与普通http URL 的host/path书写方式相同, package是Android app的包名,其它参数如action、category、component不是很理解, 具体见文档 , 比如打开考拉 app的商品详情,代码如下

<!-- 打开考拉APP --><a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;end">打开APP</a>

如果手机能匹配到相应的APP,则会直接打开;如没有安装,则会跳到手机默认的应用商店,比如Google原生系统Nexus 5,将会直接跳到Google Play, 对于国内各厂商定制过的系统,则跳转到各自的默认应用商店,或者弹出商店供选择。 intent 比scheme相对完善的一点是,提供一个打开失败去向URL的选项,可以通过指定参数 S.browser_fallback_url 来指定去向URL。 比如如下的打开APP动作,如果打开失败,则跳转到app下载页,这对于国内的特殊网络环境,还是挺有用的。

<!-- 打开考拉APP --><a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;S.browser_fallback_url=http%3A%2F%2Fapp.kaola.com;end">打开APP</a>

HTTP URL订阅

Android Chrome平台独有,app中订阅自有内容相关的URL,在Chrome中浏览相关网页时,会自动弹出提示,让用户选择用浏览器还是APP打开,通用性有限。

iOS内置APP广告条

在页面Head中增加meta, 添加智能 App 广告条 (iOS 6+ Safari), 如下

<meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"

可以自动判断是否已安装应用, 可惜只能用于iOS+Safari, 在第三方应用中就不行了。 效果如下:

20180907111836c01a5144-104e-452d-a474-aa068cd974f7.png

Android Chrome内置app安装提示

这个是Mobile Chrome 43 beta新加入的特性,在用户浏览某一个网站多次后,如果Chrome发现该站点有原生APP,则会提示用户下载原生APP,此项特性开发者无法干预,完全是Google的推荐行为,在项目中用不上,具体见新闻报道

实际应用中存在的问题

移动平台提供这么多唤醒APP的方法,但是功能还不够完善,以下情况JS无法检测并做处理:

  1. APP如果唤醒失败,很多时候都会跳到错误页,影响用户体验,而我们的需求很可能是需要跳到下载或者其它页。

  2. APP成功唤醒,页面无法直接得知,系统没有提供此类回调。

实际需求、解决方案

  1. 要在打开APP失败时,不能使当前页面跳到错误页,且打开失败时,有失败函数回调。

  2. 如果成功打开APP,有成功函数回调。 针对第一点,可以将打开动作放到iframe中,就算跳转失败仍能停留在当前页面;那剩下的问题就是如何检测APP是否成功打开; 网上常见解决方案如下:

    //创建一个隐藏的iframevar ifr = document.createElement('iframe');
    ifr.src = 'com.baidu.tieba://';
    ifr.style.display = 'none';document.body.appendChild(ifr);//记录唤醒时间var openTime = +new Date();window.setTimeout(function(){ document.body.removeChild(ifr); //如果setTimeout 回调超过2500ms,则弹出下载
     if( (+new Date()) - openTime > 2500 ){     window.location = 'http://exam.com/xxxx.apk';
     }
    },2000)

    此脚本利用了程序切换到后台时,计时器回调会被推迟的原理,如果APP被唤醒,那么此网页必然进入后台,如果用户从APP再切换回来,时间一般也会超过2.5s;如果app没有唤醒,则setTimeout 基本上会准时回调,时间不会超过2s。但是实际上,这个仅仅在iOS平台有效,Android由于是多任务的,应用放到后台,setTimeout 基本上还是会准时触发,所以这个逻辑还不够完善。

那Android 浏览器有没有方法检测应用是否进入了后台呢? 页面可见性API(Page Visibility API), 可以通过检测 document.hidden 或 document.[webkit|moz|ms]Hidden 来检查页面是否可见,或者订阅页面的visibilitychange事件; 如果仅仅应用在新版Android及Chrome上,这个是很美好的,对于老版本(<4.4)及Android Webview, 则不可用。

暮然回首,那人却在灯火阑珊处,setInterval,对,就setInterval, 如果设置比较小的运行间隔(<30ms),在浏览器或者webview中,应用切换到后台,setInterval 会被很明显的延迟执行,比如设置一个运行间隔20ms,总计运行100次的定时器,如果页面一直处于前台,则100次跑完,总耗时与20ms x 100 = 2000ms 不会有太大差异, 但页面在后台运行时,此时间会明显超过2000ms。 可以利用这一点来实现是否成功打开APP检测及回调。 代码如下:

function openApp(openUrl, appUrl, action, callback) {    //检查app是否打开
    function checkOpen(cb){        var _clickTime = +(new Date());        function check(elsTime) {            if ( elsTime > 3000 || document.hidden || document.webkitHidden) {
                cb(1);
            } else {
                cb(0);
            }
        }        //启动间隔20ms运行的定时器,并检测累计消耗时间是否超过3000ms,超过则结束
        var _count = 0, intHandle;
        intHandle = setInterval(function(){
            _count++;        
            var elsTime = +(new Date()) - _clickTime;            if (_count>=100 || elsTime > 3000 ) {
                clearInterval(intHandle);
                check(elsTime);
            }
        }, 20);
    }    //在iframe 中打开APP
    var ifr = document.createElement('iframe');
    ifr.src = openUrl;
    ifr.style.display = 'none';    if (callback) {
        checkOpen(function(opened){
            callback && callback(opened);
        });
    }    document.body.appendChild(ifr);      
    setTimeout(function() {        document.body.removeChild(ifr);
    }, 2000);  
}

iframe方式打开APP的问题:

  • Android Chrome/25+ 无法打开APP,所以最好是APP配合监听http URL 来实现。

  • iOS、Android平台,近期发现在没有安装对应app时尝试唤醒,有少数APP会连当前页面(iframe的父页面)也变成错误页的情况。

其它问题: 微信无法打开或者下载,打开APP这个基本无解,下载则只能让应用进驻应用宝市场,然后检测到在微信中运行时,跳转到应用宝页面下载。


网易云大礼包:https://www.163yun.com/gift

本文来自网易云社区,经作者刘新奇授权发布


相关文章:
【推荐】 一个只有十行的精简MVVM框架(下篇)
【推荐】 人力资源管理中的大数据应用之道

<think>我们正在讨论的是在UniApp开发中,如何在手机端(App端)唤醒浏览器下载文件。 根据引用内容,我们知道在App端,UniApp运行在jsCore环境中,并非浏览器环境,因此没有浏览器中的一些对象(如File)。同时,引用[1]提到了通过webviewApp通信,引用[2]提到了文件上传的问题。 但是,用户的问题是如何在App唤醒浏览器下载文件。这里需要明确:在App中,我们通常不能直接唤醒外部浏览器来下载文件,因为App有自己的运行环境。不过,我们可以通过一些方法实现类似功能。 思路: 1. 在UniApp中,我们可以使用`uni.downloadFile`下载文件到App本地。 2. 下载完成后,我们可以使用`uni.openDocument`打开文件(这会在App内部打开文件,但并不是用浏览器下载)。 3. 如果希望用外部浏览器下载,我们可以尝试使用`uni.openURL`打开一个下载链接,这样系统会提示用户在浏览器中打开,然后下载。 但是,注意:在App端,直接使用`uni.openURL`打开一个下载链接,系统会询问用户选择用哪个应用打开,其中可以选择浏览器。一旦用户选择了浏览器,那么就会在浏览器中开始下载。 步骤: 1. 获取要下载文件的URL(必须是完整的http/https链接)。 2. 使用`uni.openURL`打开这个链接。 示例代码: ```javascript uni.openURL({ url: 'http://example.com/path/to/file.pdf' // 替换为实际文件URL }); ``` 这样,系统会弹出选择框,用户可以选择浏览器进行下载。 但是,这种方法依赖于系统行为,而且用户需要多一步操作(选择浏览器)。另外,有些文件类型(如图片、pdf等)可能会被直接打开而不是下载。 为了确保下载行为,我们可以在服务器端设置响应头,强制浏览器下载: ``` Content-Disposition: attachment; filename="filename.ext" ``` 另外,注意:在App端,如果用户选择了浏览器,那么下载过程将在浏览器中进行,与App无关。下载的文件将保存在浏览器的默认下载目录中。 如果用户希望直接在App内下载并保存文件,则应该使用`uni.downloadFile`和`uni.saveFile`,但这不属于唤醒浏览器下载。 因此,根据用户需求“唤醒浏览器下载文件”,使用`uni.openURL`是最直接的方法。 注意:在H5端,我们可以直接使用`window.open`或`<a>`标签的download属性,但在App端,我们需要使用上述方法。 此外,引用[1]中提到的webview通信,在这里并不适用,因为我们的目标不是与webview通信,而是启动外部浏览器(或让用户选择浏览器)。 总结步骤: 1. 准备一个文件下载链接(确保服务器设置了正确的下载响应头)。 2. 使用`uni.openURL`打开该链接。 3. 用户选择浏览器后,即可在浏览器中下载。 代码示例: ```javascript // 在某个事件处理函数中 downloadWithBrowser() { const fileUrl = 'http://example.com/path/to/file.zip'; // 替换为实际文件URL uni.openURL({ url: fileUrl, success: () => { console.log('成功调起'); }, fail: (err) => { console.log('调起失败', err); } }); } ``` 注意:在iOS平台上,使用`uni.openURL`打开http链接需要在manifest.json中配置白名单(即LSApplicationQueriesSchemes),而https则不需要。同时,从iOS9开始,苹果要求使用https,如果使用http,需要在manifest.json中配置允许http。 配置manifest.json(源码视图): 在"app-plus" -> "distribute" -> "ios"下添加: ```json "urlschemewhitelist": [ "http", "https" ] ``` 同时,为了支持http,还需要在"app-plus" -> "ssl"中设置: ```json "ssl": { "http": "allow" } ``` 这样,就可以在App中打开http/https链接了。 相关问题: 1. 在UniApp中,如何实现App内下载文件并保存到本地? 2. 如何设置服务器响应头以强制浏览器下载文件? 3. UniApp中打开外部链接有哪些注意事项? 4. 在UniApp中,如何判断当前平台(如iOS或Android)以执行不同的下载策略?</think>在 UniApp 开发中,实现 App唤醒浏览器下载文件的核心方法是使用 `uni.openURL()` API。以下是具体实现步骤和注意事项: ### 解决方案步骤 1. **生成下载链接** 确保服务器提供直接的文件下载链接(如 `https://example.com/file.pdf`),链接需包含文件扩展名。 2. **调用 `uni.openURL()`** 在按钮点击事件中执行: ```javascript uni.openURL({ url: 'https://example.com/file.pdf', // 替换为实际文件URL success: () => console.log('浏览器唤醒成功'), fail: (err) => console.error('唤醒失败', err) }) ``` 3. **用户操作流程** - App 会触发系统弹窗,提示用户选择浏览器(Chrome/Safari 等) - 用户选择浏览器后,文件将在浏览器中自动开始下载 - 文件保存到浏览器的默认下载目录(如 Android 的 `Downloads/`) ### 关键注意事项 1. **平台差异处理** ```javascript // Android 需额外处理文件访问权限 if (uni.getSystemInfoSync().platform === 'android') { await uni.requestPermissions(['android.permission.WRITE_EXTERNAL_STORAGE']) } ``` 2. **服务器配置要求** - 响应头需包含 `Content-Disposition: attachment; filename="file.pdf"` - 避免触发浏览器直接预览(如 PDF),强制下载行为 3. **iOS 限制** - 需在 `manifest.json` 配置 URL Scheme 白名单: ```json "app-plus": { "distribute": { "ios": { "urlschemewhitelist": ["http", "https"] } } } ``` 4. **文件类型限制** | 文件类型 | 支持情况 | |----------------|-------------------| | PDF/图片 | 可能直接预览 | | ZIP/APK/EXE | 通常直接下载 | | 音视频 | 需服务器强制下载 | ### 替代方案对比 | 方法 | 优点 | 缺点 | |--------------------|--------------------------|--------------------------| | `uni.openURL()` | 原生支持,简单可靠 | 依赖用户选择浏览器 | | Webview 嵌入下载页 | 可定制界面 | 需处理与原生通信[^1] | | 原生插件 | 完全控制下载流程 | 增加开发复杂度[^2] | ### 常见问题排查 1. **下载无反应** - 检查 URL 是否包含特殊字符(需 `encodeURIComponent()` 处理) - 验证服务器响应头是否允许跨域(`Access-Control-Allow-Origin: *`) 2. **iOS 无法唤醒浏览器** - 确认 `manifest.json` 已配置 URL Scheme - 使用 https 协议(iOS 限制 http 链接) 3. **文件保存位置** - Android:`/storage/emulated/0/Download/` - iOS:`文件App > 浏览 > 我的iPhone > [App名]` > 提示:对于非浏览器下载场景(如 App 内直接下载),可使用 [`uni.downloadFile()`](https://uniapp.dcloud.net.cn/api/request/network-file.html#downloadfile) + [`uni.saveFile()`](https://uniapp.dcloud.net.cn/api/file/file.html#savefile)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值