navigator.geolocation.getCurrentPosition() 从不在 Android 上的 WebView 中 returns

navigator.geolocation.getCurrentPosition() never returns in WebView on Android

我正在尝试访问 Android WebView 中可用的 HTML 地理定位 API(使用 SDK 版本 24)。

主要问题是在Java脚本中对navigator.geolocation.getCurrentPosition()的调用从来没有returns(既没有错误,也没有位置数据),而在应用程序方面,我检查权限并使用 android.webkit.GeolocationPermissions.Callback class.

将它们正确传递给 WebView

更新: 在这里澄清一下,“从不 returns” 我的意思是 none 两个提供的回调 navigator.geolocation.getCurrentPosition(success, error) 是曾经打电话过。

在我为测试这个而构建的示例应用程序中(只有一个小 activity 托管 WebView)我在清单中声明权限并在应用程序启动时正确请求它们。我看到提示,可以授予或拒绝位置信息的权限。

清单:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

主窗体中的代码:

public boolean checkFineLocationPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            != PackageManager.PERMISSION_GRANTED) {

        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.ACCESS_FINE_LOCATION) && !mIsPermissionDialogShown) {
            showPermissionDialog(R.string.dialog_permission_location);
        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSION_ACCESS_FINE_LOCATION);
        }
        return false;
    } else {
        return true;
    }
} 

我可以在运行时使用 Context.checkSelfPermission() 检查权限,我看到相应的权限已授予我的应用程序。

然后我尝试在 WebView 控件中打开一个网页。 我在设置中启用所有必需的选项:

    mWebSettings.setJavaScriptEnabled(true);
    mWebSettings.setAppCacheEnabled(true);
    mWebSettings.setDatabaseEnabled(true);
    mWebSettings.setDomStorageEnabled(true);
    mWebSettings.setGeolocationEnabled(true);
    mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
    mWebSettings.setSupportZoom(true);

我使用以下 WebChromeClient 重载来处理来自 Java 脚本的地理定位请求:

protected class EmbeddedChromeClient extends android.webkit.WebChromeClient {
    @Override
    public void onGeolocationPermissionsShowPrompt(String origin,
                                                   android.webkit.GeolocationPermissions.Callback callback) {

        // do we need to request permissions ?
        if (ContextCompat.checkSelfPermission(EmbeddedBrowserActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // this should never happen, it means user revoked permissions
            // need to warn and quit?
            callback.invoke(origin, false, false);
        }
        else {
            callback.invoke(origin, true, true);
        }
    }
}

为了测试这一点,我使用以下代码(取自 Mozilla API help page,此处缩短):

function geoFindMe() {
  function success(position) {}
  function error() {}
  navigator.geolocation.getCurrentPosition(success, error);
}

我看到的是 Java脚本中对 navigator.geolocation.getCurrentPosition(success, error) 的调用从未 returns。我看到 Java 中的 onGeolocationPermissionsShowPrompt() 方法被正确调用,当我在那里检查权限时,我总是得到结果 0,即 PackageManager.PERMISSION_GRANTED,所以 callback.invoke(origin, true, true) 在每次调用时执行.如果我尝试多次,我会看到多次调用我的 Java 代码。尽管如此,在我调用 invoke().

后,Java 脚本端仍然没有任何反应

我添加了代码,使用 GeolocationPermissions class 中的 getOrigins(ValueCallback<Set<String>> callback) 调用来检查授予的权限,如 here in the documentation 所述。我在回调中看到允许我的来源请求位置(它们在集合中列出)。

知道这里可能有什么问题吗?

像这样更改您的权限请求,

ActivityCompat.requestPermissions(this, new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    },0);

这似乎有效。

对于面向 Android N 及更高版本 SDK(API 级别 > Build.VERSION_CODES.M)的应用程序,方法 onGeolocationPermissionsShowPrompt (String origin, GeolocationPermissions.Callback callback) 是仅调用来自安全来源(例如 HTTPS)的请求。在非安全源上,地理定位请求会被自动拒绝。

如果您尝试在方法中放置断点或日志,您可以自行缩小问题范围。

您有两个选择:

  1. 以较低的水平为目标 API,这显然容易得多,但并不真正受到赞赏。
  2. 在您的网站中设置 SSL。

您的情况似乎有 2 个不同的问题:

  1. getCurrentPosition永不失败
  2. getCurrentPosition永远不会成功

第一点可能只是因为 method has infinite timeout

The default value is Infinity, meaning that getCurrentPosition() won't return until the position is available.

