geolocalización de webview android

103

Tengo que recuperar la ubicación de un usuario en un archivo WebView. Hago esto con el siguiente Javascript:

function getLocation() {
   navigator.geolocation.getCurrentPosition(displayLocation, handleError);
}

Pero la ventana emergente de solicitud de permiso nunca se abre.

He establecido estas configuraciones:

ws.setJavaScriptEnabled(true);
ws.setGeolocationEnabled(true);
ws.setJavaScriptCanOpenWindowsAutomatically(true);

¿Cuál es la forma correcta de acceder a la ubicación de un usuario desde dentro de un WebView?

Ste
fuente

Respuestas:

221
  • JavaScript debe estar habilitado en el WebView, usandoWebSettings.setJavaScriptEnabled(true);
  • La aplicación necesita permiso ACCESS_FINE_LOCATION
  • El WebViewdebe utilizar una costumbre WebChromeClientque implementa WebChromeClient.onGeolocationPermissionsShowPrompt(). Este método es llamado por el WebViewpara obtener permiso para revelar la ubicación del usuario a JavaScript. (En el caso del navegador, mostramos un mensaje al usuario). La implementación predeterminada no hace nada, por lo que nunca se obtiene el permiso y la ubicación nunca se pasa a JavaScript. Una implementación simple que siempre otorga permiso es ...

    webView.setWebChromeClient(new WebChromeClient() {
     public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
        callback.invoke(origin, true, false);
     }
    });

La geolocalización utiliza bases de datos para conservar las posiciones y los permisos almacenados en caché entre sesiones. La ubicación de la base de datos se establece mediante WebSettings.setGeolocationDatabasePath(...). Si no se establece la ubicación de la base de datos, el almacenamiento persistente no estará disponible, pero la geolocalización seguirá funcionando correctamente de lo contrario. Para establecer la ubicación de las bases de datos, use ...

webView.getSettings().setGeolocationDatabasePath( context.getFilesDir().getPath() );
Chris Cashwell
fuente
6
esta respuesta es correcta, pero debe usar android.webkit.GeolocationPermissions.Callback
NullPointerException
3
Aquí hay un ejemplo de código completo turbomanage.wordpress.com/2012/04/23/…
giorgio79
2
¿Dónde se define el "contexto"?
Gráficos C
5
Terminé teniendo que usar el totalmente calificado en android.webkit.WebChromeClient()lugar de solo WebChromeClient()y android.webkit.GeolocationPermissions.Callbackpara que esto funcione.
Fat Monk
2
Esto ya no funciona con target / runtime> Android 6.0, debido a los permisos de runtime, ¿verdad?
最 白 目
17

¿Está declarando ese permiso en su manifiesto?

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

También es posible que deba declarar otros permisos de ubicación, como estos:

<uses-permission android:name="android.permission.ACCESS_GPS" />
<uses-permission android:name="android.permission.ACCESS_ASSISTED_GPS" />
<uses-permission android:name="android.permission.ACCESS_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Chris Cashwell
fuente
3
Sí, declaro estos permisos. La vista web toma la ubicación del usuario, pero el problema es que el cuadro de diálogo para aceptar o rechazar la ubicación del usuario no aparece.
Ste
3
¡Solo se requiere el primero!
TacB0sS
17

El programador diseñó el cuadro de diálogo para aceptar o rechazar la ubicación del usuario: D. Como dijo Chris Cashwell, solo usa una devolución de llamada como esta:

webview.setWebChromeClient(new WebChromeClient(){
  public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
    // callback.invoke(String origin, boolean allow, boolean remember);
    callback.invoke(origin, true, false);
  }
}

En algunos casos, HTML5 requiere usar almacenamiento, debe habilitar algunas propiedades para que webview tenga acceso completo para funcionar normalmente.

    // HTML5 API flags
    webView.getSettings().setAppCacheEnabled(true);
    webView.getSettings().setDatabaseEnabled(true);
    webView.getSettings().setDomStorageEnabled(true);
terremoto
fuente
15

Compartiendo mi clase de actividad laboral, esta es una solución completa que puede demostrar

  • Mostrando diálogo de carga mientras se carga la página web
  • Pide permiso en malvavisco y superior
  • Manejar error de página web
  • Verifique la conexión a Internet y abra la página de configuración
  • Manejo del permiso de geolocalización con y sin diálogo

