Development record of developer who study hard everyday.

레이블이 Job migration인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Job migration인 게시물을 표시합니다. 모든 게시물 표시
, , , , , ,

Android-Job(Evernote) 에서 WorkManager로 이전하기(migration)

 Android-Job(Evernote)에서 WorkManager로 migration하기

안드로이드 블로그

회사에서 예전 프로젝트의 sdk업데이트 작업을 진행했다.

소스를 까보니까 targetSDK가 29였다.

32로 올리려고한다.

그런데 이 프로젝트가 com.android.evernote.job 라이브러리를 사용하고 있었다.

이 라이브러리는 API 31부터 deprecated 되었다.

그래도 혹시나 작동을 하지 않을까하는 기대로 빌드를 해보았는데....

역시나 알람이 작동하지 않았다.

어쩔 수 없이 Evernote의 Job 라이브러리를 migration하는 방법을 구글링해보았다.

방법은 간단하다.

WorkManager와 AlarmManager를 사용하라는 것이였다.

상당히 귀찮지만.... 시작해보자!


1. 기존 프로젝트 evernote-job 구조 살펴보기

기존의 프로젝트에서 evernote-job 라이브러리를 어떻게 사용했는지 살펴보자.

evernote-job 클래스

패키지를 보면서 분석해보자.

class DailyAlarmAnalyze : DailyJob() {

protected override fun onRunDailyJob(params: Job.Params): DailyJob.DailyJobResult {
Log.d(TAG, "onRunDailyJob")
val application = context.applicationContext as MainApplication
val preferenceRepository = PreferenceRepositoryImpl(application)
val ibranceOperator = IbranceOperator(application, preferenceRepository)
ibranceOperator.analyzeAlarmJob(false)

// if any condition don't match, skip the DailyAlarmAnalyze job.
// but, i'll return as DailyJobResult.SUCCESS to handle properly.
return DailyJobResult.SUCCESS
}

companion object {
@JvmField
val TAG = DailyAlarmAnalyze::class.java.simpleName

@JvmStatic
fun schedule() {
val jobRequest = JobRequest.Builder(TAG)
.setUpdateCurrent(true)

DailyJob.schedule(jobRequest, TimeUnit.HOURS.toMillis(0),
TimeUnit.HOURS.toMillis(0) + TimeUnit.MINUTES.toMillis(25))
}
}
}

DailyAlarmAnalyze는 매일 특정한 시간에 onRunDailyJob함수를 실행시키는 DailyJob 클래스이다.


