天天看點

Android WebView關于定位部分研究

啥是H5定位

HTML5标準中提供Geolocation API,js中調用該API,用于獲得使用者的地理位置。

鑒于該特性可能侵犯使用者的隐私,除非使用者同意,否則使用者位置資訊是不可用的。 該API被設計成即支援一次性請求,也支援反複的位置更新,以及顯示的查詢緩存的位置的能力。位置資訊通過經緯度來呈現。需要注意的是:根據世界大地測量系統坐标[WGS84]提供地理位置資訊(GPS使用的是WGS84坐标)。

The geographic position information is provided in terms of World Geodetic System coordinates [WGS84].

如何使用H5定位

Navigator接口擴充

partial interface Navigator {  
    readonly attribute Geolocation geolocation;  
};
           

Navigator的geolocation屬性提供了與主裝置相關聯的位置資訊的通路。

The geolocation attribute gives access to location information associated with the hosting device.

Geolocation接口

[NoInterfaceObject]  
interface Geolocation {
  void getCurrentPosition(PositionCallback successCallback,optional PositionErrorCallback errorCallback,optional PositionOptions options);  
  
  long watchPosition(PositionCallback successCallback,optional PositionErrorCallback errorCallback,optional PositionOptions options);  
  
  void clearWatch(long watchId);  
};
           

PositionOptions接口

dictionary PositionOptions {
  boolean enableHighAccuracy = false;
  [Clamp] unsigned long timeout = 0xFFFFFFFF;
  [Clamp] unsigned long maximumAge = 0;
};
           

Position接口

[NoInterfaceObject]
interface Position {
  readonly attribute Coordinates coords;
  readonly attribute DOMTimeStamp timestamp;
};
           

Coordinates接口

[NoInterfaceObject]
interface Coordinates {
  readonly attribute double latitude;
  readonly attribute double longitude;
  readonly attribute double? altitude;
  readonly attribute double accuracy;
  readonly attribute double? altitudeAccuracy;
  readonly attribute double? heading;
  readonly attribute double? speed;
};
           
he geographic coordinate reference system used by the attributes in this interface is the World Geodetic System (2d) [WGS84]. No other reference system is supported.

PositionError接口

[NoInterfaceObject]
interface PositionError {
  const unsigned short PERMISSION_DENIED = 1;
  const unsigned short POSITION_UNAVAILABLE = 2;
  const unsigned short TIMEOUT = 3;
  readonly attribute unsigned short code;
  readonly attribute DOMString message;
};
           

The code attribute MUST return the appropriate code from the following list:

  • PERMISSION_DENIED (numeric value 1)

    The location acquisition process failed because the document does not have permission to use the Geolocation API.

  • POSITION_UNAVAILABLE (numeric value 2)

    The position of the device could not be determined. For instance, one or more of the location providers used in the location acquisition process reported an internal error that caused the process to fail entirely.

  • TIMEOUT (numeric value 3)

    The length of time specified by the timeout property has elapsed before the implementation could successfully acquire a new Position object.

下面一些示例展示如何擷取基礎位置資訊

EXAMPLE 1: Example of a “one-shot” position request

function showMap(position) {
  // Show a map centered at (position.coords.latitude, position.coords.longitude).
}

// One-shot position request.
navigator.geolocation.getCurrentPosition(showMap);
           

EXAMPLE 2: Example of requesting repeated position updates

function scrollMap(position) {
  // Scrolls the map so that it is centered at
  //  (position.coords.latitude, position.coords.longitude).
}

// Request repeated updates.
var watchId = navigator.geolocation.watchPosition(scrollMap);

function buttonClickHandler() {
  // Cancel the updates when the user clicks a button.
  navigator.geolocation.clearWatch(watchId);
}
           

EXAMPLE 3: Example of requesting repeated position updates and handling errors

function scrollMap(position) {
  // Scrolls the map so that it is centered at
  //  (position.coords.latitude, position.coords.longitude).
}

function handleError(error) {
  // Update a div element with error.message.
}

// Request repeated updates.
var watchId = navigator.geolocation.watchPosition(scrollMap, handleError);

function buttonClickHandler() {
  // Cancel the updates when the user clicks a button.
  navigator.geolocation.clearWatch(watchId);
}
           

EXAMPLE 4: Example of requesting a potentially cached position

// Request a position. We accept positions whose age is not
// greater than 10 minutes. If the user agent does not have a
// fresh enough cached position object, it will automatically
// acquire a new one.
navigator.geolocation.getCurrentPosition(successCallback,
                                         errorCallback,
                                         {maximumAge:600000});

function successCallback(position) {
  // By using the 'maximumAge' option above, the position
  // object is guaranteed to be at most 10 minutes old.
}

function errorCallback(error) {
  // Update a div element with error.message.
}
           

EXAMPLE 5: Forcing the user agent to return a fresh cached position

// Request a position. We only accept cached positions whose age is not
// greater than 10 minutes. If the user agent does not have a fresh
// enough cached position object, it will immediately invoke the error
// callback.
navigator.geolocation.getCurrentPosition(successCallback,
                                         errorCallback,
                                         {maximumAge:600000, timeout:0});

