Android webview 地理定位

我必须在 WebView中检索用户的位置:

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

但是许可请求弹出窗口永远不会打开。

我设置了这些设置:

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

WebView中访问用户位置的正确方法是什么?

103639 次浏览

你在旅客名单上声明了吗?

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

您可能还需要声明其他位置权限,如下所示:

<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" />
  • JavaScript must be enabled in the WebView, using WebSettings.setJavaScriptEnabled(true);
  • 该应用程序需要权限 ACCESS_FINE_LOCATION
  • The WebView must use a custom WebChromeClient which implements WebChromeClient.onGeolocationPermissionsShowPrompt(). This method 由 WebView调用,以获得向 JavaScript 披露用户位置的权限。(对于浏览器,我们向用户显示一个提示。)默认实现不执行任何操作,因此永远不会获得权限,也不会将位置传递给 JavaScript。一个总是授予权限的简单实现是..。

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

Geolocation uses databases to persist cached positions and permissions between sessions. The location of the database is set using WebSettings.setGeolocationDatabasePath(...). If the location of the database is not set, the persistent storage will not be available, but Geolocation will continue to function correctly otherwise. To set the location of the databases, use ...

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

接受或拒绝用户位置的对话框是由程序员设计的。正如 Chris Cashwell 所说,你只需要像这样使用一个回调函数:

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

In some cases, HTML5 requires the use of storage, therefore you must enable some properties so that webview has full access to run normal.

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

这是一个显示警告对话框的示例,以促进用户使用其位置的权限:

    final Context context = this;


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

分享我的工作活动类,这是一个完整的解决方案,可以演示

  • 在网页加载时显示加载对话框
  • 在棉花糖及以上请求许可
  • 处理网页错误
  • 检查互联网连接和打开设置页面
  • 处理带对话框和不带对话框的地理定位权限

霍普,这能节省人的时间

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

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

我最近遇到过这种情况,并为此采取了以下步骤:

第一步: 在 AndroidManifest.xml文件中添加权限

<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"/>

步骤2: 创建一个包含 WebViewProgressBar的活动(在我的例子中)

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>

步骤3: 在活动类中,添加以下代码并根据需要进行更改

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


}
}
}

作为一个新的答案,更新 Android 的一切在一个帖子,因为你不再需要使用 setWebChromeClient

对于 android 6 + ,你只需要在运行时使用 ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 123);请求 GPS 权限。

当从 Webview 请求位置时,需要动态请求 Permission

确保您已将 ACCESS _ FINE _ LOCATION 添加到舱单

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


在顶级类中定义回调函数和原点,以便能够将它们分配给 ChromeClient 提供的回调函数和原点

public class MainActivity extends AppCompatActivity {


private android.webkit.WebView myWebView;
String mGeoLocationRequestOrigin = null;
GeolocationPermissions.Callback  mGeoLocationCallback = null;


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


处理 GeoLocation Request 和 Assign value 给回调,以便在授予权限后能够使用它们

      myWebView.setWebChromeClient(new WebChromeClient(){
@Override
public void onGeolocationPermissionsShowPrompt(final String origin,
final GeolocationPermissions.Callback callback) {


int permissionCheckFineLocation = ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION);
if (permissionCheckFineLocation!= PackageManager.PERMISSION_GRANTED) {
mGeoLocationCallback=callback;
mGeoLocationRequestOrigin=origin;
//requesting permission
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 123);
}


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


}
});

一旦接收到权限,使用原点调用回调

    @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults[0]== PackageManager.PERMISSION_GRANTED){
//you have the permission now.
if(requestCode==123) {
mGeoLocationCallback.invoke(mGeoLocationRequestOrigin, true, false);
}
}