移动端webview定位--爬坑经验

写在前面

最近的一个业务需求,要求在进入页面以后,获取用户的地理位置,而后根据地理位置展现相关的内容。javascript

提及来彷佛是一个很是简单的需求,可是。。若是你的公司有lbs服务的话,而且不考虑适配,那么它真的很是很是简单。引入一个sdk或者访问一个后台接口,就能够拿到相应的地理位置了。固然,国内有完整lbs服务链路的公司也就是屈指可数。php

另外就是适配,地理位置服务的适配并非适配机型,而是适配平台,不一样的平台提供的服务也是不一致的,下面来讲说几种定位方式及其优劣,而且整理一个通用的方案吧(或许根本不存在通用的方案)。前端

lbs

先说说lbs,不说那么多没用的了,基于位置的服务,听名字你们应该都知道究竟是个什么东西。java

大部分红熟的互联网平台都或多或少地使用了lbs,来根据用户的当前地理位置来进行推荐或者搜索。android

说这个就是为了说明lbs很经常使用,也是必需要了解到的一个缩写。git

location

环境

首先说一下咱们这个业务须要适配的平台:github

  1. 站内,也就是团队的app内部;
  2. 微信,最大的站外分享平台;
  3. 其余的移动端站外平台,包括但不限于QQ、微博、各类浏览器;
  4. 甚至有些用户可能会在电脑端微信打开。

环境很恶劣是否是?若是你的团队有着本身的app,而且但愿本身的内容能够被人分享到站外浏览,那么上面的四条,你基本都须要考虑了。web

若是你作的是微信小程序,那么恭喜你,下面基本上不用看了,由于微信给了小程序很好的开发环境,不须要考虑这么多东西。数据库

方法

移动端GPS定位

移动端GPS定位这个方法仅限于大家有着本身的app,做为一个前端开发通常是不须要了解native是如何给你各类JSBridge的。可是稍微了解一点也蛮好的。小程序

提及nativeweb通讯,不得不说的就是JSBridge,native经过JSBridge提供各类必须可是web拿不到的方法,好比原生的定位,麦克风,摄像头等设备的使用。

举个栗子

function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
setupWebViewJavascriptBridge(function(bridge) {
    /* Initialize your app here */
    var button = document.querySelector(".JSToNativeButton");
    button.addEventListener('click', function(event) {
        bridge.callHandler('JsToNative', "This is a message from javascript to native");
    })
    bridge.registerHandler('NativeToJs', function(data, responseCallback) {
        alert(data);
    })
    // bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
    //     console.log("JS received response:", responseData)
    // })
})
复制代码

这是一个很简单的web和native的例子,上面是web端的代码。能够看出来,通讯的方法就是咱们最常使用的事件监听机制。上面的代码看起来有点绕,可是应该仍是能够看懂的,就很少解释了。由于彷佛有点跑题了。既然跑题了就索性说完吧Orz。下面的是相关的一点OC代码片断。

[self.bridge registerHandler:@"JsToNative" handler:^(id data, WVJBResponseCallback responseCallback) {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"message from JS" message:data preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *actionCancel = [UIAlertAction actionWithTitle:@"close" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        
    }];
    [alertController addAction:actionCancel];
    [self presentViewController:alertController animated:YES completion:nil];
}];
复制代码

简而言之,JSBridge能够实现两端的通讯,native能够注册一个window上面的对象,这个对象提供给web注册事件的方法(上面代码中的bridge.registerHandler),这个注册事件在native能够触发,而且传入参数,让native可以通知到web端。又在native上能够注册事件,而后能够在web中调用这个事件函数,传入参数,让web可以调用native的功能。

跑题跑的够远的,可是之因此跑题,是由于这个方案确实没啥可说的。native给你约定一个协议,而后你去调用那个协议,传个函数进去等着回调就行了。。基本上对于咱们前端没有很大的难度和工做量。

什么? 大家的native没有提供这个协议? 下个迭代给他们排的满满的。

提需求的来啦

这个方法好嘛?

固然好,很是好。定位准,提示友好,除了会弹出权限(固然这也是必须的),基本上是app内的最优解。固然若是大家有着大量的用户定位数据,而且可以保证这些数据实时有效的话,那当我没说。即便有接口,静默定位会不会引发用户反感,还须要你的交互和策划来肯定。

这个小demo在个人github上面有工程,哇,才发现写了半年论文,github都很久没更新了。。

H5定位

新的标准给了咱们不少统一口径的机会。不得不说,H5定位的兼容性仍是不错的,目前咱们团队的移动端兼容性支持到了android 4.4.2以上,这个版本以上的手机,基本上都支持了H5定位的功能。就在我准备将其当作第一解决方案的时候,我却发现了一个能让交互疯掉的问题。

首先,H5定位会要求第三方平台,也就是app的地理位置权限,其次,H5定位会弹出一个webview的地理位置权限弹窗。

这两个权限只要有一个被取消,那么H5定位就只可以根据IP来进行定位了。

两个弹框还不是重点,最重要的是H5定位,webview的弹框上面会显示:www.xxx.com想要获取您的地理位置。,这样会让用户很是迷惑,用户可能不知道你的域名,只知道你的软件叫作什么。

除了这两个问题,H5定位不管是兼容性,仍是对于前端定位的统一性来讲,都是一个很是之好的选择。

