javascript - navigator.geolocation.getCurrentPosition() nunca regresa en WebView en Android
android-webview w3c-geolocation (6)
Estoy tratando de acceder a la API de geolocalización HTML disponible en Android WebView (con SDK versión 24).
El problema principal es que la llamada a navigator.geolocation.getCurrentPosition()
en JavaScript nunca regresa (ni con un error, ni con datos de posición), mientras que en el lado de la aplicación verifico los permisos y los paso correctamente a WebView usando android.webkit.GeolocationPermissions.Callback
class.
ACTUALIZACIÓN: Solo para aclarar aquí, con "nunca devuelve" quiero decir que ninguna de las devoluciones de llamada también proporcionadas navigator.geolocation.getCurrentPosition(success, error)
nunca se llama.
En una aplicación de muestra que creé para probar esto (con solo una pequeña actividad que aloja WebView) declaro los permisos en manifiesto y los solicito correctamente al iniciar la aplicación. Veo el aviso y puedo conceder o denegar el permiso a la información de ubicación.
Manifiesto:
<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" />
Código en el formulario principal:
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;
}
}
Puedo verificar los permisos durante el tiempo de ejecución utilizando Context.checkSelfPermission()
y veo que se conceden los permisos respectivos a mi aplicación.
Luego trato de abrir una página web en un control WebView
. Habilito todas las opciones requeridas en la configuración:
mWebSettings.setJavaScriptEnabled(true);
mWebSettings.setAppCacheEnabled(true);
mWebSettings.setDatabaseEnabled(true);
mWebSettings.setDomStorageEnabled(true);
mWebSettings.setGeolocationEnabled(true);
mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
mWebSettings.setSupportZoom(true);
Uso la siguiente sobrecarga de WebChromeClient
para manejar solicitudes de geolocalización desde 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);
}
}
}
Para probar esto, uso el siguiente código (tomado de la página de ayuda de la API de Mozilla , acortado aquí):
function geoFindMe() {
function success(position) {}
function error() {}
navigator.geolocation.getCurrentPosition(success, error);
}
Lo que veo es que la llamada a navigator.geolocation.getCurrentPosition(success, error)
en JavaScript nunca regresa . Veo que el método onGeolocationPermissionsShowPrompt()
en Java se llama correctamente y, al verificar los permisos, siempre obtengo el resultado 0, es decir, PackageManager.PERMISSION_GRANTED
, por lo que callback.invoke(origin, true, true)
se ejecuta en cada llamada. Si lo intento varias veces, veo varias llamadas a mi código Java. Aún así, no sucede nada en el lado de JavaScript aquí después de que llame a invoke()
.
getOrigins(ValueCallback<Set<String>> callback)
el código para verificar los permisos otorgados usando la invocación de getOrigins(ValueCallback<Set<String>> callback)
en la clase GeolocationPermissions
, como se describe aquí en la documentación . Veo en la devolución de llamada que mis orígenes tienen permiso para solicitar ubicaciones (se enumeran en el conjunto).
¿Alguna idea de lo que podría estar mal aquí?
Cambie su solicitud de permiso como este,
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
},0);
Esto parece funcionar.
En realidad, el navigator
es un elemento secundario del objeto global del navegador, la window
. Primero debes acceder a la window
luego llamar al navigator
. En algunos navegadores modernos, además de la window
, algunos objetos secundarios se presentan como un objeto global, por ejemplo: location
, navigator
y etc.
El WebView
no tiene ninguna window
objeto global. Para que pueda agregarlo manualmente, para esta acción, lea este artículo del medio .
Tal vez tu código sea como el siguiente:
my_web_view.evaluateJavascript("javascript: " + "updateFromAndroid(/"" + edit_text_to_web.text + "/")", null);
Y luego agregue la interfaz de JavaScript
, como el objeto de window
, a este evaluador:
my_web_view.addJavascriptInterface(JavaScriptInterface(), JAVASCRIPT_OBJ); //JAVASCRIPT_OBJ: like window, like navigator, like location and etc.
Hay un extenso post sobre este tema:
navigator.geolocation.getCurrentPosition a veces funciona, a veces no
Aparentemente, la solución es verificar en navigator.geolocation
luego hacer la llamada:
if(navigator.geolocation) { // dummy call
navigator.geolocation.getCurrentPosition(success, error)
}
Para aplicaciones dirigidas a Android N y SDK posteriores (nivel de API> Build.VERSION_CODES.M), el método onGeolocationPermissionsShowPrompt (String origin, GeolocationPermissions.Callback callback)
solo se llama para solicitudes que se originan en orígenes seguros como HTTPS. En orígenes no seguros, las solicitudes de geolocalización se rechazan automáticamente.
Podría limitar su problema por sí mismo si hubiera intentado poner un punto de interrupción o un registro dentro del método.
Tienes dos opciones:
- Dirigirse a una API de nivel inferior, que obviamente es mucho más fácil pero no se aprecia realmente.
- Configure SSL en su sitio web.
Parece que hay 2 problemas diferentes en tu caso:
-
getCurrentPosition
nunca falla -
getCurrentPosition
nunca tiene éxito
El primer punto podría ser solo porque el source tiene timeout infinito
El valor predeterminado es Infinito, lo que significa que getCurrentPosition () no regresará hasta que la posición esté disponible.
El segundo punto puede ser complicado, hay un param maximumAge que significa
La propiedad PositionOptions.maximumAge es un valor largo positivo que indica la antigüedad máxima en milisegundos de una posible posición en caché que es aceptable devolver. Si se establece en 0, significa que el dispositivo no puede usar una posición en caché y debe intentar recuperar la posición actual real. Si se establece en Infinito, el dispositivo debe devolver una posición guardada en caché independientemente de su antigüedad.
0
por defecto significa que el dispositivo no usará la posición en caché e intentará obtener la real y podría ser un problema para una respuesta prolongada.
También puede consultar este informe, lo que podría significar que esta API no funciona realmente bien en Android: https://github.com/ionic-team/ionic-native/issues/1958 https://issues.apache.org/jira/browse/CB-13241
* Córdoba deja cosas de geolocalización para el navegador.
Pruebe con opciones para establecer el tiempo de espera ( source ):
var options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
};
navigator.geolocation.getCurrentPosition(success, error, options);
Si falla, intente anular 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);
}
}
})();
Como tercera opción, anote con @JavascriptInterface ( source ) en EmbeddedChromeClient
También agregue en el lugar apropiado en su código:
mWebSettings.setJavaScriptEnabled(true);
//...
mWebSettings.setSupportZoom(true);
webView.addJavascriptInterface(new EmbeddedChromeClient(webView), "injectedObject");
webView.loadData("html here", "text/html", null);
La última opción es simplemente usar etiquetas en html, cargar el html desde el almacenamiento en disco, reemplazar las etiquetas en la función de llamada, cargar el html / cadena en el webView. He usado este enfoque antes en Android cuando el posicionamiento me frustró demasiado. Entonces tampoco tienes que preocuparte por https.