Android Webview:navigator.geolocation API 不工作,抛出异常 POSITION_UNAVAILABLE

Android Webview: navigator.geolocation API is not working, throwing exception POSITION_UNAVAILABLE

我正在尝试将 Web 应用程序嵌入 Android Webview。我的 Web 应用程序使用 HTML5 Geolocation API 来获取用户的当前位置。现在我们正在尝试创建一个 android 应用程序,它在 webview 中打开它,用户也能够从移动应用程序执行相同的操作。 在 Webview 中,我们无法看到任何类型的用户要求允许或拒绝的消息,就像我们在浏览器中看到的那样,它会抛出 POSITION_UNAVAILABLE 错误。

与地理定位相关的Web应用JS代码API

// Geolocation fetch function for getting value 
function fetchGeoLocationFunction(result){
    if(result ==  true){
        // reset the search location to initiate with the new process
        resetAddressSelection();
        if (navigator.geolocation){            
            navigator.geolocation.getCurrentPosition(showPosition,showError);
        }
        else {
            var msgToDisplay = 'Geolocation is not supported by your browser or device, Please try with any latest popular browser';
            CreatePopupMessage(false,msgToDisplay,'Not Supported',false);            
        }
    }
}

function showPosition(position){   
    var attempt = 1;
    convertGeoCordstoAddress(position.coords.latitude,position.coords.longitude,attempt);
}

function showError(error){
showProcessingBar(); // Show the Processing Bar
setTimeout(function(){
   hideProcessingBar();
   console.log('Its in failure',error.code);
   var err_Msg = '';
   switch(error.code){    
        case error.PERMISSION_DENIED:
          err_Msg = 'You have denied the Request for your Geo location, without that You can\'t proceed further. Please allow this operation from your browser settings'
          break;
        case error.POSITION_UNAVAILABLE:
          err_Msg ='Location information is unavailable. please try after some time'
          break;
        case error.TIMEOUT:
          err_Msg ='The request to get user location timed out.Please try again'
          break;
        case error.UNKNOWN_ERROR:
          err_Msg ='An unknown error occurred. if the error is repetative, please contact to application provider'
          break;
   }   
   if(err_Msg == ''){
       err_Msg  = 'An Error Occured, Contact to Application provider';
   }
   //alert(err_Msg);
   CreatePopupMessage(false,err_Msg,'',true);   
},500);  
}

这是我的 android 应用代码开始

Android 应用代码

MainActivuty.java

package com.example.timedin;

import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;

import android.provider.Settings;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.CookieManager;
import android.webkit.GeolocationPermissions;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Manifest;

public class MainActivity extends AppCompatActivity {
    private WebView webView;
    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;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // webview initilization
        webView = (WebView) findViewById(R.id.webview);
        webView.setWebViewClient(new GeoWebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setAppCacheEnabled(false);
        webView.getSettings().setDatabaseEnabled(true);
        webView.getSettings().setDomStorageEnabled(true);
        webView.getSettings().setGeolocationEnabled(true);

        /// Below required for geolocation
        webView.setWebChromeClient(new GeoWebChromeClient());
        webView.getSettings().setGeolocationDatabasePath(getFilesDir().getPath());
        webView.loadUrl("<my_webapp_url>");

    }

    /**
     * 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) {

            // permission and the user has therefore already granted it
            callback.invoke(origin, true, false);

        }
    }

    /**
     * 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(MainActivity.this);

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
            webViewPreviousState = PAGE_STARTED;
            //CookieManager.getInstance().removeAllCookies(null);
            if (loadingDialog == null || !loadingDialog.isShowing())
                loadingDialog = ProgressDialog.show(MainActivity.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(android.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(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED


                ) {
                    // All Permissions Granted

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

                } else {
                    // Permission Denied
                    Toast.makeText(MainActivity.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, android.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(MainActivity.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(MainActivity.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;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<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" />

所以每次当我 运行 应用程序在 android Webview 中时,它什么都不问它只是向我显示带有错误的弹出消息,POSITION_UNAVAILABLE 来自 JS 代码的错误,这给出了错误 Location information is unavailable. please try after some time

我已经尝试了其他代码逻辑并阅读了其他各种答案,但 none 其中的答案有效。我目前正在使用 android pie 版本进行测试,但也尝试了其他版本,但它不起作用。

您似乎没有请求运行时权限。使用这样的东西:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {

    // Permission is not granted
    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.ACCESS_FINE_LOCATION)) {
        // Show an explanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.
    } else {
        // No explanation needed; request the permission
        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);

        // MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
} else {
    // Permission has already been granted
}

查看 https://developer.android.com/training/permissions/usage-notes.html 了解详情。

让我们检查一下问题出在调用它的权限代码上。我不知道你为什么要以这种方式调用许可,因为我们有很多你可以轻松调用它的媒介。让我们使用nabinbhandari/Android-Permissions它很容易使用。

这样调用:

@Override
protected void onCreate(Bundle savedInstanceState) {

                .....................


String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION, 
Manifest.permission.ACCESS_COARSE_LOCATION};
Permissions.check(this/*context*/, permissions, null/*rationale*/, null/*options*/, new 
PermissionHandler() {
  @Override
  public void onGranted() {
    // call your task

    // webview initilization
    webView = (WebView) findViewById(R.id.webview);
    webView.setWebViewClient(new GeoWebViewClient());
    webView.getSettings().setJavaScriptEnabled(true);
    webView.getSettings().setAppCacheEnabled(false);
    webView.getSettings().setDatabaseEnabled(true);
    webView.getSettings().setDomStorageEnabled(true);
    webView.getSettings().setGeolocationEnabled(true);

    /// Below required for geolocation
    webView.setWebChromeClient(new GeoWebChromeClient());
    webView.getSettings().setGeolocationDatabasePath(getFilesDir().getPath());
    webView.loadUrl("<my_webapp_url>");


  }

  @Override
  public void onDenied(Context context, ArrayList<String> deniedPermissions) {
      // permission denied, block the feature.
    Toast.makeText(this,"Permission denied",Toast.LENGTH_SHORT);
  }

});

或者根据您的需要实现这些权限覆盖功能。