Android:应用因后台位置而被 Google 拒绝 在后台请求位置需要ACCESS_BACKGROUND_LOCATION

问题描述

我的应用最近遇到了问题,Google Play 突然拒绝了它,因为他们发现我正在使用后台位置。但实际上我没有使用这个功能。我只有 ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION 权限,我正在使用 FusedLocationProviderClient 在我的应用中获取位置。此位置仅由应用程序内的用户操作请求,因此如果它在后台,则永远不会调用。我检查了合并清单功能,并尝试查找我导入的某些库是否正在使用后台位置权限,但我什么也没找到。此外,我还预防性地将 <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove"/> 添加到我的清单中,以阻止任何后台位置权限请求。我根本没有任何与位置有关的后台服务。唯一的后台服务是用于推送通知FirebaseMessagingService

最近有人遇到这个问题吗?

更新:

我在我的应用中检查了合并清单,但在那里找不到 ACCESS_BACKGROUND_LOCATION 权限。但是我发现了一些可以触发后台位置的服务,但我不确定。它们是 Firebase Crashlytics 的一部分,可能用于将数据发送到 Firebase,并且可以在后台工作。但我不认为他们正在发送任何位置。它们也是来自 Google 的 firebase 插件的一部分。

 <service
        android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
        android:exported="false"
        android:permission="android.permission.BIND_JOB_SERVICE" >
    </service>

    <receiver
        android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerSchedulerbroadcastReceiver"
        android:exported="false" />

更新 #2:

这是我用来获取位置的代码

