"Allow all the time" Android SDK 29 中未出现位置提示



我在 SDK 29 中无法收到位置的"一直允许"提示。我已经在清单中设置了这些权限:

<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

并请求用户在运行时允许这些权限。但它仅返回"当应用打开时"和"拒绝"选项。

关于如何在 SDK 29 中显示它的任何想法。

要在运行 Android 10(API 级别 29(或更高版本的设备上后台访问该位置,您还需要在清单文件中使用以下权限

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

请参阅以下链接以获取更多信息

https://developer.android.com/training/location/permissions?hl=fr

在清单中添加权限ACCESS_BACKGROUND_LOCATION。需要在 android 10 及更高版本上显示始终允许选项。

请参阅 https://developer.android.com/training/location/background#evaluate 中的第二点

要在具有android API 级别 29 或更高级别的设备后台访问位置信息,您还需要在清单文件中添加以下权限。

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

您可以保留所有三个,也可以删除ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION但其中一个必须存在于清单文件中。

添加这些权限后,如果我们可以通过以下代码块访问所有这些权限,则需要在某个地方签入代码。如果没有,您需要通过构建所有权限的数组并使用基于整数的请求代码来请求权限:

if (requireContext().checkSelfPermission(PERMISSION_NAME_1) != PackageManager.PERMISSION_DENIED && requireContext().checkSelfPermission(PERMISSION_NAME_2) != PackageManager.PERMISSION_DENIED ...) {
requestPermissions(@NonNull String[] permissions, int requestCode)

我发现仅在清单中添加权限是不够的。我必须为用户提供一个选项,以便在启动时授予它,就像我需要为ACCESS_FINE_LOCATION所做的那样。有趣的是,当我检查后台权限是否已在应用程序中授予时,它永远不会给出 false。但是如果我不检查,我就不会得到"一直允许"选项。这是在Android Pixel 2版本10上

它仅在 10+ 版本的 Android 中是必需的。请求这两个权限为用户提供了对话框中的选择...当然,用户可能不会选择"一直"。对此无能为力!

添加我的代码有点尴尬,因为我知道它很糟糕。其他人肯定可以改进它。我找不到避免两次循环访问权限的方法。

/**
* This method lists all the permissions needed and gives reasons for each of them in a single
* dialog. When the user presses ok, Android will popup separate dialogs for each permission
* that is needed. In the case of location, the "Allow all the time" gives background permission
* "Allow only while the app is running gives just the location permission while in foreground.
* @return false if permissions are to be requested, true if they have already been granted.
*
* In Android 11 Build 30 things are a lot different. One has to ask for location first, approve
* the 'while the app is running' option and then upon return in the Activity result, ask for
* the background. Thus the new variable 'secondPassR' to handle this case. After the background
* one comes back to the Activity result and then one can go into the main activity.
*/
static boolean secondPassR = false;
private static final int REQUEST_CODE_MULTIPLE_PERMISSIONS = 57;
private boolean requestPermissions(Activity activity)
{
Log.v(TAG, "requestPermissions() called");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
final List<String> permissionsList = new ArrayList<>();
final List<String> reasonList = new ArrayList<>();
if(!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION, activity))
{
reasonList.add("LOCATION PERMISSION: Needs needs to be granted for the Bluetooth " +
" LE scanner to discover devices!nn");
}
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) || secondPassR)
{
if (!addPermission(permissionsList, Manifest.permission.ACCESS_BACKGROUND_LOCATION, activity))
{
reasonList.add("BACKGROUND PERMISSION: Needs to be granted for the Bluetooth " +
"LE Scanner to run in the background.nn");
}
}
if (permissionsList.size() > 0)
{
if (reasonList.size() > 0)
{
// Need Rationale
StringBuilder message = new StringBuilder(reasonList.get(0));
for (int i = 1; i < reasonList.size(); i++)
{
message.append(" ").append(reasonList.get(i));
}
final androidx.appcompat.app.AlertDialog.Builder builder =
new androidx.appcompat.app.AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.Theme_AppCompat_Light));
builder.setTitle("Demo needs the following permissions:");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
builder.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY));
}
else
{
builder.setMessage(Html.fromHtml(message.toString()));
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(dialog -> {
Log.v(TAG, "Requesting permissions");
activity.requestPermissions(permissionsList.toArray(new String[0]),  // newer Java recommended
REQUEST_CODE_MULTIPLE_PERMISSIONS);
});
builder.show();
return false;
}
activity.requestPermissions(permissionsList.toArray(new String[0]),   // newer Java recommended
REQUEST_CODE_MULTIPLE_PERMISSIONS);
}
else
{
return true;
}
}
else
{
return true;
}
return false;
}
@TargetApi(23)
private boolean addPermission(List<String> permissionsList, String permission, Activity activity)
{
Log.v(TAG,
"addPermission() called with: " + "permissionsList = " +
"[" + permissionsList + "], permission = [" + permission + "]");
if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED)
{
permissionsList.add(permission);
// Check for Rationale Option
return activity.shouldShowRequestPermissionRationale(permission);
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
String permission = "";
Log.v(TAG,
"onRequestPermissionsResult() called with: " + "requestCode = [" + requestCode +
"], permissions = [" + Arrays.toString(permissions) + "]," +
" grantResults = [" + Arrays.toString(grantResults) + "]");
if (requestCode == REQUEST_CODE_MULTIPLE_PERMISSIONS)
{
for (int i = 0; i < permissions.length; i++)
{
switch (permissions[i])
{
case Manifest.permission.ACCESS_FINE_LOCATION:
if (grantResults[i] == PackageManager.PERMISSION_GRANTED)
{
Log.d(TAG, "H@H: onRequestPermissionsResult: FINE LOCATION PERMISSION");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
Log.d(TAG, "H@H: Now requesting BACKGROUND PERMISSION for version 11+");
secondPassR = true;
requestPermissions(thisActivity);
return;
}
}
break;
case Manifest.permission.ACCESS_BACKGROUND_LOCATION:
if (grantResults[i] == PackageManager.PERMISSION_GRANTED)
{
Log.d(TAG, "H@H: onRequestPermissionsResult: BACKGROUND PERMISSION");
}
break;
}
}
}
Log.d(TAG, "Starting primary activity");
secondPassR = false;
startActivityForResult(new Intent(context, PchaDemoPhg_Activity.class), EXIT_QUIT);
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener)
{
new AlertDialog.Builder(this)
.setMessage(message)
.setCancelable(false)
.setPositiveButton("OK", okListener)
.create()
.show();
}