Esperanza, le ahorra tiempo a alguien

    /**
     * Created by Hitesh.Sahu on 3/24/2017.
     */

    public class WebViewActivity extends AppCompatActivity {

        final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
        private int webViewPreviousState;
        private final int PAGE_STARTED = 0x1;
        private final int PAGE_REDIRECTED = 0x2;
        private CoordinatorLayout rootView;
        private WebView webView;


        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_webview);
            webView = (WebView) findViewById(R.id.webView);
            rootView = (CoordinatorLayout) findViewById(R.id.root_view);

            if (Build.VERSION.SDK_INT >= 23) {
                // Marshmallow+ Permission APIs
                fuckMarshMallow();
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) {
                    WebView.setWebContentsDebuggingEnabled(true);
                }
            }
            webView.setInitialScale(1);
            webView.getSettings().setLoadWithOverviewMode(true);
            webView.getSettings().setUseWideViewPort(true);
            webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
            webView.setScrollbarFadingEnabled(false);

            webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
            webView.getSettings().setBuiltInZoomControls(true);
            webView.setWebViewClient(new GeoWebViewClient());
            // Below required for geolocation
            webView.getSettings().setJavaScriptEnabled(true);
            webView.getSettings().setGeolocationEnabled(true);
            webView.setWebChromeClient(new GeoWebChromeClient());

            webView.getSettings().setAppCacheEnabled(true);
            webView.getSettings().setDatabaseEnabled(true);
            webView.getSettings().setDomStorageEnabled(true);

            webView.getSettings().setGeolocationDatabasePath(getFilesDir().getPath());

            webView.loadUrl("file:///android_asset/index.html");
        }

        /**
         * WebChromeClient subclass handles UI-related calls
         * Note: think chrome as in decoration, not the Chrome browser
         */
        public class GeoWebChromeClient extends android.webkit.WebChromeClient {
            @Override
            public void onGeolocationPermissionsShowPrompt(final String origin,
                                                           final GeolocationPermissions.Callback callback) {
                // Always grant permission since the app itself requires location
                // permission and the user has therefore already granted it
                callback.invoke(origin, true, false);

    //            final boolean remember = false;
    //            AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this);
    //            builder.setTitle("Locations");
    //            builder.setMessage("Would like to use your Current Location ")
    //                    .setCancelable(true).setPositiveButton("Allow", new DialogInterface.OnClickListener() {
    //                public void onClick(DialogInterface dialog, int id) {
    //                    // origin, allow, remember
    //                    callback.invoke(origin, true, remember);
    //                }
    //            }).setNegativeButton("Don't Allow", new DialogInterface.OnClickListener() {
    //                public void onClick(DialogInterface dialog, int id) {
    //                    // origin, allow, remember
    //                    callback.invoke(origin, false, remember);
    //                }
    //            });
    //            AlertDialog alert = builder.create();
    //            alert.show();
            }
        }

        /**
         * WebViewClient subclass loads all hyperlinks in the existing WebView
         */
        public class GeoWebViewClient extends WebViewClient {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                // When user clicks a hyperlink, load in the existing WebView
                view.loadUrl(url);
                return true;
            }

            Dialog loadingDialog = new Dialog(WebViewActivity.this);

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                webViewPreviousState = PAGE_STARTED;

                if (loadingDialog == null || !loadingDialog.isShowing())
                    loadingDialog = ProgressDialog.show(WebViewActivity.this, "",
                            "Loading Please Wait", true, true,
                            new DialogInterface.OnCancelListener() {

                                @Override
                                public void onCancel(DialogInterface dialog) {
                                    // do something
                                }
                            });

                loadingDialog.setCancelable(false);
            }


            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onReceivedError(WebView view, WebResourceRequest request,
                                        WebResourceError error) {


                if (isConnected()) {
                    final Snackbar snackBar = Snackbar.make(rootView, "onReceivedError : " + error.getDescription(), Snackbar.LENGTH_INDEFINITE);
                    snackBar.setAction("Reload", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            webView.loadUrl("javascript:window.location.reload( true )");
                        }
                    });
                    snackBar.show();
                } else {
                    final Snackbar snackBar = Snackbar.make(rootView, "No Internet Connection ", Snackbar.LENGTH_INDEFINITE);
                    snackBar.setAction("Enable Data", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            startActivityForResult(new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
                            webView.loadUrl("javascript:window.location.reload( true )");
                            snackBar.dismiss();
                        }
                    });
                    snackBar.show();
                }

                super.onReceivedError(view, request, error);

            }

            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onReceivedHttpError(WebView view,
                                            WebResourceRequest request, WebResourceResponse errorResponse) {

                if (isConnected()) {
                    final Snackbar snackBar = Snackbar.make(rootView, "HttpError : " + errorResponse.getReasonPhrase(), Snackbar.LENGTH_INDEFINITE);

                    snackBar.setAction("Reload", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            webView.loadUrl("javascript:window.location.reload( true )");
                        }
                    });
                    snackBar.show();
                } else {
                    final Snackbar snackBar = Snackbar.make(rootView, "No Internet Connection ", Snackbar.LENGTH_INDEFINITE);
                    snackBar.setAction("Enable Data", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            startActivityForResult(new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
                            webView.loadUrl("javascript:window.location.reload( true )");
                            snackBar.dismiss();
                        }
                    });
                    snackBar.show();
                }
                super.onReceivedHttpError(view, request, errorResponse);
            }

            @Override
            public void onPageFinished(WebView view, String url) {

                if (webViewPreviousState == PAGE_STARTED) {

                    if (null != loadingDialog) {
                        loadingDialog.dismiss();
                        loadingDialog = null;
                    }
                }
            }
        }


        /**
         * Check if there is any connectivity
         *
         * @return is Device Connected
         */
        public boolean isConnected() {

            ConnectivityManager cm = (ConnectivityManager)
                    this.getSystemService(Context.CONNECTIVITY_SERVICE);

            if (null != cm) {
                NetworkInfo info = cm.getActiveNetworkInfo();
                return (info != null && info.isConnected());
            }

            return false;

        }

        @Override
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            switch (requestCode) {
                case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
                    Map<String, Integer> perms = new HashMap<String, Integer>();
                    // Initial
                    perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);


                    // Fill with results
                    for (int i = 0; i < permissions.length; i++)
                        perms.put(permissions[i], grantResults[i]);

                    // Check for ACCESS_FINE_LOCATION
                    if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED


                            ) {
                        // All Permissions Granted

                        // Permission Denied
                        Toast.makeText(WebViewActivity.this, "All Permission GRANTED !! Thank You :)", Toast.LENGTH_SHORT)
                                .show();

                    } else {
                        // Permission Denied
                        Toast.makeText(WebViewActivity.this, "One or More Permissions are DENIED Exiting App :(", Toast.LENGTH_SHORT)
                                .show();

                        finish();
                    }
                }
                break;
                default:
                    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }

        @TargetApi(Build.VERSION_CODES.M)
        private void fuckMarshMallow() {
            List<String> permissionsNeeded = new ArrayList<String>();

            final List<String> permissionsList = new ArrayList<String>();
            if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
                permissionsNeeded.add("Show Location");

            if (permissionsList.size() > 0) {
                if (permissionsNeeded.size() > 0) {

                    // Need Rationale
                    String message = "App need access to " + permissionsNeeded.get(0);

                    for (int i = 1; i < permissionsNeeded.size(); i++)
                        message = message + ", " + permissionsNeeded.get(i);

                    showMessageOKCancel(message,
                            new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                            REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                                }
                            });
                    return;
                }
                requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                return;
            }

            Toast.makeText(WebViewActivity.this, "No new Permission Required- Launching App .You are Awesome!!", Toast.LENGTH_SHORT)
                    .show();
        }


        private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
            new AlertDialog.Builder(WebViewActivity.this)
                    .setMessage(message)
                    .setPositiveButton("OK", okListener)
                    .setNegativeButton("Cancel", null)
                    .create()
                    .show();
        }

        @TargetApi(Build.VERSION_CODES.M)
        private boolean addPermission(List<String> permissionsList, String permission) {

            if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                permissionsList.add(permission);
                // Check for Rationale Option
                if (!shouldShowRequestPermissionRationale(permission))
                    return false;
            }
            return true;
        }
    }