const noop = () => {};
getH5Location(options = {}) {
    const cb = options.cb || noop;
    const errCb = options.errCb || noop;
    let isLocation = false;
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
            res => {
                isLocation = true;
                cb(res.coords.longitude, res.coords.latitude);
            },
            () => {
                isLocation = true;
                errCb();
            }
        );
    }
    // 定位兜底
    setTimeout(() => {
        if (!isLocation) {
            errCb();
        }
    }, 5000);
},
复制代码

这是一个简单的H5定位的封装,为了防止某些设备不支持H5定位,加了个兜底的方法。兜底触发的延迟能够根据需求本身设置。


这里要注意H5定位的一个小坑,让我爬了好久。众所周知,H5定位在比较新的浏览器中,都会要求https协议才可以进行定位,当你将协议切换成了https以后,你会发现全部的http资源都被H5定位阻塞了。可是若是你注释掉H5定位的代码,就会发现http资源仅仅会显示一个warning,可是能够正常显示。

若是你的静态资源有https版本的话,仍是推荐你使用//via.placeholder.com/333x333这种无协议方式引入,这样能够自动切换资源协议,防止资源被阻塞。


微信API

在微信平台,由于H5的定位的种种小问题,虽然不影响使用,可是效果老是差那么一丢丢。若是你的团队可以申请到微信的SDK,那么就是极好的了。

简单的调用,前提是你引入了微信的js-sdk,这不是一个很困难的事情。微信给的文档已经很是详细,这里就不赘述了。

wx.getLocation({
    type: 'wgs84', // 默认为wgs84的gps坐标,若是要返回直接给openLocation用的火星坐标,可传入'gcj02'
    success: function (res) {
        var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
        var longitude = res.longitude ; // 经度,浮点数,范围为180 ~ -180。
        var speed = res.speed; // 速度,以米/每秒计
        var accuracy = res.accuracy; // 位置精度
    }
});
复制代码

在微信平台,使用微信的接口无疑是最好的选择。它完善,简单,通过了无数人的使用,很NICE,并且微信平台是你必须适配的平台,由于微信平台是你的大部分落地页的第一着陆点。

使用方法微信公众平台文档中已经写得很是详细了。

爸爸

第三方SDK(百度地图,高德地图等)

这是最不靠谱的一种方法,我花了一成天时间,把百度地图,高德地图,腾讯地图就引入了,而且进行了尝试。很有一种病急乱投医的风格。

后来从根本上思考了这个问题。他们是根据什么定位的。在没有GPS权限的状况下,他们难道不是经过IP来进行定位的吗??

果真,尝试了屡次以后才发现,这些第三方SDK中,有些会经过H5来定位,由于弹出了api.map.xxx.com想要获取您的位置,这样我何不使用H5定位呢。后台爸爸们已经提供了一个IP定位的接口,若是仍是经过第三方SDK进行IP定位,那不是浪费了后台爸爸们的辛勤劳动呢。

第三方地图很好,可是他们的定位服务并不必定好,若是你仅仅须要定位,那么仍是不要考虑这个方法了,由于第三方地图SDK的主要功能是获得可视化的地图。

我竟然花了半天时间测试完以后才想明白这个问题。。

若是你想要可视化的地图服务,请选择第三方地图SDK,若是仅仅是为了定位,那么其余方法都是好于这个方法的。

IP定位

依赖后端爸爸给的接口,能够直接经过接口拿到保存在后端数据库内的定位信息。或者经过用户的IP,来进行地理位置的获取。

看起来很美好。若是你比较追求定位的准确度的话,仍是放弃这个方法吧。IP定位在4G网络中的效果很是之差。

4G网络环境下,IP定位通常是根据运营商的归属地或者基站来进行定位的。若是你住在北京四环,颇有可能把你定位到石家庄。。

可是也没办法,在拿不到权限的状况下,IP定位能够做为兜底的方案,仍是比较现实的。

终极方案

哈哈,其实所谓的终极方案就是把上面的多个方案进行适配。

  • 首先,app内部让客户端开发们给你搭一个JSBridge,来让你好好地调用一下native的定位方法。

  • 其次,做为第一分享平台的微信,固然要特殊照顾了,微信经过微信的js-sdk来进行定位。

  • 再次,对于其余全部移动端,客户端平台,能够一律而论了。所有采用H5定位,暴力又好用。

  • 最后,任意一个定位失效了以后,乖乖IP定位吧。

固然,上面所说的这么复杂的适配方案是让你获得更好的用户体验而设计的。若是你以为麻烦或者某些条件不容许(客户端排期满了?不存在的)。能够根据上述的优缺点,进行替换,适合本身的方案才是最好的方案。

最后,别忘了封装一下你的定位函数,让后边的人可以更方便的复用。你必定也不但愿下次再须要定位的时候,再回来看这篇干干的文章吧~

export const getLocation = (options = {
    cb: () => {},
    errCb: () => {},
}) => {
    const isInApp = Utils.getEnv().isInApp();
    const isInWechat = Utils.isInWechat();
    if (isInApp) {
        // 站内定位
        return this.getAppLocation(options);
    }
    if (isInWechat) {
        // 微信定位
        return this.getWechatLocation(options);
    }
    // H5定位
    return this.getH5Location(options);
},
复制代码
相关文章
相关标签/搜索