function successCallback(position) {
  // By using the 'maximumAge' option above, the position
  // object is guaranteed to be at most 10 minutes old.
  // By using a 'timeout' of 0 milliseconds, if there is
  // no suitable cached position available, the user agent
  // will asynchronously invoke the error callback with code
  // TIMEOUT and will not initiate a new position
  // acquisition process.
}

function errorCallback(error) {
  switch(error.code) {
    case error.TIMEOUT:
      // Quick fallback when no suitable cached position exists.
      doFallback();
      // Acquire a new position object.
      navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
      break;
    case ... // treat the other error cases.
  };
}

function doFallback() {
  // No fresh enough cached position available.
  // Fallback to a default position.
}
           

EXAMPLE 6: Forcing the user agent to return any available cached position

// Request a position. We only accept cached positions, no matter what
// their age is. If the user agent does not have a cached position at
// all, it will immediately invoke the error callback.
navigator.geolocation.getCurrentPosition(successCallback,
                                         errorCallback,
                                         {maximumAge:Infinity, timeout:0});

function successCallback(position) {
  // By setting the 'maximumAge' to Infinity, the position
  // object is guaranteed to be a cached one.
  // By using a 'timeout' of 0 milliseconds, if there is
  // no cached position available at all, the user agent
  // will immediately invoke the error callback with code
  // TIMEOUT and will not initiate a new position
  // acquisition process.
  if (position.timestamp < freshness_threshold &&
      position.coords.accuracy < accuracy_threshold) {
    // The position is relatively fresh and accurate.
  } else {
    // The position is quite old and/or inaccurate.
  }
}

function errorCallback(error) {
  switch(error.code) {
    case error.TIMEOUT:
      // Quick fallback when no cached position exists at all.
      doFallback();
      // Acquire a new position object.
      navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
      break;
    case ... // treat the other error cases.
  };
}

function doFallback() {
  // No cached position available at all.
  // Fallback to a default position.
}
           

如何給Android裝置的Webview添加定位功能

  1. 首先在AndroidManifest.xml中添加定位權限申明
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
               

如果應用的target小于Android.M,至此就完成了給WebView添加支援定位功能。因為預設是許可網站擷取位置的。如圖:。But,你看到這篇文章時,肯定target不下于Android.M了,是以,接着看。

2. 給WebView設定setWebChromeClient;

3. 重寫setWebChromeClient的 onGeolocationPermissionsShowPrompt() 方法;

4. 判斷App是否獲得了定位權限,如果沒有請求定位權限;

5. 判斷裝置是否開啟了定位服務,在某些情況下如果沒有開啟定位無法擷取到定位的,如下:

  • 擷取不到位置
    • 裝置很長時間沒有開啟定位了。
    • 裝置不久前開啟了定位,現在已經關閉定位了,但是js又要求必須擷取到實時的位置(不允許使用緩存位置)。
  • 擷取到了位置
    • 裝置目前定位服務處于開啟狀态。
    • 裝置不久前開啟了定位,現在處于關閉狀态,js中接受緩存位置。
  1. 調用 GeolocationPermissionsCallback 回調,設定是否許可js擷取目前裝置的權限

整體邏輯就是這樣。中間比較複雜的是第4步。這一步需要考慮到這一點:App申請定位權限時,系統會給使用者三種選擇,分别是:

  • 許可: 使用者選擇之後,我們就可以走第5步了。
  • 禁止: 走第6步,回調,拒絕js的位置請求。js下一次請求時,會回調 onGeolocationPermissionsShowPrompt() 方法,繼續走4、5、6。
  • 禁止并不再提示:

    本次的調用同上,但是,js下一次請求時,流程就有一點點不一樣。首先,系統直接不提示使用者授權了。what?這還玩個P啊。但是,系統會給我們的回調一個拒絕的結果。是以,在此時,我們可以請求讓使用者到系統的設定去給應用授權。使用者如果願意去設定,就讓他去設定,然後回來時,我們判斷一下,使用者給應用權限了,我們就到第6步,許可權限;如果沒有給權限,對不起,隻能到第6步,回調不許可權限了。如果使用者不願意去設定,那也是到第6步,回調不許可權限。

嗯,但願你沒有暈倒[苦笑]。我也想給你講的簡單點,但是,事實就是這麼複雜,我能怎麼辦,我也很無奈啊[笑哭]。

好了,你們一定想說,别扯淡了,趕緊放碼出來。諾,這是你要的代碼

會遇到哪些問題

  • H5請求定位,用戶端的onGeolocationPermissionsShowPrompt方法沒有回調

    三點需要注意

    1. Android系統版本是不是大于6.0?
    2. 是不是安全連結?

      從Android6.0開始,如果連結不是安全的,該方法不被調用。

    Note that for applications targeting Android N and later SDKs (API level > Build.VERSION_CODES.M) this method is only called for requests originating from secure origins such as https. On non-secure origins geolocation requests are automatically denied.
    1. js是否接受緩存位址?

      如果js接受緩存位址,盡管定位服務處于關閉狀态,如果不久前裝置剛剛定了位置,這個方法也不會被調用。

繼續閱讀