산책하는 거리를 계산하기 위해 일정 시간마다 해당 위치를 저장하고 각 위치간의 거리와 산책 소요 시간을 포그라운드 서비스를 통해 출력하고자 하였으나...
포그라운드 서비스를 사용하기 위해 기존 코드를 사용하려 했으나 android 12부터 몇 가지 특수한 사례를 제외하고 포그라운드 서비스를 사용할 수 없게 되었다. 공식 페이지에서는 대안으로 WorkManager를 추천하여 사용하게 되었다.
지속적인 작업에 권장되는 방식. 앱이 다시 시작되거나 시스템이 재부팅되어도 작업이 예약된 채로 남아 있고 작업이 유지됨. 대부분의 백그라운드 처리는 지속적인 작업에 처리되므로 백그라운드 처리에 권장되는 api임
android 12부터는 WorkManager 없이 앱이 백그라운드에서 실행되는 동안 포그라운드 서비스를 시작하려고 해도 포그라운드 서비스가 예외 사례 중 하나를 충족하지 못하면 시스템에서 ForegroundServiceStartNotAllowedException이 발생.
CoroutineWorker를 상속받은 WorkManager를 생성하고 내부에서 LocationManager를 통해 감지된 location을 통해 이동 거리를 계산한다
override suspend fun doWork(): Result {
Log.d(TAG, "doWork: 산책 시작")
val foregroundInfo = createForegroundInfo(notificationContent)
setForegroundAsync(foregroundInfo)
handler.post {
getProviders()
}
simulateLocationUpdates()
return Result.success()
}
private fun createForegroundInfo(progress: String): ForegroundInfo {
createNotificationChannel()
val title = applicationContext.getString(R.string.app_name)
val notification = NotificationCompat.Builder(applicationContext, FOREGROUND_SERVICE_ID.toString())
.setContentTitle("지금은 산책중입니다~")
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.main_logo_small)
.setOngoing(true)
.build()
return ForegroundInfo(FOREGROUND_SERVICE_ID, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager: NotificationManager =
applicationContext.getSystemService(NotificationManager::class.java)
val serviceChannel = NotificationChannel(
FOREGROUND_SERVICE_ID.toString(),
applicationContext.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW,
)
manager.createNotificationChannel(serviceChannel)
}
}
private val locationManager by lazy {
applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
private lateinit var location1: Location
private lateinit var location2: Location
private fun getProviders() {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0,
0f,
listener,
)
}
private val listener = object : LocationListener {
// 위치가 변경될때 호출될 method
override fun onLocationChanged(location: Location) {
when (location.provider) {
LocationManager.GPS_PROVIDER -> {
location2 = location
}
}
}
override fun onProviderEnabled(provider: String) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, this)
}
}
private var totalDist = 0F
private var time = 0
private suspend fun simulateLocationUpdates() {
while (true) {
delay(1000) // Simulate delay between updates
if (this::location1.isInitialized) {
totalDist += location1.distanceTo(location2)
WalkFragment.walkDist = totalDist
Log.d(TAG, "simulateLocationUpdates: 이동거리 $totalDist")
}
if (this::location2.isInitialized) {
location1 = location2
}
time++
WalkFragment.walkTime = time
updateNotification("산책 거리 : ${StringFormatUtil.distanceIntToString(totalDist.toInt())} \n산책 시간 : ${StringFormatUtil.timeIntToString(time)}")
}
}
private fun updateNotification(content: String) {
val foregroundInfo = createForegroundInfo(content)
setForegroundAsync(foregroundInfo)
}
class WalkWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val locationManager by lazy {
// ~~Manager는 getSystemService로 호출
applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
private lateinit var location1: Location
private lateinit var location2: Location
private var totalDist = 0F
private var time = 0
private val handler = Handler(Looper.getMainLooper())
private val notificationContent = "산책 시작!"
override suspend fun doWork(): Result {
Log.d(TAG, "doWork: 산책 시작")
val foregroundInfo = createForegroundInfo(notificationContent)
setForegroundAsync(foregroundInfo)
Log.d(TAG, "doWork: notification 생성")
handler.post {
getProviders()
}
// Simulate receiving location updates
simulateLocationUpdates()
return Result.success()
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
createNotificationChannel()
val title = applicationContext.getString(R.string.app_name)
val notification = NotificationCompat.Builder(applicationContext, FOREGROUND_SERVICE_ID.toString())
.setContentTitle("지금은 산책중입니다~")
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.main_logo_small)
.setOngoing(true)
.build()
return ForegroundInfo(FOREGROUND_SERVICE_ID, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager: NotificationManager =
applicationContext.getSystemService(NotificationManager::class.java)
val serviceChannel = NotificationChannel(
FOREGROUND_SERVICE_ID.toString(),
applicationContext.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW,
)
manager.createNotificationChannel(serviceChannel)
}
}
private suspend fun simulateLocationUpdates() {
while (true) {
delay(1000) // Simulate delay between updates
if (this::location1.isInitialized) {
totalDist += location1.distanceTo(location2)
WalkFragment.walkDist = totalDist
Log.d(TAG, "simulateLocationUpdates: 이동거리 $totalDist")
}
if (this::location2.isInitialized) {
location1 = location2
}
time++
WalkFragment.walkTime = time
updateNotification("산책 거리 : ${StringFormatUtil.distanceIntToString(totalDist.toInt())} \n산책 시간 : ${StringFormatUtil.timeIntToString(time)}")
}
}
private fun updateNotification(content: String) {
val foregroundInfo = createForegroundInfo(content)
setForegroundAsync(foregroundInfo)
}
companion object {
const val FOREGROUND_SERVICE_ID = 12345
}
@SuppressLint("MissingPermission")
private fun getProviders() {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0,
0f,
listener,
)
}
private val listener = object : LocationListener {
// 위치가 변경될때 호출될 method
override fun onLocationChanged(location: Location) {
when (location.provider) {
LocationManager.GPS_PROVIDER -> {
location2 = location
}
}
}
@SuppressLint("MissingPermission")
override fun onProviderEnabled(provider: String) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, this)
}
}
}