移動端webview定位--爬坑經驗

LucasTwilight發表於2018-10-22

寫在前面

最近的一個業務需求,要求在進入頁面之後,獲取使用者的地理位置,然後根據地理位置展示相關的內容。

說起來似乎是一個非常簡單的需求,但是。。如果你的公司有lbs服務的話,並且不考慮適配,那麼它真的非常非常簡單。引入一個sdk或者訪問一個後臺介面,就可以拿到相應的地理位置了。當然,國內有完整lbs服務鏈路的公司也就是屈指可數。

另外就是適配,地理位置服務的適配並不是適配機型,而是適配平臺,不同的平臺提供的服務也是不一致的,下面來說說幾種定位方式及其優劣,並且整理一個通用的方案吧(或許根本不存在通用的方案)。

lbs

先說說lbs,不說那麼多沒用的了,基於位置的服務,聽名字大家應該都知道到底是個什麼東西。

大部分成熟的網際網路平臺都或多或少地使用了lbs,來根據使用者的當前地理位置來進行推薦或者搜尋。

說這個就是為了說明lbs很常用,也是必須要了解到的一個縮寫。

location

環境

首先說一下我們這個業務需要適配的平臺:

  1. 站內,也就是團隊的app內部;
  2. 微信,最大的站外分享平臺;
  3. 其他的移動端站外平臺,包括但不限於QQ、微博、各種瀏覽器;
  4. 甚至有些使用者可能會在電腦端微信開啟。

環境很惡劣是不是?如果你的團隊有著自己的app,並且希望自己的內容可以被人分享到站外瀏覽,那麼上面的四條,你基本都需要考慮了。

如果你做的是微信小程式,那麼恭喜你,下面基本上不用看了,因為微信給了小程式很好的開發環境,不需要考慮這麼多東西。

方法

移動端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定位一般是根據運營商的歸屬地或者基站來進行定位的。如果你住在北京四環,很有可能把你定位到石家莊。。

移動端webview定位--爬坑經驗

但是也沒辦法,在拿不到許可權的情況下,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);
},
複製程式碼

相關文章