class HistoryJob : Job() {

override fun onRunJob(params: Params): Result {
val intent = Intent(context, AlarmHistoryActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
// context.showNotification(intent, context.getString(R.string.app_name), context.getString(R.string.alarm_history_message))
return Result.SUCCESS
}

companion object {
@JvmField
val TAG = HistoryJob::class.java.simpleName
}
}
class HospitalJob : Job() {

override fun onRunJob(params: Params): Result {
val intent = Intent(context, AlarmHospitalActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
// context.showNotification(intent, context.getString(R.string.app_name), context.getString(R.string.alarm_hospital_message))
return Result.SUCCESS
}

companion object {
@JvmField
val TAG = HospitalJob::class.java.simpleName
}
}
class MedicationJob : Job() {

override fun onRunJob(params: Params): Result {
val intent = Intent(context, AlarmMedicationActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
// context.showNotification(intent, context.getString(R.string.app_name), context.getString(R.string.alarm_medication_message))
return Result.SUCCESS
}

companion object {
@JvmField
val TAG = MedicationJob::class.java.simpleName
}
}

HistoryJob, HospitalJob, MedicationJob은 알람시에 작동해주어야할 코드가 들어있는 Job객체이다.


class OnceAlarmAnalyze : Job() {
override fun onRunJob(params: Params): Result {
val application = context.applicationContext as MainApplication
val preferenceRepository = PreferenceRepositoryImpl(application)
val ibranceOperator = IbranceOperator(application, preferenceRepository)
ibranceOperator.analyzeAlarmJob()
return Result.SUCCESS
}

companion object {
@JvmField
val TAG = OnceAlarmAnalyze::class.java.simpleName

@JvmStatic
fun schedule() {
JobRequest.Builder(OnceAlarmAnalyze.TAG)
.setUpdateCurrent(true)
.startNow()
.build()
.schedule()
}
}
}

OnceAlarmAnalyze는 onRunJob()에 있는 코드를 바로 실행시키는 Job클래스이다.


현재 프로젝트에서는 앱이 시작되서 MainViewModel이 초기화되면 DailyAlarmAnalyze를 실행시켜서 HistoryJob, HospitalJob, MedicationJob 중 어떤 작업을 실행해야할지 판단해서 작업을 예약한다.

그리고 사용자가 HistoryJob, HospitalJob, MedicationJob 중 조건을 변경하였을 때, OnceAlarmAnalyze를 실행시켜 모든 작업을 취소시키고 다시 어떤 작업을 실행해야할지 판단해서 작업을 예약한다.


참고로 IbranceJobCreator는 JobCreator를 상속하는 클래스로 Job 클래스들을 구분할 TAG를 만들어준다.

migration할 시 핵심내용은 아니라서 그냥 패스하자.


2. AlarmManager와 WorkManager로 evernote.job에서 migration하기

evernote.job에서 매일 특정 시간에 작업을 수행하는 DailyJob은 AlarmManager로 대체했다.

private void setAlarmManager() {
AlarmManager alarmMgr = (AlarmManager) getBaseContext().getSystemService(Context.ALARM_SERVICE);

if(alarmIntent != null) alarmMgr.cancel(alarmIntent);

Intent intent = new Intent(getBaseContext(), AlarmReceiver.class);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
alarmIntent = PendingIntent.getBroadcast(getBaseContext(), 0, intent, PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
} else {
alarmIntent = PendingIntent.getBroadcast(getBaseContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

//set the alarm to start at 00:00
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 24);


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
} else {
alarmMgr.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
}
}

☝ 00시 정각에 알람을 세팅해준다.

setExactAndAllowWhileIdle와 setExact를 통해 RTC_WAKEUP을 사용할 것이기 때문에 manifest.xml에 권한도 추가해준다.

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


알람이 울리면 신호를 받을 BroadcastReceiver도 만들어준다.

class AlarmReceiver : BroadcastReceiver() {
val TAG = this::class.java.simpleName

override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "AlarmReceiver onReceive")

val application = context?.applicationContext as MainApplication
val preferenceRepository = PreferenceRepositoryImpl(application)
val ibranceOperator = IbranceOperator(application, preferenceRepository)
ibranceOperator.analyzeAlarmWorker(false)

setTomorrowAlarm(context)
}

private fun setTomorrowAlarm(context: Context?) {
val alarmMgr = context?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java)

val alarmIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
} else {
PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}

//set the alarm to start at 00:00
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis + TimeUnit.HOURS.toMillis(24), alarmIntent)
} else {
alarmMgr.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis + TimeUnit.HOURS.toMillis(24), alarmIntent)
}
}
}

안드로이드 알람매니저 공식문서를 보면 setRepeating(), setInexactRepeating()을 사용해서 반복 알람을 설정할 수 있다는데 나는 구현이 되지 않았다...

도무지 안되서 일단 알람을 울리고 BroadcastReceiver에서 다시 24시간 뒤 알람을 울리도록 했다.

그게 setTomorrowAlarm() 함수이다.


<receiver android:name=".example.AlarmReceiver"/>

☝ manifest.xml에도 BroadcastReceiver를 등록해준다.


evernote.job에서 Job객체들은 WorkManager로 전부 바꿔주었다.

예를들어, HistoryJob을 HistoryWork 클래스를 만들어서 구현했다.

class HistoryWorker(val appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {

override fun doWork(): Result {
Log.d(TAG, "HistoryWorker doWork")


return Result.success()
}

companion object {
@JvmField
val TAG = HistoryWorker::class.java.simpleName
}
}


그리고 필요한 경우에 따라 TAG로 각 Worker 객체를 구분해서 필요한 작업을 필요한 시간에 진행한다. (아래 코드 참고)

val timeMillis = target - now

val workRequest = when(tag) {
HistoryWorker.TAG ->
OneTimeWorkRequestBuilder<HistoryWorker>()
.addTag(HistoryWorker.TAG)
.setInitialDelay(timeMillis, TimeUnit.MILLISECONDS)
.build()

HospitalWorker.TAG ->
OneTimeWorkRequestBuilder<HospitalWorker>()
.addTag(HospitalWorker.TAG)
.setInitialDelay(timeMillis, TimeUnit.MILLISECONDS)
.build()

MedicationWorker.TAG ->
OneTimeWorkRequestBuilder<MedicationWorker>()
.addTag(MedicationWorker.TAG)
.setInitialDelay(timeMillis, TimeUnit.MILLISECONDS)
.build()

else ->
return false
}

WorkManager.getInstance(application.applicationContext)
.enqueue(workRequest)


Share:
Read More