第二点可能有点棘手,有一个参数 maximumAge 表示

The PositionOptions.maximumAge property is a positive long value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return. If set to 0, it means that the device cannot use a cached position and must attempt to retrieve the real current position. If set to Infinity the device must return a cached position regardless of its age.

0 默认情况下意味着设备不会使用缓存的位置并会尝试获取真实的位置,这可能是长时间响应的问题。

您也可以查看此报告,这可能意味着 API 在 Android 上效果不佳: https://github.com/ionic-team/ionic-native/issues/1958 https://issues.apache.org/jira/browse/CB-13241

*Cordova 为浏览器保留了地理位置信息。

实际上,navigator 是浏览器全局对象 window 的子对象。您应该首先访问 window,然后调用 navigator。在一些现代浏览器中,除了window之外,一些子对象被呈现为全局对象,例如:locationnavigator

WebView 没有全局对象 window。所以您可以手动添加它,此操作请阅读 this medium article

也许你的代码会像下面这样:

my_web_view.evaluateJavascript("javascript: " + "updateFromAndroid(\"" + edit_text_to_web.text + "\")", null);

然后添加 JavaScript 接口,如 window 对象,到这个计算器:

my_web_view.addJavascriptInterface(JavaScriptInterface(), JAVASCRIPT_OBJ); //JAVASCRIPT_OBJ: like window, like navigator, like location and etc.

关于这个主题有广泛的post:

navigator.geolocation.getCurrentPosition sometimes works sometimes doesn't

显然解决方案是检查 navigator.geolocation 然后拨打电话:

if(navigator.geolocation) { // dummy call
    navigator.geolocation.getCurrentPosition(success, error)
}

尝试使用选项设置超时 (source):

var options = {
    enableHighAccuracy: true,
    timeout: 10000,
    maximumAge: 0
};

navigator.geolocation.getCurrentPosition(success, error, options);

如果失败则尝试覆盖 getCurrentPosition (source):

(function() {

if (navigator.geolocation) {
    function PositionError(code, message) {
        this.code = code;
        this.message = message;
    }

    PositionError.PERMISSION_DENIED = 1;
    PositionError.POSITION_UNAVAILABLE = 2;
    PositionError.TIMEOUT = 3;
    PositionError.prototype = new Error();

    navigator.geolocation._getCurrentPosition = navigator.geolocation.getCurrentPosition;

    navigator.geolocation.getCurrentPosition = function(success, failure, options) {
        var successHandler = function(position) {
            if ((position.coords.latitude == 0 && position.coords.longitude == 0) ||
                (position.coords.latitude == 37.38600158691406 && position.coords.longitude == -122.08200073242188)) 
                return failureHandler(new PositionError(PositionError.POSITION_UNAVAILABLE, 'Position unavailable')); 

            failureHandler = function() {};
            success(position);
        }

        var failureHandler = function(error) {
            failureHandler = function() {};
            failure(error);
        }

        navigator.geolocation._getCurrentPosition(successHandler, failureHandler, options);

        window.setTimeout(function() { failureHandler(new PositionError(PositionError.TIMEOUT, 'Timed out')) }, 10000);
    }
}
})();

作为第三个选项,在 EmbeddedChromeClient

中使用 @JavascriptInterface (source) 注释

同时在代码中的适当位置添加:

mWebSettings.setJavaScriptEnabled(true);
//...
mWebSettings.setSupportZoom(true);
webView.addJavascriptInterface(new EmbeddedChromeClient(webView), "injectedObject");
webView.loadData("html here", "text/html", null);

最后一个选项就是在html中使用标签,从磁盘存储加载html,在调用函数中替换标签,在webView中加载html/string。我之前在 Android 中使用过这种方法,当时定位让我非常沮丧。那你也不用担心https了。

如果你们中的一些人仍然遇到这个问题,我可以通过为 选项 设置一些值来让 navigator.geolocation.getCurrentPosition() 为我工作参数,如下:

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(  
      async (p) => {
        await this.workMyCurrentPosition(p);
      },
      (e) => this.navigatorError(e),
      { timeout: 7000, enableHighAccuracy: true, maximumAge: 0 }
    );
}

希望对你有用!

我在 Android 8 遇到了这个问题。错误回调是用 error.code==1 "PERMISSION_DENIED" 调用的。

https://developers.google.com/web/updates/2016/04/geolocation-on-secure-contexts-only 地理定位 API 从 Chrome 50

的不安全来源中移除

意思是,您的应用程序的 URL 必须以 https:// 开头才能调用。