在 Android WebView 中,navigator.geolocation.getCurrentPosition() 永远不会返回结果

22

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

主要问题是,JavaScript中调用navigator.geolocation.getCurrentPosition()从不返回(无论是错误还是位置数据),而在应用程序端,我检查权限并正确地将其传递给WebView,使用android.webkit.GeolocationPermissions.Callback类。

更新:这里需要澄清的是,“从不返回”的意思是指两个提供的回调函数navigator.geolocation.getCurrentPosition(success, error)都没有被调用。

在我构建的一个用于测试此问题的示例应用程序中(只有一个小活动托管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重载来处理来自JavaScript的地理位置请求:

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帮助页面,此处略有删减):

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

我的观察是,JavaScript中的navigator.geolocation.getCurrentPosition(success, error)调用从未返回。我注意到Java中的onGeolocationPermissionsShowPrompt()方法被正确地调用,并且我在那里检查权限时始终获得结果0,即PackageManager.PERMISSION_GRANTED,因此每次调用都会执行callback.invoke(origin, true, true)。如果我尝试几次,我就能看到我的Java代码多次调用。然而,在我调用invoke()之后,JavaScript这方面仍然没有任何反应。

我添加了检查授予权限的代码,使用了在GeolocationPermissions类中调用getOrigins(ValueCallback<Set<String>> callback),如文档这里所述。我在回调中看到我的来源已被允许请求位置(它们被列在集合中)。

有什么想法可能出了问题?


2
也许是 webview.setJavaScriptEnabled(true),哈哈,只是猜测。 - JRowan
2
@JRowan 眼力真好!如果是那个原因就太好了,但我只是在复制项目中的代码时跳过了那一行。现在已经修复了,所以那不是原因。 - Alexander Galkin
1
请添加您的重现 git 链接,我想看一下您的重现仓库以便帮助。 - AmerllicA
8个回答

9

尝试使用选项设置超时时间(来源):

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

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

如果失败,请尝试覆盖getCurrentPosition(来源):
(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/字符串。当我在Android中感到定位困难时,我曾经使用过这种方法。然后你就不必担心https了。

我个人会在“本地”代码中从设备本身获取位置,然后添加一个webView.addJavascriptInterface(new Tracker(webView), "alexander"),我将使用它来创建本地代码和webview之间的桥梁,以便网页可以调用您的本地函数。然后其他人可以在加载页面时(如果与他们交谈)使用alexander.lat、alexander.lon,如果不是则使用一些注入,使用上述方法覆盖navigator.geolocation。 - Gillsoft AB
还可以尝试添加:webView.setWebViewClient(new WebViewClient()); mWebSettings.setPluginState(WebSettings.PluginState.ON); mWebSettings.setJavaScriptEnabled(true); 之后。 - Gillsoft AB
好的,你尝试使用过这个方法吗:callback.invoke(origin, true, false); 我记得在某个地方看到过,如果你更改了域名,就需要新的批准,而且由于你已经保存了用户的选择,所以它可能会失败,你尝试过了吗?不过我可能记错了。 - Gillsoft AB
谢谢您!使用超时选项让它在我的 WebView 应用程序中工作了! - Laurens Swart
我们是否可以跳过插件调用,直接调用Android谷歌地图获取当前位置并将其传递给插件结果? - prat
显示剩余4条评论

4

看起来你的情况有两个不同的问题:

  1. getCurrentPosition 从不失败
  2. getCurrentPosition 从不成功

第一个问题可能只是因为method具有无限的timeout

默认值为Infinity,这意味着getCurrentPosition()直到位置可用才会返回。

第二个问题可能比较棘手,有一个参数maximumAge,它的意思是

PositionOptions.maximumAge属性是一个正长整型值,表示可接受返回的可能被缓存的位置的最大年龄(以毫秒为单位)。如果设置为0,则意味着设备无法使用缓存的位置,并必须尝试检索实际的当前位置。如果设置为Infinity,则设备必须返回一个缓存的位置,而不考虑其年龄。

默认情况下,0 表示设备不会使用缓存位置,而是尝试获取真实位置,这可能会导致长时间响应的问题。
此外,您可以检查这些报告,这可能意味着此 API 在 Android 上表现不佳: https://github.com/ionic-team/ionic-native/issues/1958 https://issues.apache.org/jira/browse/CB-13241 *Cordova 将地理定位功能留给浏览器。

非常好的解释。 - EJK

2
针对目标为 Android N 及更高版本的 SDK(API 级别 > Build.VERSION_CODES.M)的应用程序,方法 onGeolocationPermissionsShowPrompt (String origin, GeolocationPermissions.Callback callback) 仅在来自安全来源(如 HTTPS)的请求时调用。对于非安全来源,地理位置请求会自动被拒绝。 如果您尝试在该方法中设置断点或日志,就可以自己缩小问题范围。

您有两个选择:

  1. 将目标 API 设置为较低级别,这显然更容易,但不是很受欢迎。
  2. 在您的网站上设置 SSL。

2

如果你们中还有人遇到这个问题,我通过设置 options 参数的一些值,成功让 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 }
    );
}

希望它对你有用!

谢谢伙计,我已经花了两天时间找出代码的问题,而你解决了它。谢谢,伙计。 - Maninder Singh

1
将你的请求权限改为以下内容:
ActivityCompat.requestPermissions(this, new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    },0);

这似乎有效。


1
实际上,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.

0

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接