==============更新安卓 12 和活动结果启动器 ========

在Android 12中,出现了一组新的权限,我们不再需要请求位置权限即可使用BTLE扫描仪。还有一种新的方法来处理活动结果,那就是ActivityResultLauncher,其中一个内置功能是运行时权限。以下是我现在用来从 ANdroid 6+ 请求蓝牙扫描仪和后台权限的内容。我使用单权限启动器在每个请求之前放置一个解释对话框。用户体验比我以前好得多。(我包括我的字符串资源。

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import java.util.List;

/*
resource strings used:
<string name="permissions_connect_12">&lt;p&gt;This app needs permission to connect to Bluetooth devices&lt;/p&gt; </string>
<string name="permissions_scan_12">&lt;p&gt;To search for Bluetooth devices this app needs permission to use the BLE scanner.&lt;/p&gt; </string>
<string name="permissions_overlay_11">READ CAREFULLY! Setting permissions in Android 11+ is much more complicated!nn
This app wants to popup a dialog when it discovers a PHD that it can work with.nn
It needs Window-Overlay permissions to do that. Android will start a Settings system activity where it will list all the installed applications.nn
You will need to scroll down to Health@Home PHG and tap it. That will bring you to the original pre-11 Settings system overlay permission activity.
Give the permission and use the back arrow to exit. You will need use the back arrow once more to return to Health@Home.</string>
<string name="permissions_location">&lt;p&gt;&lt;font color="#007700"&gt;&lt;b&gt;LOCATION PERMISSION:&lt;/b&gt;&lt;/font&gt; Needs to be granted in order for this app to use the Bluetooth LE scanner.
The scanner is needed to discover BLE health devices and know what they are.&lt;/p&gt;
&lt;p&gt;&lt;font color="red"&gt;&lt;b&gt;This app does NOT use location information or expose location information!&lt;/b&gt;&lt;/font&gt;
Without this permission you will only be able to work with SPP and HDP devices.&lt;/p&gt;</string>
<string name="permissions_background">&lt;p&gt;BACKGROUND PERMISSION: Needs to be granted for the Bluetooth LE Scanner to run in the background
to support re-connection without user intervention.&lt;/p&gt;
&lt;p&gt;Please select 'Allow all the time'.&lt;/p&gt;</string>
*/
public class PermissionsActivity extends AppCompatActivity
{
private final static String TAG = PermissionsActivity.class.getName();
private final static boolean isVersionS_Plus = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S); // 31
private final static boolean isVersionM_Plus = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); // 23 (Marshmallow)
private final static boolean isVersionN_Plus = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
private static final boolean isVersionO_Plus = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
private static final boolean isVersionR = (Build.VERSION.SDK_INT == Build.VERSION_CODES.R); // 30
private static final boolean isVersionR_Plus = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); // 30
private static final boolean isVersionOtoR = ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)  // 26
&& (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R));  // 30

