应用程序被杀死后无法访问位置

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))
    }
}