主要活动:

     /**
     * Updating location every second/1 meter
     */
    var currLocation: GpsLocation? = null
    private var locationManager : LocationManager? = null
    private fun initLocationManager() {
        if (app.hasLocationPermission){
            locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        }
        changeLocationUpdaters(true)
    }

    private fun changeLocationUpdaters(isEnabled: Boolean){
        if (ActivityCompat.checkSelfPermission(
                        this@MainActivity,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(
                        this@MainActivity,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            locationManager?.apply{
                if (isEnabled && app.hasLocationPermission){
                    requestLocationUpdates(LocationManager.GPS_PROVIDER,LOCATION_UPDATE_TIME_INTERVAL,LOCATION_UPDATE_disT_INTERVAL,this@MainActivity)
                    requestLocationUpdates(LocationManager.NETWORK_PROVIDER,this@MainActivity)
                } else {
                    removeUpdates(this@MainActivity)
                }
            }
        } else {
            return
        }
    }

然后在应用程序处于后台删除位置更新程序:

override fun onPause() {
  super.onPause()
  changeLocationUpdaters(false)
}

override fun onResume() {
  super.onResume()
  changeLocationUpdaters(true)
}

然后我在 FusedLocationProvider 中使用 Fragment 来获得更准确的位置。它仅通过调用函数使用,因此不像以前那样自动化。它用于 GoogleMap 类以及应用程序内的一些 onClick 事件以返回当前位置。没有服务或更新程序调用它。

private inner class LocationCb(val lp: FusedLocationProviderClient,val onFailure: (()->Unit)? = null,val onSuccess: (GpsLocation)->Unit)
        : LocationCallback() {

        init {
            val lr = LocationRequest.create().apply {
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY
                interval = 200
            }
            val lsr = LocationSettingsRequest.Builder().run {
                addLocationRequest(lr)
                build()
            }
            val check = LocationServices.getSettingsClient(activity!!).checkLocationSettings(lsr)
            check.addOnCompleteListener {
                try {
                    check.getResult(ApiException::class.java)
                    val task = lp.requestLocationUpdates(lr,this,Looper.getMainLooper())
                    task.addOnFailureListener {
                        onFailure?.invoke()
                    }
                } catch (e: ApiException) {
                    when (e.statusCode) {
                        LocationSettingsstatusCodes.RESOLUTION_required-> if(!locationResolutionAsked){
                            // Location settings are not satisfied. But Could be fixed by showing the user a dialog.
                            try {
                                // Cast to a resolvable exception.
                                val re = e as ResolvableApiException
                                // Show the dialog by calling startResolutionForResult(),and check the result in onActivityResult().
                                re.startResolutionForResult(mainActivity,MainActivity.REQUEST_LOCATION_SETTINGS)
                                locationResolutionAsked = true
                            } catch (e: Exception) {
                                e.printstacktrace()
                            }
                        }
                        LocationSettingsstatusCodes.SETTINGS_CHANGE_UNAVAILABLE->{
                            App.warn("Location is not available")
                            onFailure?.invoke()
                        }
                    }
                }
            }
        }

        fun cancel(){
            lp.removeLocationUpdates(this)
            currLocCb = null
        }

        override fun onLocationResult(lr: LocationResult) {
            cancel()
            val ll = lr.lastLocation
            onSuccess(GpsLocation(ll.longitude,ll.latitude))
        }
    }

返回结果后,此位置提供程序被取消,因此只能一次性使用。但是我在 onPauseonStop 中为 Fragment 添加了类似的取消方法,而不是在 MainActivity 中,以确保它在应用程序处于后台时处于非活动状态。

override fun onStop() {
   super.onStop()
   currLocCb?.cancel()
}

override fun onPause() {
   super.onPause()
   currLocCb?.cancel()
}

解决方法

合并的清单可能不包含所有权限

遗憾的是,并非所有图书馆都发布包含所有必要 <uses-permission> 元素的清单。这意味着,简单地检查合并的 AndroidManifest.xml 不会有太大帮助 - 您必须检查每个库的文档以找出它真正需要哪些权限,或者只是将必要的权限添加到您自己的 AndroidManifest.xml先发制人。

API 29 的后台权限限制

您还提到您的目标 SDK 是 29。因此,根据官方文档 here,如果需要,您必须在 AndroidManifest.xml 中明确设置权限。以前,如果应用具有前台位置访问权限(基本上,ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION),它会自动授予。

在 Android 10(API 级别 29)及更高版本上,您必须声明 ACCESS_BACKGROUND_LOCATION 权限在您的应用清单中按顺序排列 在运行时请求后台位置访问。在早期版本上 在 Android 中,当您的应用收到前台位置访问权限时,它会 也会自动接收后台位置访问权限。

因此,对于旧版本,您的应用会自动获得 ACCESS_BACKGROUND_LOCATION,因为它事先获得了 ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION

在后台请求位置需要ACCESS_BACKGROUND_LOCATION

此外,即使您或您的任何库没有在任何地方设置 ACCESS_BACKGROUND_LOCATION,系统仍会认为您的应用在任何情况下都使用后台位置,除了

属于您的应用的 Activity 可见。

您的应用正在运行 前台服务。当前台服务运行时,系统 通过显示持久通知来提高用户意识。你的应用 当它被放置在后台时保留访问权限,例如当 用户按下其设备上的主页按钮或转动其设备的 显示关闭。

结论

后者的意思是可能有一个或多个需要 ACCESS_BACKGROUND_LOCATION 的库,但无论出于何种原因,它都没有出现在它们的 AndroidManifest.xml 中。它曾经适用于 API 29,因为您的应用已被自动授予权限(由于前台位置权限)。

此外,现在,如果在您的可见 Activity 之外或不在 Foreground Service 中完成,系统会将当前位置的任何使用视为背景位置。因此,请确保您没有在应用的任何部分这样做。

更新

根据您更新的问题,您正在通过调用 OnCompleteListener 请求 lp.requestLocationUpdates 内的当前位置:

...
        check.addOnCompleteListener {
            try {
                check.getResult(ApiException::class.java)
                val task = lp.requestLocationUpdates(lr,this,Looper.getMainLooper())
                task.addOnFailureListener {
                    onFailure?.invoke()
                }
...

这可能是一个问题(我不能确定,因为您没有展示该类在您的应用程序中的使用方式)因为该应用程序可能会在 OnCompleteListener 完成之前转到后台,因此位置将是在后台请求。

如上一节所述,通过这样做,系统认为您需要后台位置权限才能这样做。因此,如果您的应用进入后台,您必须取消订阅回调 OnCompleteListener

您可以使用另一个版本的 addOnCompleteListener,它也接受您的 Activity 实例,如图所示here

公共任务 addOnCompleteListener(Activity 活动,OnCompleteListener 监听器)

在这种情况下,监听器将在 Activity.onStop() 期间自动移除。

,

首先,从清单中完全删除单词 ACCESS_BACKGROUND_LOCATION。即使使用 tools:node="remove"

第二个:如果您没有手动添加 ACCESS_BACKGROUND_LOCATION 并不意味着它不存在 - 某些库可能已经为您添加了它。而不是检查您的项目清单文件 - 检查合并的清单 - 它的通常路径是:(如果您有不同的风格,则可能会有所不同)

/project/module/build/intermediates/manifests/full/debug/AndroidManifest.xml

检查那里是否有 ACCESS_BACKGROUND_LOCATION 权限 - 如果有 - 这意味着某个图书馆在那里添加了它。手动检查所有库的所有清单以找出哪个库已将其添加到那里。找到后 - 删除它。

如果您的项目严重依赖于目标库 - 您有另一种解决方案 - 在应用程序和 Play 商店控制台中写一份说明,说明您为什么需要使用后台位置并在位置权限对话框之前显示它,消息看起来像:

We need access to your location in the background to ensure our app can function correctly.

请记住,此消息可能不够描述 - 但如果是,Google 的测试人员会通知您。

无论如何,披露是最后的机会解决方案......

如果您没有 ACCESS_BACKGROUND_LOCATION 并且您没有在前台服务中使用位置,而只是在应用程序运行时在应用程序内使用 - 请写一封信给谷歌支持并说明您的所有论点,并询问他们究竟是什么导致了拒绝问题.要有礼貌,脾气好——它会得到解决。我过去遇到过类似的问题,联系他们的发布支持总是有帮助的。