private final List<String> permissionsList = new ArrayList<>();
private final List<String> reasonList = new ArrayList<>();
private int index = 0;  // Keeps track of what permission is being requested
private int indexMax;   // The maximum number of permissions being asked for (set below)
ActivityResultLauncher<String> activityResultLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>()
{
@Override
public void onActivityResult(Boolean result)
{
Log.d(TAG, "HH2: Permission " + permissionsList.get(index) + (result ? " granted" : "rejected"));
index++;
if (index >= indexMax)
{
handlePermissionSummary();
}
else
{
requestPermissions(index);
}
}
});
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// too lazy to make a layout; your activity very likely has one!
if (handlePermissions())
{
//setLoginScreen();  // Do what needs to be done after permissions are granted or if they already are granted
}
}
// Method checks to see if the needed permissions are granted and if they are it returns true.
// If they are not granted the method returns false but starts the process of asking the user to grant the permissions
@SuppressLint("NewApi")
private boolean handlePermissions()
{
if (isVersionS_Plus)  // Android 12 + (31+)  Completely new runtime permissions for Bluetooth in Android 12
{                     // At least one no longer has to ask for location permissions for Bluetooth completely
// confusing the user.
// Requesting BLUETOOTH_CONNECT, BLUETOOTH_SCAN, and ACCESS_BACKGROUND_LOCATION. The latter is still needed
// to use the BTLE scanner in the background.
// There is a weird bug in Android 12 with respect to the BLUETOOTH_CONNECT and BLUETOOTH_SCAN
// permissions. If you need both, regardless of what order you ask for them in, you get only one
// dialog from Android where the user grants the permission. But you have to ask for both permissions
// (if you need both). See this bug: https://issuetracker.google.com/issues/214434187
// Thus I skip the application dialog by making it empty for the second permission and just request
// the permission. That way both permissions get granted.
if (checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED)
{
permissionsList.add(Manifest.permission.BLUETOOTH_CONNECT);
reasonList.add(getString(R.string.permissions_connect_12));
}
if (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)
{
permissionsList.add(Manifest.permission.BLUETOOTH_SCAN);
reasonList.add(""); // Work-a-round. If empty, present no dialog explaining the request to the user
//reasonList.add(getString(R.string.permissions_scan_12));
}
if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
permissionsList.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
reasonList.add(getString(R.string.permissions_background));
}
indexMax = 3; // Need three permissions
}
else if (isVersionM_Plus)
{
// Need location permissions to use the BTLE Scanner. Some versions of Android after 6 require FINE location and
// some require only coarse and some both. TO minimize headache, I always ask for FINE and place COARSE location
// in the Manifest file. That gives you use of the BTLE scanner for all pre-12 versions of Android
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) // Android 6 +
{
permissionsList.add(Manifest.permission.ACCESS_FINE_LOCATION); // Require ACCESS_COARSE_LOCATION in Manifest file as well
reasonList.add(getString(R.string.permissions_location));
indexMax = 1;  // Need only one here
}
if (isVersionOtoR)  // Android 8 - 11. For these versions need BACKGROUND permission to use the scanner in the background.
{
if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
permissionsList.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
reasonList.add(getString(R.string.permissions_background));
indexMax = 2;  // Need two permissions here
}
}
}
// If no permissions are needed, return true.
if (permissionsList.size() == 0)
{
return true;
}
// Otherwise, begin the permission request sequence which involves launching permission requests.
// The process is asynchronous, so the launch returns immediately.
requestPermissions(index);
return false;  // returning false indicates that permissions have not been granted and the request process is ongoing
}
// THis method pops up an application dialog explaining to the user why the application needs the requested permission.
// When the user clicks OK, the permission request is launched. Android pops up whatever system action it dreams up to
// handle the request. Sometimes it is a dialog, and sometimes it is an activity.
// After the user responds, the result is returned in ActivityResultCallback above. The result is a boolean - true if
// granted, false if not. Within the callback, the 'index' is checked. If there are more permissions to request,
// this method is called again. If not, the summary method below is called.
// It's ugly, but it is the only way I have been able to figure out how to place an explanation dialog before each
// Android System action for the permission. Using the multiple permission approach I could not get my dialogs to
// appear before each of the Android actions.
@SuppressLint("NewApi")
private void requestPermissions(int index)
{
if (reasonList.get(index).isEmpty()) // Work-a-round for Android 12. If explanation is empty then
{
// Skip explanation dialog but still request permission. Android pops up no dialog but auto-grants permission.
activityResultLauncher.launch(permissionsList.get(index));
return;
}
// Popup a dialog explaining why the app needs this permission and perhaps what Android is going to put you
// through to grant the permission. For example, for BLUETOOTH_CONNECT/SCAN permissions Android pops up a
// dialog. But for BACKGROUND permissions, Android presents an Activity. Exiting the dialog requires different
// behavior from the user than exiting an activity.
final androidx.appcompat.app.AlertDialog.Builder builder =
new androidx.appcompat.app.AlertDialog.Builder(new ContextThemeWrapper(this, R.style.Theme_AppCompat_Light));
builder.setTitle("Health@Home needs the following permission:");
if (isVersionN_Plus)
{
builder.setMessage(Html.fromHtml(reasonList.get(index), Html.FROM_HTML_MODE_LEGACY));
}
else
{
builder.setMessage(Html.fromHtml(reasonList.get(index)));
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(dialog ->
{
Log.v(TAG, "HH2: Requesting permissions");
activityResultLauncher.launch(permissionsList.get(index));
});
builder.show();
}
// THis method just summarizes the results of the permissions.
@SuppressLint("NewApi")
private void handlePermissionSummary()
{
boolean connectOk = (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED);
boolean scanOk = (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED);
boolean locationOk = (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED);
boolean backgroundOk = (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED);
Log.d(TAG, "HH2: BLUETOOTH_CONNECT Permissions are " + connectOk);
Log.d(TAG, "HH2: BLUETOOTH_SCAN Permissions are " + scanOk);
Log.d(TAG, "HH2: ACCESS_FINE_LOCATION Permissions are " + locationOk);
Log.d(TAG, "HH2: ACCESS_BACKGROUND_LOCATION Permissions are " + backgroundOk);
String message = "";
if (!connectOk && isVersionS_Plus)
{
message = "<p><b>Bluetooth Connect permissions not given.</b> You will be unable to find and connect to Bluetooth Low Energy devices</p>";
}
if (!scanOk && isVersionS_Plus)
{
message = "<p><b>Bluetooth Scan permissions not given.</b> You will be unable to find and connect to Bluetooth Low Energy devices</p>";
}
if (!locationOk && !isVersionS_Plus)
{
message = "<p><b>Location permissions not given.</b> You will be unable to find and connect to Bluetooth Low Energy devices</p>";
}
if (!backgroundOk && isVersionO_Plus)
{
message = message + "<p><b>Background permissions not given.</b> Operations with Bluetooth Low Energy devices will only work " +
"while Health@Home PHG is visible on the screen.</p>";
}
if (!message.isEmpty())
{
message = message + "<p>Remedies:<br>1. Restart Health@Home PHG.<br>2. Set permissions directly in the Android Settings menu<br>" +
"3. Uninstall and re-install Health@Home PHG</p>";
final AlertDialog.Builder builder =
new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.Theme_AppCompat_Light));
builder.setTitle("Health@Home not given certain permissions!");
if (isVersionN_Plus)
{
builder.setMessage(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY));
}
else
{
builder.setMessage(Html.fromHtml(message));
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(dialog ->
{
Log.d(TAG, "Starting Login");
// setLoginScreen(); // Do whatever you would do after permissions are handled
});
builder.show();
return;
}
Log.d(TAG, "Starting Login");
//  setLoginScreen(); // Do whatever you would do after permissions are handled
}
}

