Геолокация веб-просмотра Android
Я должен получить местоположение пользователя в WebView
, Я делаю это со следующим Javascript:
function getLocation() {
navigator.geolocation.getCurrentPosition(displayLocation, handleError);
}
Но всплывающее окно запроса разрешения никогда не открывается.
Я установил эти настройки:
ws.setJavaScriptEnabled(true);
ws.setGeolocationEnabled(true);
ws.setJavaScriptCanOpenWindowsAutomatically(true);
Как правильно получить доступ к местоположению пользователя из WebView
?
8 ответов
- JavaScript должен быть включен в
WebView
, с помощьюWebSettings.setJavaScriptEnabled(true);
- Приложение требует разрешения
ACCESS_FINE_LOCATION
WebView
должен использовать обычайWebChromeClient
который реализуетWebChromeClient.onGeolocationPermissionsShowPrompt()
, Этот метод вызываетсяWebView
получить разрешение на раскрытие местоположения пользователя в JavaScript. (В случае браузера мы показываем пользователю подсказку.) Реализация по умолчанию ничего не делает, поэтому разрешение никогда не получается, а местоположение никогда не передается в JavaScript. Простая реализация, которая всегда дает разрешение...webView.setWebChromeClient(new WebChromeClient() { public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { callback.invoke(origin, true, false); } });
Geolocation использует базы данных для сохранения кэшированных позиций и разрешений между сеансами. Расположение базы данных задается с помощью WebSettings.setGeolocationDatabasePath(...)
, Если местоположение базы данных не задано, постоянное хранилище будет недоступно, но в противном случае геолокация продолжит работать правильно. Чтобы установить расположение баз данных, используйте...
webView.getSettings().setGeolocationDatabasePath( context.getFilesDir().getPath() );
Диалог для принятия или отклонения местоположения пользователя разработан программистом:D. Как сказал Крис Кэшвелл, вы просто используете обратный вызов:
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);
}
}
В некоторых случаях HTML5 требует использования хранилища, необходимо включить некоторые свойства, чтобы веб-просмотр имел полный доступ для нормальной работы.
// HTML5 API flags
webView.getSettings().setAppCacheEnabled(true);
webView.getSettings().setDatabaseEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
Делясь своим классом Рабочей деятельности, это полное решение, которое может продемонстрировать
- Отображение диалогового окна загрузки во время загрузки веб-страницы
- Спросите разрешения у зефира и выше
- Обрабатывать ошибку веб-страницы
- Проверьте подключение к интернету и откройте страницу настроек
- Обработка разрешения геолокации с и без диалога
Надеюсь, это сэкономит кому-то время
/**
* 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;
}
}
Вы декларируете это разрешение в своем манифесте?
<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" />
Я недавно столкнулся с такой ситуацией и сделал следующие шаги для ее достижения:
Шаг 1. Добавьте разрешения в свой 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. Создайте действие, которое будет содержать WebView
а также ProgressBar
(в моем случае)
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)
}
}
}
}
}
}
Это пример показа диалогового окна с предупреждением для предоставления пользователю разрешения использовать его / ее местоположение:
@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();
}
вам нужно динамически запрашивать разрешение, когда местоположение запрашивается из Webview
Убедитесь, что вы добавили 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;
...................................................
Обрабатывать запрос геолокации и назначать значение обратным вызовам, чтобы иметь возможность использовать их после предоставления разрешения.
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);
}
}
Публикация в качестве нового ответа для обновленного Android со всем в одном сообщении, потому что вам больше не нужно использовать
setWebChromeClient
.
С Android 6+ вы просто запрашиваете разрешения GPS во время выполнения, используя
ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 123);
.