应用程序被杀死后无法访问位置
Can't access location after app is killed
我正在开发 android 应每 15 分钟收集一次用户地理定位(如果用户同意 ofc)的应用程序。我熟悉 WorkManger 的概念,我正在使用它来执行 PeriodicWork。另外,我使用它的主要原因是它在设备重启后可以正常工作。
我使用 LocationManager 而不是 Fused,因为我的要求之一是可能在 Play 商店之外分发。
问题是:当应用程序被终止(最近的项目 -> 滑动)时,工作人员仍然执行 LocationWork,但提供者 returns 始终为 null。我听说过后台位置限制,所以我尝试将此功能移至前台服务。不幸的是,它没有改变任何东西。
我的代码:
class GeoLocationWorker(context: Context, workerParameters: WorkerParameters) :
Worker(context, workerParameters) {
private var requestQueue: RequestQueue = Volley.newRequestQueue(applicationContext)
override fun doWork(): Result {
Log.i("GLW", "before sleep")
Thread.sleep(5000)
Log.i("GLW", "after sleep")
Log.i("GLW", "location lookup start")
applicationContext.startForegroundService(Intent(applicationContext, GeoService::class.java))
return Result.success()
}
}
class GeoService : Service() {
override fun onBind(p0: Intent?): IBinder? {
stopForeground(true)
return null
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground()
return START_STICKY
}
private fun startForeground() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("my_service", "My Background Service")
} else {
""
}
val notificationBuilder = NotificationCompat.Builder(this, channelId)
val notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build()
startForeground(101, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
val locationManager =
applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager
/* no checking for permissions, because this code is executed only after successful request
for ACCESS_LOCATION_*
*/
locationManager.getCurrentLocation(
LocationManager.GPS_PROVIDER,
null,
applicationContext.mainExecutor,
{
if (it == null) {
Log.e("location", "location == null")
} else {
Log.i("location", it.toString())
}
stopForeground(true)
})
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
val chan = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
}
清单:
<service
android:name="a.b.c.d.GeoService"
android:foregroundServiceType="location" />
请求权限:
class PermissionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
setContentView(R.layout.activity_permission)
}
@SuppressLint("InlinedApi")
fun askForPermissions(view: View) {
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
),
1
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
for (i in permissions.indices) {
if (permissions[i] == android.Manifest.permission.ACCESS_FINE_LOCATION
&& grantResults[i] == PackageManager.PERMISSION_GRANTED
) {
val request =
PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES)
.build()
WorkManager
.getInstance(this)
.enqueueUniquePeriodicWork(
WorkerConfig.LOCATION_WORKER,
ExistingPeriodicWorkPolicy.KEEP,
request
)
}
}
finish()
startActivity(Intent(this, HomeActivity::class.java))
}
}
从 android 10 及更高版本开始,您需要请求 ACCESS_BACKGROUND_LOCATION
以在应用程序处于后台时获取位置 - Request Location Permission
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
更新您的位置代码:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// requesting background location
BottomNavigationActivity.this.requestPermissions(
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION},
MY_PERMISSION_LOCATION);
} else {
MyActivity.this.requestPermissions(
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSION_LOCATION);
}
我已经设法找到解决方案。 Nitish 的评论对我帮助很大,但主要问题是我在文档中遗漏了一些内容:
Caution: If your app targets Android 11 (API level 30) or higher, the system enforces this best practice. If you request a foreground location permission and the background location permission at the same time, the system ignores the request and doesn't grant your app either permission.
工作许可请求代码:
class PermissionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
setContentView(R.layout.activity_permission)
}
fun askForPermissions(view: View) {
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
),
1
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
2
)
return
}
for (i in permissions.indices) {
if (permissions[i] == android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
&& grantResults[i] == PackageManager.PERMISSION_GRANTED
) {
val request =
PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES)
.build()
WorkManager
.getInstance(this)
.enqueueUniquePeriodicWork(
WorkerConfig.LOCATION_WORKER,
ExistingPeriodicWorkPolicy.REPLACE,
request
)
}
}
finish()
startActivity(Intent(this, HomeActivity::class.java))
}
}
我正在开发 android 应每 15 分钟收集一次用户地理定位(如果用户同意 ofc)的应用程序。我熟悉 WorkManger 的概念,我正在使用它来执行 PeriodicWork。另外,我使用它的主要原因是它在设备重启后可以正常工作。
我使用 LocationManager 而不是 Fused,因为我的要求之一是可能在 Play 商店之外分发。
问题是:当应用程序被终止(最近的项目 -> 滑动)时,工作人员仍然执行 LocationWork,但提供者 returns 始终为 null。我听说过后台位置限制,所以我尝试将此功能移至前台服务。不幸的是,它没有改变任何东西。
我的代码:
class GeoLocationWorker(context: Context, workerParameters: WorkerParameters) :
Worker(context, workerParameters) {
private var requestQueue: RequestQueue = Volley.newRequestQueue(applicationContext)
override fun doWork(): Result {
Log.i("GLW", "before sleep")
Thread.sleep(5000)
Log.i("GLW", "after sleep")
Log.i("GLW", "location lookup start")
applicationContext.startForegroundService(Intent(applicationContext, GeoService::class.java))
return Result.success()
}
}
class GeoService : Service() {
override fun onBind(p0: Intent?): IBinder? {
stopForeground(true)
return null
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground()
return START_STICKY
}
private fun startForeground() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("my_service", "My Background Service")
} else {
""
}
val notificationBuilder = NotificationCompat.Builder(this, channelId)
val notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build()
startForeground(101, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
val locationManager =
applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager
/* no checking for permissions, because this code is executed only after successful request
for ACCESS_LOCATION_*
*/
locationManager.getCurrentLocation(
LocationManager.GPS_PROVIDER,
null,
applicationContext.mainExecutor,
{
if (it == null) {
Log.e("location", "location == null")
} else {
Log.i("location", it.toString())
}
stopForeground(true)
})
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
val chan = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
}
清单:
<service
android:name="a.b.c.d.GeoService"
android:foregroundServiceType="location" />
请求权限:
class PermissionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
setContentView(R.layout.activity_permission)
}
@SuppressLint("InlinedApi")
fun askForPermissions(view: View) {
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
),
1
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
for (i in permissions.indices) {
if (permissions[i] == android.Manifest.permission.ACCESS_FINE_LOCATION
&& grantResults[i] == PackageManager.PERMISSION_GRANTED
) {
val request =
PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES)
.build()
WorkManager
.getInstance(this)
.enqueueUniquePeriodicWork(
WorkerConfig.LOCATION_WORKER,
ExistingPeriodicWorkPolicy.KEEP,
request
)
}
}
finish()
startActivity(Intent(this, HomeActivity::class.java))
}
}
从 android 10 及更高版本开始,您需要请求 ACCESS_BACKGROUND_LOCATION
以在应用程序处于后台时获取位置 - Request Location Permission
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
更新您的位置代码:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// requesting background location
BottomNavigationActivity.this.requestPermissions(
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION},
MY_PERMISSION_LOCATION);
} else {
MyActivity.this.requestPermissions(
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSION_LOCATION);
}
我已经设法找到解决方案。 Nitish 的评论对我帮助很大,但主要问题是我在文档中遗漏了一些内容:
Caution: If your app targets Android 11 (API level 30) or higher, the system enforces this best practice. If you request a foreground location permission and the background location permission at the same time, the system ignores the request and doesn't grant your app either permission.
工作许可请求代码:
class PermissionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.hide()
setContentView(R.layout.activity_permission)
}
fun askForPermissions(view: View) {
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
),
1
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
2
)
return
}
for (i in permissions.indices) {
if (permissions[i] == android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
&& grantResults[i] == PackageManager.PERMISSION_GRANTED
) {
val request =
PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES)
.build()
WorkManager
.getInstance(this)
.enqueueUniquePeriodicWork(
WorkerConfig.LOCATION_WORKER,
ExistingPeriodicWorkPolicy.REPLACE,
request
)
}
}
finish()
startActivity(Intent(this, HomeActivity::class.java))
}
}