Hitesh Sahu
fuente
2
Esta es la única respuesta que tuvo en cuenta el hecho de que al crear la versión 6+ de Android, debe solicitar permisos durante el tiempo de ejecución.
hatzaviv
1
LMAO, F Marshmallow. Jajaja.
Taslim Oseni
5

Este es un ejemplo de mostrar un diálogo de alerta para promover el permiso del usuario para usar su ubicación:

     @Override
     public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
        Log.i(TAG, "onGeolocationPermissionsShowPrompt()");

        final boolean remember = false;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Locations");
        builder.setMessage("Would like to use your Current Location ")
        .setCancelable(true).setPositiveButton("Allow", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
              // origin, allow, remember
              callback.invoke(origin, true, remember);
           }
        }).setNegativeButton("Don't Allow", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
              // origin, allow, remember
              callback.invoke(origin, false, remember);
           }
        });
        AlertDialog alert = builder.create();
        alert.show();
     }
Gary S.
fuente
4
¡Gracias! Un pequeño retoque: el argumento pasado al constructor AlertDialog.Builder debería ser su actividad. En ese contexto, "esto" es incorrecto.
Joseph Lennox
¿Alguien podría darme un código de muestra completamente funcional para esto? Gracias por adelantado.
farsa
2
He hecho todo esto pero el código todavía no funciona, ¿qué está mal? Lo estoy probando en el emulador de eclipse
shams
1

