navigator.geolocation.getCurrentPosition() никогда не возвращается в WebView на Android

Я пытаюсь получить доступ к API геолокации HTML, доступному в Android WebView (с использованием SDK версии 24).

Основная проблема заключается в том, что вызов navigator.geolocation.getCurrentPosition() в JavaScript никогда не возвращается (ни с ошибкой, ни с данными позиции), а на стороне приложения я проверяю разрешения и правильно передаю их в WebView с помощью android.webkit.GeolocationPermissions.Callback Класс android.webkit.GeolocationPermissions.Callback.

ОБНОВЛЕНИЕ: просто чтобы пояснить здесь, "никогда не возвращается", я имею в виду, что ни один из слишком поданных обратных вызовов navigator.geolocation.getCurrentPosition(success, error) когда-либо вызываются.

В примере приложения, которое я построил, чтобы протестировать это (только с одним небольшим хостингом WebView), я объявляю разрешения в манифесте и запрашиваю их правильно в начале приложения. Я вижу приглашение и могу предоставить или запретить разрешение на информацию о местоположении.

Manifest:

<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 управления 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);
        }
    }
}

Чтобы проверить это, я использую следующий код (взятый из справочной страницы API Mozilla, сокращенный здесь):

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

Я вижу, что вызов navigator.geolocation.getCurrentPosition(success, error) в JavaScript никогда не возвращается. Я вижу, что onGeolocationPermissionsShowPrompt() в Java правильно называется, и когда я проверяю разрешения, я всегда получаю результат 0, то есть PackageManager.PERMISSION_GRANTED, поэтому callback.invoke(origin, true, true) выполняется при каждом вызове. Если я попробую несколько раз, я вижу несколько вызовов моего Java-кода. Тем не менее, ничего не происходит на стороне JavaScript здесь после вызова invoke().

Я добавил код для проверки разрешенных прав с помощью вызова getOrigins(ValueCallback<Set<String>> callback) в классе GeolocationPermissions, как описано здесь в документации. Я вижу в обратном вызове, что в моем происхождении разрешено запрашивать местоположения (они перечислены в наборе).

Какие-нибудь идеи, что может быть неправильно здесь?

Ответ 1

Попробуйте с настройками таймаута (источник):

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);
    }
}
})();

Как третий вариант, аннотироваться с @JavascriptInterface (source) в EmbeddedChromeClient

Также добавьте в нужное место в своем коде:

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

Последний вариант - просто использовать теги в html, загружать html с дискового хранилища, заменять теги в вызывающей функции, загружать html/string в webView. Я использовал этот подход раньше, чем в Android, когда позиционирование меня слишком расстраивало. Тогда вам также не нужно беспокоиться о https.

Ответ 2

Похоже, в вашем случае есть две разные проблемы:

  1. getCurrentPosition никогда не сработает
  2. getCurrentPosition никогда не будет успешным

Первая точка может быть только потому, что метод имеет бесконечный тайм-аут

Значение по умолчанию - Infinity, что означает, что getCurrentPosition() не вернется, пока позиция не будет доступна.

Второй момент может быть сложным, существует параметр maximumAge, что означает

Свойство PositionOptions.maximumAge является положительным длинным значением, указывающим максимальный возраст в миллисекундах возможной позиции в кэше, приемлемой для возврата. Если установлено значение 0, это означает, что устройство не может использовать кэшированную позицию и должно попытаться получить реальную текущую позицию. Если установлено значение Infinity, устройство должно возвращать кэшированную позицию независимо от ее возраста.

0 по умолчанию означает, что устройство не будет использовать кэшированную позицию и попытается извлечь реальный, и это может быть проблемой для длительного ответа.

Также вы можете проверить этот репортер, который может означать, что этот API не работает действительно хорошо на Android: https://github.com/ionic-team/ionic-native/issues/1958 https://issues.apache.org/JIRA/просмотр/CB-13241

* Кордова оставляет материал геолокации для браузера.

Ответ 3

Измените запрос на подобное разрешение,

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

Кажется, это работает.

Ответ 4

Для приложений, ориентированных на Android N и более поздние версии SDK (уровень API> Build.VERSION_CODES.M), метод onGeolocationPermissionsShowPrompt (String origin, GeolocationPermissions.Callback callback) вызывается только для запросов, исходящих из безопасного источника, такого как HTTPS. При ненадежном происхождении запросы геолокации автоматически отклоняются.

Вы можете сузить свою проблему самостоятельно, если попытались поставить точку останова или журнал внутри метода.

У вас есть два варианта:

  1. Задайте более низкий уровень API, который, очевидно, намного проще, но не очень ценится.
  2. Настройте SSL на своем веб-сайте.

Ответ 5

На самом деле, navigator является дочерним элементом глобального объекта браузера, window. вы должны сначала получить доступ к window затем вызвать navigator. В некоторых современных браузерах в дополнение к window некоторый дочерний объект представлен как глобальный объект, например: location, navigator и т.д.

WebView не имеет глобального window объектов. Поэтому вы можете добавить его вручную, для этого действия, пожалуйста, прочитайте эту статью.

Возможно, ваш код будет выглядеть следующим образом:

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.