如何在运行时请求位置权限

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 31compileSdk 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 10Android Q 中的位置权限隐私更改。

如果用户想在后台访问他们当前的位置,我们必须定义额外的 ACCESS_BACKGROUND_LOCATION 权限,因此用户需要在requestPermission() 中授予权限runtime p>

如果我们使用低于 Android 10 的设备,则 ACCESS_BACKGROUND_LOCATION 权限 自动允许 使用 ACCESS_FINE_LOCATIONACCESS_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 的手机中请求和/或更改它们?

低于 M 的 android 版本的 Android 运行时权限?

程序启动时如何请求管理员权限?

Android 5.1 上的位置权限

请求运行时权限时检查 Android 版本