【问题标题】:navigator.geolocation.getCurrentPosition() never returns in WebView on Androidnavigator.geolocation.getCurrentPosition() 永远不会在 Android 上的 WebView 中返回
【发布时间】:2019-01-13 22:31:41
【问题描述】:

我正在尝试访问 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 help page,在此处缩短):

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&lt;Set&lt;String&gt;&gt; callback) 调用来检查授予的权限,如here in the documentation 所述。我在回调中看到允许我的来源请求位置(它们在集合中列出)。

你有什么想法吗?

【问题讨论】:

  • 也许 webview.setJavaScriptEnabled (true) 哈哈只是一个猜测。
  • @JRowan 敏锐的眼睛!如果是这样的话我会很高兴,但我只是在从我的项目中复制代码时跳过了那一行。现在修好了。所以,这不是原因。
  • 请添加您的复制 git 链接,我想查看您的复制 repo 以提供帮助

标签: javascript android android-webview w3c-geolocation


【解决方案1】:

尝试使用选项设置超时 (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。

【讨论】:

  • 虽然我可以确认添加超时参数使其适用于测试网页,但如果我尝试在该 WebView 中打开 Google 或 Yelp,这仍然不起作用。这是我真正需要的——我需要其他页面(不是我的)能够访问位置数据并显示正确的结果。
  • 我会亲自在“本机”代码中从设备本身获取位置,然后添加一个 webView.addJavascriptInterface(new Tracker(webView), "alexander") 用于在本机代码和 webview,以便网页可以调用您的本机功能。然后其他人,可以在加载页面时使用 alexander.lat、alexander.lon(如果您与他们交谈),如果不使用上述方法覆盖 navigator.geolocation 的注入。
  • 也尝试添加:webView.setWebViewClient(new WebViewClient()); mWebSettings.setPluginState(WebSettings.PluginState.ON); 之后 mWebSettings.setJavaScriptEnabled(true);
  • 我知道 Android 桥接器,我已经在应用程序中使用它来与一些 已知 页面进行通信。但我需要一个通用的解决方案,它适用于所有页面,最重要的是适用于谷歌、Yelp 等(我说的是移动网页,而不是应用程序!)。在我的 Java 代码中获取位置(并且也使用它)没有任何问题,但是 WebView 托管的页面也必须能够做到这一点。
【解决方案2】:

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

  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 将地理定位信息留给浏览器。

【讨论】:

  • 很好的解释。
【解决方案3】:

像这样更改您的许可请求,

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

这似乎行得通。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

      您有两种选择:

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

      【讨论】:

        【解决方案6】:

        如果你们中的一些人仍然遇到这个问题,我可以通过为 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 }
            );
        }
        

        希望它对你有用!

        【讨论】:

          【解决方案7】:

          关于这个主题有一篇广泛的帖子:

          navigator.geolocation.getCurrentPosition sometimes works sometimes doesn't

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

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

          【讨论】:

            【解决方案8】:

            我在 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:// 开头,调用才能正常工作。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-11-15
              • 2011-07-25
              • 1970-01-01
              • 2012-10-02
              • 2013-02-18
              • 1970-01-01
              相关资源
              最近更新 更多