如何在运行时请求位置权限
Posted
技术标签:
【中文标题】如何在运行时请求位置权限【英文标题】:How to request Location Permission at runtime 【发布时间】:2017-03-01 17:44:56 【问题描述】:在清单文件中,我添加了粗略和精细的权限,当我在装有 android 6 的设备上运行时,什么也没有发生!我试试 一切,但无法获取位置更新...
我做错了什么?
public class MainActivity extends AppCompatActivity implements LocationListener
LocationManager locationManager;
String provider;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
provider = locationManager.getBestProvider(new Criteria(), false);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
Location location = locationManager.getLastKnownLocation(provider);
if (location != null)
Log.i("Location Info", "Location achieved!");
else
Log.i("Location Info", "No location :(");
@Override
protected void onResume()
super.onResume();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
locationManager.requestLocationUpdates(provider, 400, 1, this);
@Override
protected void onPause()
super.onPause();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
locationManager.removeUpdates(this);
@Override
public void onLocationChanged(Location location)
Double lat = location.getLatitude();
Double lng = location.getLongitude();
Log.i("Location info: Lat", lat.toString());
Log.i("Location info: Lng", lng.toString());
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
@Override
public void onProviderEnabled(String provider)
@Override
public void onProviderDisabled(String provider)
public void getLocation(View view)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
Location location = locationManager.getLastKnownLocation(provider);
onLocationChanged(location);
【问题讨论】:
【参考方案1】:您需要在运行时实际请求位置权限(请注意代码中的 cmets 说明了这一点)。
更新了 Kotlin 和 API 31 (Android 12) 的后台位置:
从 API 30 开始,后台位置必须单独请求。
此示例使用targetSdk 31
和compileSdk 31
。
请注意,可以将后台位置请求与 API 29 上的主要位置请求捆绑在一起,但要做到这一点,您需要维护三个单独的代码路径。
将其分解为对 29 及以上的请求更容易。
确保在应用级别 gradle(撰写本文时为 18.0.0)中包含最新的定位服务:
implementation "com.google.android.gms:play-services-location:18.0.0"
在清单中包含位置权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
这是一个简化的示例,可以处理大多数情况,但以简化的方式。在用户选择“不再询问”的情况下,在下次启动应用程序时,它将打开用户手动启用权限的设置。
完整的活动代码:
import android.Manifest
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
class MainActivity : AppCompatActivity()
private var fusedLocationProvider: FusedLocationProviderClient? = null
private val locationRequest: LocationRequest = LocationRequest.create().apply
interval = 30
fastestInterval = 10
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
maxWaitTime = 60
private var locationCallback: LocationCallback = object : LocationCallback()
override fun onLocationResult(locationResult: LocationResult)
val locationList = locationResult.locations
if (locationList.isNotEmpty())
//The last location in the list is the newest
val location = locationList.last()
Toast.makeText(
this@MainActivity,
"Got Location: " + location.toString(),
Toast.LENGTH_LONG
)
.show()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationProvider = LocationServices.getFusedLocationProviderClient(this)
checkLocationPermission()
override fun onResume()
super.onResume()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
)
fusedLocationProvider?.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
override fun onPause()
super.onPause()
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
)
fusedLocationProvider?.removeLocationUpdates(locationCallback)
private fun checkLocationPermission()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
)
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
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.
AlertDialog.Builder(this)
.setTitle("Location Permission Needed")
.setMessage("This app needs the Location permission, please accept to use location functionality")
.setPositiveButton(
"OK"
) _, _ ->
//Prompt the user once explanation has been shown
requestLocationPermission()
.create()
.show()
else
// No explanation needed, we can request the permission.
requestLocationPermission()
else
checkBackgroundLocation()
private fun checkBackgroundLocation()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
)
requestBackgroundLocationPermission()
private fun requestLocationPermission()
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
),
MY_PERMISSIONS_REQUEST_LOCATION
)
private fun requestBackgroundLocationPermission()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
MY_PERMISSIONS_REQUEST_BACKGROUND_LOCATION
)
else
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_PERMISSIONS_REQUEST_LOCATION
)
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
)
when (requestCode)
MY_PERMISSIONS_REQUEST_LOCATION ->
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
// permission was granted, yay! Do the
// location-related task you need to do.
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
fusedLocationProvider?.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
// Now check background location
checkBackgroundLocation()
else
// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show()
// Check if we are in a state where the user has denied the permission and
// selected Don't ask again
if (!ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", this.packageName, null),
),
)
return
MY_PERMISSIONS_REQUEST_BACKGROUND_LOCATION ->
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
// permission was granted, yay! Do the
// location-related task you need to do.
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
fusedLocationProvider?.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
Toast.makeText(
this,
"Granted Background Location Permission",
Toast.LENGTH_LONG
).show()
else
// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show()
return
companion object
private const val MY_PERMISSIONS_REQUEST_LOCATION = 99
private const val MY_PERMISSIONS_REQUEST_BACKGROUND_LOCATION = 66
在 Android 10 (API 29) 上,用户可以选择在初始位置请求后授予后台位置:
在 Android 12 (API 31) 上它会执行相同的操作,但界面不同:
Java 中的原始答案:
这里是请求位置权限的测试和工作代码。
把这段代码放到Activity中:
public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;
public boolean checkLocationPermission()
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED)
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
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.
new AlertDialog.Builder(this)
.setTitle(R.string.title_location_permission)
.setMessage(R.string.text_location_permission)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
@Override
public void onClick(DialogInterface dialogInterface, int i)
//Prompt the user once explanation has been shown
ActivityCompat.requestPermissions(MainActivity.this,
new String[]Manifest.permission.ACCESS_FINE_LOCATION,
MY_PERMISSIONS_REQUEST_LOCATION);
)
.create()
.show();
else
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this,
new String[]Manifest.permission.ACCESS_FINE_LOCATION,
MY_PERMISSIONS_REQUEST_LOCATION);
return false;
else
return true;
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults)
switch (requestCode)
case MY_PERMISSIONS_REQUEST_LOCATION:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED)
// permission was granted, yay! Do the
// location-related task you need to do.
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED)
//Request location updates:
locationManager.requestLocationUpdates(provider, 400, 1, this);
else
// permission denied, boo! Disable the
// functionality that depends on this permission.
return;
然后调用onCreate()
中的checkLocationPermission()
方法:
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//.........
checkLocationPermission();
然后您可以完全按照问题中的方式使用onResume()
和onPause()
。
这是一个更简洁的精简版:
@Override
protected void onResume()
super.onResume();
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED)
locationManager.requestLocationUpdates(provider, 400, 1, this);
@Override
protected void onPause()
super.onPause();
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED)
locationManager.removeUpdates(this);
【讨论】:
什么是“提供者”? 'MainActivity' 是请求权限的活动吗? @AbdullahUmer 你可以看到provider
在问题中是如何使用的:String provider = locationManager.getBestProvider(new Criteria(), false);
是的,MainActivity 是此示例中此代码所在的 Activity。查看问题中的 MainActivity 了解更多上下文。
很好的例子。谢谢!。注意问题.. 确保您使用的是android.Manifest
。 Android Studio 似乎默认为 my.app.package.Manifest
。
@hexicle 你是对的。我只是按原样运行这段代码,它肯定不能正常工作。最简单的解决方法是在onCreate()
中调用checkLocationPermission()
,所以我修改了答案来做到这一点。使用此解决方案,您可以完全按照问题中的方式使用onResume()
和onPause()
。感谢您提醒我注意这一点,干杯!【参考方案2】:
Google 创建了一个用于轻松管理权限的库。它叫EasyPermissions
这是一个使用这个库请求位置权限的简单示例。
public class MainActivity extends AppCompatActivity
private final int REQUEST_LOCATION_PERMISSION = 1;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestLocationPermission();
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
@AfterPermissionGranted(REQUEST_LOCATION_PERMISSION)
public void requestLocationPermission()
String[] perms = Manifest.permission.ACCESS_FINE_LOCATION;
if(EasyPermissions.hasPermissions(this, perms))
Toast.makeText(this, "Permission already granted", Toast.LENGTH_SHORT).show();
else
EasyPermissions.requestPermissions(this, "Please grant the location permission", REQUEST_LOCATION_PERMISSION, perms);
@AfterPermissionsGranted(REQUEST_CODE)
用于表示请求码为REQUEST_CODE
的权限请求被授予后需要执行的方法。
上述情况,如果用户授予访问位置服务的权限,则调用方法requestLocationPermission()
方法。因此,该方法既可用作回调,又可用作请求权限的方法。
您也可以针对授予的权限和拒绝的权限实现单独的回调。 github页面中有说明。
【讨论】:
提这个库很有帮助,非常感谢【参考方案3】:Android 10 或 Android Q 中的位置权限隐私更改。
如果用户想在后台访问他们当前的位置,我们必须定义额外的 ACCESS_BACKGROUND_LOCATION
权限,因此用户需要在requestPermission()
中授予权限runtime p>
如果我们使用低于 Android 10 的设备,则 ACCESS_BACKGROUND_LOCATION
权限 自动允许 使用 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
权限
如果我们不在清单文件中指定ACCESS_BACKGROUND_LOCATION
,这种表格格式可能很容易理解。
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> // here we defined ACCESS_BACKGROUND_LOCATION for Android 10 device
MainActivity.java
在onCreate()
或onResume()
中致电checkRunTimePermission()
public void checkRunTimePermission()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED)
gpsTracker = new GPSTracker(context);
else
requestPermissions(new String[]Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
10);
else
gpsTracker = new GPSTracker(context); //GPSTracker is class that is used for retrieve user current location
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 10)
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
gpsTracker = new GPSTracker(context);
else
if (!ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, Manifest.permission.ACCESS_FINE_LOCATION))
// If User Checked 'Don't Show Again' checkbox for runtime permission, then navigate user to Settings
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle("Permission Required");
dialog.setCancelable(false);
dialog.setMessage("You have to Allow permission to access user location");
dialog.setPositiveButton("Settings", new DialogInterface.OnClickListener()
@Override
public void onClick(DialogInterface dialog, int which)
Intent i = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package",
context.getPackageName(), null));
//i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(i, 1001);
);
AlertDialog alertDialog = dialog.create();
alertDialog.show();
//code for deny
@Override
public void startActivityForResult(Intent intent, int requestCode)
super.startActivityForResult(intent, requestCode);
switch (requestCode)
case 1001:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED)
gpsTracker = new GPSTracker(context);
if (gpsTracker.canGetLocation())
latitude = gpsTracker.getLatitude();
longitude = gpsTracker.getLongitude();
else
requestPermissions(new String[]Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION,10);
break;
default:
break;
build.gradle(应用级别)
android
compileSdkVersion 29 //should be >= 29
buildToolsVersion "29.0.2"
useLibrary 'org.apache.http.legacy'
defaultConfig
applicationId "com.example.runtimepermission"
minSdkVersion 21
targetSdkVersion 29 //should be >= 29
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
Here你可以找到GPSTracker.java
文件代码
【讨论】:
你好,谢谢你的回答。您知道如何确定用户选择了“始终允许”选项还是“一次性使用”选项?? @MohammadZarei 很高兴为您提供帮助,如果答案有帮助,请支持我的答案,是的,您可以跟踪记录,有onRequestPermissionsResult()
方法,其中有一个参数int[] grantResults
你必须写 for loop for (int grantResult : grantResults) Log.d("TAG", "onRequestPermissionsResult: " + grantResult); 如果 grantResult 0 然后权限被授予 else -1 然后权限被拒绝。
在“always allow”和“one-time-use”都授予了权限,但是如果用户选择了“one-time-use”,下次他回来我们的应用程序时,必须给我们相同的权限。我想知道我们是否可以检查他是否永久允许我们?
@MohammadZarei 所以你必须在sharedPreference
中存储标志onRequestPermissionsResult()
。当用户选择“一次性使用”权限时,您必须将标志设置为 true
(默认设置为 false),在 MainActivity
中,您可以从偏好中获得价值并检查它是真还是假跨度>
是的,但我找不到一种方法来说明用户选择“一次性”还是总是。这就是我要找的。span>
【参考方案4】:
从 MainActivity 检查此代码
// Check location permission is granted - if it is, start
// the service, otherwise request the permission
fun checkOrAskLocationPermission(callback: () -> Unit)
// Check GPS is enabled
val lm = getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER))
Toast.makeText(this, "Please enable location services", Toast.LENGTH_SHORT).show()
buildAlertMessageNoGps(this)
return
// Check location permission is granted - if it is, start
// the service, otherwise request the permission
val permission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
if (permission == PackageManager.PERMISSION_GRANTED)
callback.invoke()
else
// callback will be inside the activity's onRequestPermissionsResult(
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSIONS_REQUEST
)
加
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSIONS_REQUEST)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
// Permission ok. Do work.
加
fun buildAlertMessageNoGps(context: Context)
val builder = AlertDialog.Builder(context);
builder.setMessage("Your GPS is disabled. Do you want to enable it?")
.setCancelable(false)
.setPositiveButton("Yes") _, _ -> context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
.setNegativeButton("No") dialog, _ -> dialog.cancel();
val alert = builder.create();
alert.show();
用法
checkOrAskLocationPermission()
// Permission ok. Do work.
【讨论】:
【参考方案5】:以下是我在 28 及以下、29 和 30 上请求前台和后台位置权限的解决方案。API 之间的差异很细微但很重要。
API 28 及以下,系统将前台和后台位置权限视为相同。如果您授予位置权限,则应用程序被隐式授予。
API 29,您可以同时请求前台和后台权限。
API 30,您必须先申请前台位置权限,然后才能在授予前台位置权限后才申请后台位置权限。如果您同时请求前台和后台权限,那么系统将忽略该请求。另一个区别是用户必须在应用程序位置权限设置中允许后台位置权限,而不是通过系统对话框。
下面的解决方案只有在用户接受了前台和后台位置跟踪后才开始指定的操作(例如后台位置跟踪):
LocationPermissionUtil.kt
private const val REQUEST_CODE_FOREGROUND = 1
private const val REQUEST_CODE_FOREGROUND_AND_BACKGROUND = 2
object LocationPermissionUtil
private fun Context.isPermissionGranted(permission: String): Boolean = ActivityCompat
.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
private val Context.isFineLocationPermissionGranted
get() = isPermissionGranted(
Manifest.permission.ACCESS_FINE_LOCATION
)
private val Context.isBackgroundPermissionGranted
get() = when
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED
else -> isFineLocationPermissionGranted
private val Context.isFineAndBackgroundLocationPermissionsGranted
get() = isFineLocationPermissionGranted && isBackgroundPermissionGranted
private fun Activity.checkFineLocationPermission()
if (isFineLocationPermissionGranted) return
val shouldShowFineLocationPermissionRationale = ActivityCompat
.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
if (shouldShowFineLocationPermissionRationale)
presentAlertDialog(
R.string.dialog_fine_location_rationale_title,
R.string.dialog_fine_location_rationale_description,
R.string.yes,
)
requestLocationPermissions()
else
requestLocationPermissions()
private fun Activity.requestLocationPermissions() =
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
requestFineLocationAndBackground()
else
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE_FOREGROUND
)
@TargetApi(29)
private fun Activity.requestFineLocationAndBackground()
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
REQUEST_CODE_FOREGROUND_AND_BACKGROUND
)
@TargetApi(29)
private fun Activity.checkBackgroundLocationPermission()
if (isFineAndBackgroundLocationPermissionsGranted) return
val shouldShowBackgroundPermissionRationale = ActivityCompat
.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
if (shouldShowBackgroundPermissionRationale)
presentAlertDialog(
R.string.dialog_background_location_rationale_title,
R.string.dialog_background_location_rationale_description,
R.string.yes,
)
requestFineLocationAndBackground()
else
requestFineLocationAndBackground()
fun checkLocationPermissions(activity: Activity, action: () -> Unit) = with(activity)
if (isFineAndBackgroundLocationPermissionsGranted)
action()
return
checkFineLocationPermission()
fun onRequestPermissionsResult(
activity: Activity,
requestCode: Int,
action: () -> Unit
) = with(activity)
when (requestCode)
REQUEST_CODE_FOREGROUND ->
if (!isFineLocationPermissionGranted)
checkFineLocationPermission()
return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
checkBackgroundLocationPermission()
else
action()
REQUEST_CODE_FOREGROUND_AND_BACKGROUND ->
if (!isFineLocationPermissionGranted)
checkFineLocationPermission()
return
if (isBackgroundPermissionGranted)
action()
else
checkBackgroundLocationPermission()
活动:
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
LocationPermissionUtil.checkLocationPermissions(this, this::onLocationPermissionsGranted)
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
LocationPermissionUtil.onRequestPermissionsResult(
this,
requestCode,
this::onLocationPermissionsGranted
)
private fun onLocationPermissionsGranted()
Toast.makeText(
this,
"Background location permitted, starting location tracking...",
Toast.LENGTH_LONG
).show()
【讨论】:
你好@masterwok 什么是presentAlertDialog? Android Studio 无法解决此问题。【参考方案6】:此代码对我有用。我也处理过“Never Ask Me”案
在 AndroidManifest.xml 中
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
在 build.gradle(模块:app)中
dependencies
....
implementation "com.google.android.gms:play-services-location:16.0.0"
这是 CurrentLocationManager.kt
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.IntentSender
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.os.CountDownTimer
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.util.Log
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.LocationSettingsRequest
import com.google.android.gms.location.LocationSettingsStatusCodes
import java.lang.ref.WeakReference
object CurrentLocationManager : LocationListener
const val REQUEST_CODE_ACCESS_LOCATION = 123
fun checkLocationPermission(activity: Activity)
if (ContextCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
)
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE_ACCESS_LOCATION
)
else
Thread(Runnable
// Moves the current Thread into the background
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND)
//
requestLocationUpdates(activity)
).start()
/**
* be used in HomeActivity.
*/
const val REQUEST_CHECK_SETTINGS = 55
/**
* The number of millis in the future from the call to start().
* until the countdown is done and onFinish() is called.
*
*
* It is also the interval along the way to receive onTick(long) callbacks.
*/
private const val TWENTY_SECS: Long = 20000
/**
* Timer to get location from history when requestLocationUpdates don't return result.
*/
private var mCountDownTimer: CountDownTimer? = null
/**
* WeakReference of current activity.
*/
private var mWeakReferenceActivity: WeakReference<Activity>? = null
/**
* user's location.
*/
var currentLocation: Location? = null
@Synchronized
fun requestLocationUpdates(activity: Activity)
if (mWeakReferenceActivity == null)
mWeakReferenceActivity = WeakReference(activity)
else
mWeakReferenceActivity?.clear()
mWeakReferenceActivity = WeakReference(activity)
//create location request: https://developer.android.com/training/location/change-location-settings.html#prompt
val mLocationRequest = LocationRequest()
// Which your app prefers to receive location updates. Note that the location updates may be
// faster than this rate, or slower than this rate, or there may be no updates at all
// (if the device has no connectivity)
mLocationRequest.interval = 20000
//This method sets the fastest rate in milliseconds at which your app can handle location updates.
// You need to set this rate because other apps also affect the rate at which updates are sent
mLocationRequest.fastestInterval = 10000
mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
//Get Current Location Settings
val builder = LocationSettingsRequest.Builder().addLocationRequest(mLocationRequest)
//Next check whether the current location settings are satisfied
val client = LocationServices.getSettingsClient(activity)
val task = client.checkLocationSettings(builder.build())
//Prompt the User to Change Location Settings
task.addOnSuccessListener(activity)
Log.d("CurrentLocationManager", "OnSuccessListener")
// All location settings are satisfied. The client can initialize location requests here.
// If it's failed, the result after user updated setting is sent to onActivityResult of HomeActivity.
val activity1 = mWeakReferenceActivity?.get()
if (activity1 != null)
startRequestLocationUpdate(activity1.applicationContext)
task.addOnFailureListener(activity) e ->
Log.d("CurrentLocationManager", "addOnFailureListener")
val statusCode = (e as ApiException).statusCode
when (statusCode)
CommonStatusCodes.RESOLUTION_REQUIRED ->
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try
val activity1 = mWeakReferenceActivity?.get()
if (activity1 != null)
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
val resolvable = e as ResolvableApiException
resolvable.startResolutionForResult(
activity1, REQUEST_CHECK_SETTINGS
)
catch (sendEx: IntentSender.SendIntentException)
// Ignore the error.
sendEx.printStackTrace()
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE ->
// Location settings are not satisfied. However, we have no way
// to fix the settings so we won't show the dialog.
fun startRequestLocationUpdate(appContext: Context)
val mLocationManager = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (ActivityCompat.checkSelfPermission(
appContext.applicationContext,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
//Utilities.showProgressDialog(mWeakReferenceActivity.get());
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
mLocationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 10000, 0f, this
)
else
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 10000, 0f, this
)
/*Timer to call getLastKnownLocation() when requestLocationUpdates don 't return result*/
countDownUpdateLocation()
override fun onLocationChanged(location: Location?)
if (location != null)
stopRequestLocationUpdates()
currentLocation = location
override fun onStatusChanged(provider: String, status: Int, extras: Bundle)
override fun onProviderEnabled(provider: String)
override fun onProviderDisabled(provider: String)
/**
* Init CountDownTimer to to get location from history when requestLocationUpdates don't return result.
*/
@Synchronized
private fun countDownUpdateLocation()
mCountDownTimer?.cancel()
mCountDownTimer = object : CountDownTimer(TWENTY_SECS, TWENTY_SECS)
override fun onTick(millisUntilFinished: Long)
override fun onFinish()
if (mWeakReferenceActivity != null)
val activity = mWeakReferenceActivity?.get()
if (activity != null && ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
val location = (activity.applicationContext
.getSystemService(Context.LOCATION_SERVICE) as LocationManager)
.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
stopRequestLocationUpdates()
onLocationChanged(location)
else
stopRequestLocationUpdates()
else
mCountDownTimer?.cancel()
mCountDownTimer = null
.start()
/**
* The method must be called in onDestroy() of activity to
* removeUpdateLocation and cancel CountDownTimer.
*/
fun stopRequestLocationUpdates()
val activity = mWeakReferenceActivity?.get()
if (activity != null)
/*if (ActivityCompat.checkSelfPermission(activity,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) */
(activity.applicationContext
.getSystemService(Context.LOCATION_SERVICE) as LocationManager).removeUpdates(this)
/**/
mCountDownTimer?.cancel()
mCountDownTimer = null
在 MainActivity.kt 中
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
...
CurrentLocationManager.checkLocationPermission(this@LoginActivity)
override fun onDestroy()
CurrentLocationManager.stopRequestLocationUpdates()
super.onDestroy()
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == CurrentLocationManager.REQUEST_CODE_ACCESS_LOCATION)
if (grantResults[0] == PackageManager.PERMISSION_DENIED)
//denied
val builder = AlertDialog.Builder(this)
builder.setMessage("We need permission to use your location for the purpose of finding friends near you.")
.setTitle("Device Location Required")
.setIcon(com.eswapp.R.drawable.ic_info)
.setPositiveButton("OK") _, _ ->
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
//only deny
CurrentLocationManager.checkLocationPermission(this@LoginActivity)
else
//never ask again
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivityForResult(intent, CurrentLocationManager.REQUEST_CHECK_SETTINGS)
.setNegativeButton("Ask Me Later") _, _ ->
// Create the AlertDialog object and return it
val dialog = builder.create()
dialog.show()
else if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
CurrentLocationManager.requestLocationUpdates(this)
//Forward Login result to the CallBackManager in OnActivityResult()
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
when (requestCode)
//case 1. After you allow the app access device location, Another dialog will be displayed to request you to turn on device location
//case 2. Or You chosen Never Ask Again, you open device Setting and enable location permission
CurrentLocationManager.REQUEST_CHECK_SETTINGS -> when (resultCode)
RESULT_OK ->
Log.d("REQUEST_CHECK_SETTINGS", "RESULT_OK")
//case 1. You choose OK
CurrentLocationManager.startRequestLocationUpdate(applicationContext)
RESULT_CANCELED ->
Log.d("REQUEST_CHECK_SETTINGS", "RESULT_CANCELED")
//case 1. You choose NO THANKS
//CurrentLocationManager.requestLocationUpdates(this)
//case 2. In device Setting screen: user can enable or not enable location permission,
// so when user back to this activity, we should re-call checkLocationPermission()
CurrentLocationManager.checkLocationPermission(this@LoginActivity)
else ->
//do nothing
else ->
super.onActivityResult(requestCode, resultCode, data)
【讨论】:
老兄这个问题有 JAVA 和 android 标签...但是 Kotlin 人总是要让你知道【参考方案7】:在清单文件中定义它之后,比原生解决方案更友好的替代方案是使用 Aaper:https://github.com/LikeTheSalad/aaper,如下所示:
@EnsurePermissions(permissions = [Manifest.permission.ACCESS_FINE_LOCATION])
private fun scanForLocation()
// Your code that needs the location permission granted.
免责声明,我是 Aaper 的创造者。
【讨论】:
【参考方案8】:寻找更简单的代码?试试这个!
if (ContextCompat.checkSelfPermission(LoginActivity.this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(LoginActivity.this,
new String[]Manifest.permission.ACCESS_FINE_LOCATION, REQUEST_CALL);
另外,不要忘记请求权限
【讨论】:
以上是关于如何在运行时请求位置权限的主要内容,如果未能解决你的问题,请参考以下文章
如何在 android Unity 应用程序中禁用 android 运行时位置权限提示
权限 - 如何在运行时运行 MIUI 的手机中请求和/或更改它们?