正如@Brian Reinhold在其回答中所说,将ACCESS_BACKGROUND_LOCATION放入清单中并不足以使">始终允许"。 选项显示在权限弹出窗口中。 您必须在代码中明确要求ACCESS_BACKGROUND_LOCATION权限,例如:

使用简易权限库:

if (!EasyPermissions.hasPermissions(requireContext(), *perms)) {
val builder = PermissionRequest
.Builder(this, REQUEST_LOCATION_PERMISSION, *perms)
.setRationale(getString(R.string.location_disclaimer))

EasyPermissions.requestPermissions(builder.build())
}
companion object {
var perms = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
}
}

正如其他答案所说,你必须使用

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

但是您还必须手动请求此权限,以便用户允许它。在离子科尔多瓦中,您可以像这样做:

const accessFineLocationPermission = await this.permissions.checkPermission(this.permissions.PERMISSION.ACCESS_FINE_LOCATION)
if (accessFineLocationPermission && !accessFineLocationPermission.hasPermission) {
await this.permissions.requestPermission(this.permissions.PERMISSION.ACCESS_FINE_LOCATION)
}

使用 Android 权限 Cordova 插件: https://ionicframework.com/docs/native/android-permissions

您必须申请ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION和ACCESS_BACKGROUND_LOCATION。

位置信息访问所需的两种类型的 Android 权限:

前台
  1. 位置的前台权限(仅在使用应用程序时允许(
  2. 后台位置的后台权限(始终允许(

如果你的应用请求后台权限,则请求应遵循:

  • 首先,请求前台位置访问。
  • 然后,您可以请求后台位置信息访问权限。

注意:建议禁用后台用户访问 位置,直到你的应用具有前台位置访问权限。

那么如何访问后台权限:

步骤 1:在清单上声明这些权限。

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

步骤 2:检查运行时权限

if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
// Request for Foreground permission
ActivityCompat.requestPermissions(
this@YourActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
FINE_LOCATION_REQUEST_CODE
)
} else {
// Request for Background permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActivityCompat.requestPermissions(
this@MainActivity,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
BACKGROUND_REQUEST_CODE
)
}
}

最新更新