Recientemente me encontré con este tipo de situación y he realizado los siguientes pasos para lograrlo:

Paso 1: agregue permisos en su AndroidManifest.xmlarchivo

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

Paso 2: crea una actividad que contenga un WebViewy ProgressBar(en mi caso)

xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="0dp"
        android:minHeight="4dp"
        android:padding="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <WebView
        android:id="@+id/webView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar" />
</androidx.constraintlayout.widget.ConstraintLayout>

Paso 3: en su clase de actividad, agregue el código a continuación y realice los cambios de acuerdo con sus necesidades

class WebActivity : AppCompatActivity() {
    var pageUrl: String = "https://couponia.co/"
    var mGeoLocationRequestOrigin: String? = null
    var mGeoLocationCallback: GeolocationPermissions.Callback? = null
    val MAX_PROGRESS = 100
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web)
        initWebView()
        setWebClient()
        loadUrl(pageUrl)
    }


    @SuppressLint("SetJavaScriptEnabled")
    private fun initWebView() {
        webView.settings.javaScriptEnabled = true
        webView.settings.loadWithOverviewMode = true
        webView.settings.useWideViewPort = true
        webView.settings.domStorageEnabled = true
        webView.settings.databaseEnabled = true
        webView.settings.setAppCacheEnabled(true)
        webView.webViewClient = object : WebViewClient() {
            override
            fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
                handler?.proceed()
            }
        }

    }

    private fun setWebClient() {

        webView.webChromeClient = object : WebChromeClient() {
            override fun onGeolocationPermissionsShowPrompt(
                origin: String?,
                callback: GeolocationPermissions.Callback?
            ) {

                if (ContextCompat.checkSelfPermission(
                        this@WebActivity,
                        Manifest.permission.ACCESS_FINE_LOCATION
                    )
                    != PackageManager.PERMISSION_GRANTED
                ) {

                    if (ActivityCompat.shouldShowRequestPermissionRationale(
                            this@WebActivity,
                            Manifest.permission.ACCESS_FINE_LOCATION
                        )
                    ) {
                        AlertDialog.Builder(this@WebActivity)
                            .setMessage("Please turn ON the GPS to make app work smoothly")
                            .setNeutralButton(
                                android.R.string.ok,
                                DialogInterface.OnClickListener { dialogInterface, i ->
                                    mGeoLocationCallback = callback
                                    mGeoLocationRequestOrigin = origin
                                    ActivityCompat.requestPermissions(
                                        this@WebActivity,
                                        arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001
                                    )

                                })
                            .show()

                    } else {
                        //no explanation need we can request the locatio
                        mGeoLocationCallback = callback
                        mGeoLocationRequestOrigin = origin
                        ActivityCompat.requestPermissions(
                            this@WebActivity,
                            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001
                        )
                    }
                } else {
                    //tell the webview that permission has granted
                    callback!!.invoke(origin, true, true)
                }

            }

            override fun onProgressChanged(view: WebView?, newProgress: Int) {
                super.onProgressChanged(view, newProgress)
                progressBar.progress = newProgress
                if (newProgress < MAX_PROGRESS && progressBar.visibility == ProgressBar.GONE) {
                    progressBar.visibility = ProgressBar.VISIBLE
                }
                if (newProgress == MAX_PROGRESS) {
                    progressBar.visibility = ProgressBar.GONE
                }
            }


        }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        // Check if the key event was the Back button and if there's history
        if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
            webView.goBack()
            return true
        }
        // If it wasn't the Back key or there's no web page history, exit the activity)
        return super.onKeyDown(keyCode, event)
    }

    private fun loadUrl(pageUrl: String) {
        webView.loadUrl(pageUrl)
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        when (requestCode) {
            1001 -> {
                //if permission is cancel result array would be empty
                if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //permission was granted
                    if (mGeoLocationCallback != null) {
                        mGeoLocationCallback!!.invoke(mGeoLocationRequestOrigin, true, true)
                    }
                } else {
                    //permission denied
                    if (mGeoLocationCallback != null) {
                        mGeoLocationCallback!!.invoke(mGeoLocationRequestOrigin, false, false)
                    }
                }
            }

        }
    }
}
Suraj Bahadur
fuente