移動端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);
},
複製代碼
相關文章
相關標籤/搜索