설정한 시간을 주기로 현재 위치를 서버로 post하는 어플리케이션을 생성하겠습니다.
아래 순서대로 해 보겠습니다.
- 저장 주기 설정
- 알람 등록
- 현재 위치와 현재 시간 가져오기
- 저장 주기마다 값을 서버에 POST 하기
기본적인 UI는 안드로이드의 preference UI를 이용해서
서비스의 시작 시간과 제어 시간, 중심 위치등을 설정할 수 있게 합니다.
저장 주기 설정
TimePickerPreference를 설정합니다.
<com.kakaroo.footprinterservice.TimepickerPreference
app:key="timePref_Key"
app:title="@string/service_start_time"
app:summary="서비스가 시작되는 시간을 설정해 주세요"
app:iconSpaceReserved="false"
app:defaultValue="90"/>
<ListPreference
app:key="timeIntervalPref_key"
app:defaultValue="30"
app:entries="@array/time_entries"
app:entryValues="@array/time_values"
app:title="@string/service_time_gap"
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true" />
TimePickerPreferenceDialog를 생성합니다.
class TimePickerPreferenceDialog : PreferenceDialogFragmentCompat() {
lateinit var mTimePicker: TimePicker
override fun onCreateDialogView(context: Context?): View {
mTimePicker = TimePicker(context)
return mTimePicker
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onBindDialogView(view: View?) {
super.onBindDialogView(view)
val minutesAfterMidnight = (preference as TimepickerPreference)
.getPersistedMinutesFromMidnight()
mTimePicker.setIs24HourView(false)
mTimePicker.hour = minutesAfterMidnight / 60
mTimePicker.minute = minutesAfterMidnight % 60
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onDialogClosed(positiveResult: Boolean) {
// Save settings
if (positiveResult) {
val hours: Int
val minutes: Int
// Get the current values from the TimePicker
if (Build.VERSION.SDK_INT >= 23) {
hours = mTimePicker.hour
minutes = mTimePicker.minute
} else {
@Suppress("DEPRECATION")
hours = mTimePicker.currentHour
@Suppress("DEPRECATION")
minutes = mTimePicker.currentMinute
}
// Generate value to save
val minutesAfterMidnight = hours * 60 + minutes
// Save the value
preference.apply {
// This allows the client to ignore the user value.
if (callChangeListener(minutesAfterMidnight)) {
// Save the value
(preference as TimepickerPreference).persistMinutesFromMidnight(minutesAfterMidnight)
preference.summary = (preference as TimepickerPreference).minutesFromMidnightToHourlyTime(minutesAfterMidnight)
}
}
}
/*if(positiveResult) {
val minutesAfterMidnight = (timepicker.hour * 60) + timepicker.minute
(preference as TimepickerPreference).persistMinutesFromMidnight(minutesAfterMidnight)
preference.summary = "123"//minutesFromMidnightToHourlyTime(minutesAfterMidnight)
}*/
}
companion object {
fun newInstance(key: String): TimePickerPreferenceDialog {
val fragment = TimePickerPreferenceDialog()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}
알람 서비스 등록
alarm 을 시작/종료 하는 함수를 구현합니다.
해제시간 설정시 서비스 시간을 재설정하기 위해 intent action은 repeat mode와 restart mode로 구분하였습니다.
alarm repetition으로 테스트 한 결과, 정확한 시간에 알람이 울리지 않아,
알람이 울리고 나면 다시 알람을 재설정 하는 로직으로 변경했습니다.
@RequiresApi(Build.VERSION_CODES.M)
fun startAlarmService(action: Int) {
Log.i(Common.MY_TAG, "startAlarmService:: action:$action" )
val bStartedService = mPref.getBoolean("service_start_key", false)
Log.i(Common.MY_TAG, "startAlarmService:: bStartedService:${bStartedService}")
//기존 알람은 삭제
cancelAlarm()
val alarmManager = this.mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(this.mContext, MyReceiver::class.java)
alarmIntent.action = Common.MY_ACTION_FOOT_PRINTER
/*
if(action == Common.ALARM_REPEAT_MODE || action == Common.ALARM_RESTART_REPEAT_MODE || action == Common.ALARM_DAY_CHANGE_MODE) {
alarmIntent.action = Common.MY_ACTION_FOOT_PRINTER
} else if(action == Common.ALARM_RESTART_MODE) {
alarmIntent.action = Common.MY_ACTION_FOOT_PRINTER_RESTART
}*/
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT//FLAG_UPDATE_CURRENT
}
val pendingIntent = PendingIntent.getBroadcast(this.mContext, 0, alarmIntent, flags)//PendingIntent.FLAG_CANCEL_CURRENT)
var triggerTimeMillis: Long = 0
if(action == Common.ALARM_REPEAT_MODE) { //start service
val calendar = MyUtility().getCalendar(mPref.getInt("timePref_Key", 0), true)
val strInterval : String? = mPref.getString("timeIntervalPref_key", "0")
val minuteInterval : Int = strInterval?.toInt() ?: 0
try {
val selections = mPref.getStringSet("timeExDayPref_key", null) as HashSet<String>
if(selections != null) {
val selected: List<String> = selections.toList()
//위에서 설정된 시간이 제외 요일에 포함되어 있는 경우 제외되지 않은 다음 날짜로 설정한다.
if(selected.isNotEmpty()) {
var dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) //일:1, 월:2 .. 토:7
selected.forEach { item ->
if(item.toInt() == dayOfWeek) {
Log.w(Common.MY_TAG, "제외 요일이므로 다음날짜에 시작됩니다.")
calendar.add(Calendar.DATE, 1)
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
}
}
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
if(dayOfWeek == 1) { //위에서 계산된 요일이 일요일이면 한 번 더 체크한다.
selected.forEach { item ->
Log.w(Common.MY_TAG, "2nd item: ${item.toInt()}, dayOfWeek: ${dayOfWeek}")
if(item.toInt() == dayOfWeek) {
Log.w(Common.MY_TAG, "제외 요일이므로 다음날짜에 시작됩니다.")
calendar.add(Calendar.DATE, 1)
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
}
}
}
}
}
} catch (e: NullPointerException) {
Log.w(Common.MY_TAG, "$e :: intentional NullPointerException when getting timeExDayPref_key")
}
val dateText = MyUtility().getDateTimeFromCalendar(calendar)
Toast.makeText(this.mContext, dateText + "부터, "+minuteInterval+"분 간격으로 " +"알림이 실행됩니다!", Toast.LENGTH_LONG).show()
Log.d(Common.MY_TAG, dateText + "부터, "+minuteInterval+"분 간격으로 " +"알림이 실행됩니다!")
//ELAPSED_REALTIME_WAKEUP
triggerTimeMillis = if(Common.ALARM_WAKEUP_TYPE_ELAPSED == 1) {
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
val diffTimeMillis = calendar.timeInMillis - nowCalendar.timeInMillis
diffTimeMillis
} else {
calendar.timeInMillis
}
} else if(action == Common.ALARM_RESTART_REPEAT_MODE) {
val calendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
calendar[Calendar.SECOND] = 0
val strInterval : String? = mPref.getString("timeIntervalPref_key", "0")
val minuteInterval : Int = strInterval?.toInt() ?: 0
val millisInterval = MyUtility().getMilliTime(minuteInterval)
val dateText = MyUtility().getDateTimeFromCalendar(calendar)
//Toast.makeText(this.mContext, dateText + "부터, "+minuteInterval+"분 간격으로 " +"알림이 실행됩니다!", Toast.LENGTH_LONG).show()
Log.d(Common.MY_TAG, dateText + "부터, "+minuteInterval+"분 간격으로 알람이 울립니다!")
//ELAPSED_REALTIME_WAKEUP
if(Common.ALARM_WAKEUP_TYPE_ELAPSED == 1) {
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
nowCalendar[Calendar.SECOND] = 0
val diffMillis = abs(System.currentTimeMillis() - nowCalendar.timeInMillis)
Log.d(Common.MY_TAG, "diffMillis: $diffMillis")
//0초 부터 알람을 울리기 위해
triggerTimeMillis = millisInterval - diffMillis
} else {
triggerTimeMillis = calendar.timeInMillis + millisInterval
}
} else if(action == Common.RESTART_SERVICE_MODE) {
Log.w(Common.MY_TAG, "RESTART_SERVICE_MODE!!!")
var bTimeIsSet = false
var calendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
calendar[Calendar.SECOND] = 0
val strInterval : String? = mPref.getString("timeIntervalPref_key", "0")
val minuteInterval : Int = strInterval?.toInt() ?: 0
val prevTriggerCalendar = getTriggerPrefTime()
Log.d(Common.MY_TAG, "현재 시간은 ${MyUtility().getDateTimeFromCalendar(calendar)} 입니다")
Log.d(Common.MY_TAG, "트리거시간은 ${MyUtility().getDateTimeFromCalendar(prevTriggerCalendar)} 입니다")
if(prevTriggerCalendar.before(calendar) || prevTriggerCalendar == calendar) { //prevTriggerCalendar < calendar :: Trigger할 시간이 지났으면 바로 알람을 띄운다.
bTimeIsSet = true
Log.w(Common.MY_TAG, "RESTART_SERVICE_MODE:Case-1")
} else if(mPref.getBoolean(Common.HAS_BEEN_POST_THIS_SERVICE_KEY, false)) { // 이번 서비스에 post 한 적이 있다면
val postYear = mPref.getInt(Common.POST_CALENDAR_YEAR_KEY, 2000)
val postDayOfYear = mPref.getInt(Common.POST_CALENDAR_DAY_OF_YEAR_KEY, 1)
val postHour = mPref.getInt(Common.POST_CALENDAR_HOUR_KEY, 9)
val postMinute = mPref.getInt(Common.POST_CALENDAR_MINUTE_KEY, 10)
val postCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
postCalendar[Calendar.YEAR] = postYear
postCalendar[Calendar.DAY_OF_YEAR] = postDayOfYear
postCalendar[Calendar.HOUR_OF_DAY] = postHour
postCalendar[Calendar.MINUTE] = postMinute + minuteInterval
postCalendar[Calendar.SECOND] = 0
Log.d(Common.MY_TAG, "포스트예상시간은 ${MyUtility().getDateTimeFromCalendar(postCalendar)} 입니다")
if(postCalendar.before(calendar) || postCalendar == calendar) { //Post할 시간이 현재 시간을 지났으면 바로 알람을 띄운다.
bTimeIsSet = true
Log.w(Common.MY_TAG, "RESTART_SERVICE_MODE:Case-2")
}
}
if(bTimeIsSet) {
//전송할 시간이 exception시간에 속하면 exception 종료시간으로 알람을 설정한다.
if(MyReceiver().timeIsIncludedExceptionTime(calendar, mPref)) {
val minute = mPref.getInt("timeExPref_key", 0)
val strInterval : String? = mPref.getString("timeExGapPref_key", "0")
val minuteInterval : Int = strInterval?.toInt() ?: 0
calendar = MyUtility().getCalendar(minute + minuteInterval, true)
Log.w(Common.MY_TAG, "RESTART_SERVICE_MODE: Exception 시간으로 전송시간을 재설정합니다.")
}
}
if(bTimeIsSet) {
//최종적으로 설정된 시간이 맞는지 체크
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
nowCalendar[Calendar.SECOND] = 0
if(calendar.before(nowCalendar) || calendar == nowCalendar) {
Log.e(Common.MY_TAG, "Error:: Time is already passed, will be changed to current time")
calendar = nowCalendar
//calendar.add(Calendar.DATE, 1);
}
val dateText = MyUtility().getDateTimeFromCalendar(calendar)
//Toast.makeText(this.mContext, dateText + "부터, "+minuteInterval+"분 간격으로 " +"알림이 실행됩니다!", Toast.LENGTH_LONG).show()
Log.d(Common.MY_TAG, dateText + "부터, "+minuteInterval+"분 간격으로 알람이 울립니다!")
//ELAPSED_REALTIME_WAKEUP
if(Common.ALARM_WAKEUP_TYPE_ELAPSED == 1) {
val millisInterval = MyUtility().getMilliTime(minuteInterval)
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
nowCalendar[Calendar.SECOND] = 0
val diffMillis = abs(System.currentTimeMillis()-nowCalendar.timeInMillis)
Log.d(Common.MY_TAG, "diffMillis: $diffMillis")
//0초 부터 알람을 울리기 위해
triggerTimeMillis = millisInterval - diffMillis
} else {
triggerTimeMillis = calendar.timeInMillis
}
}
} else if(action == Common.ALARM_RESTART_MODE) {
val minute = mPref.getInt("timeExPref_key", 0)
val strInterval : String? = mPref.getString("timeExGapPref_key", "0")
val minuteInterval : Int = strInterval?.toInt() ?: 0
val calendar = MyUtility().getCalendar(minute + minuteInterval, true)
val dateText = MyUtility().getDateTimeFromCalendar(calendar)
//Toast.makeText(this.mContext, dateText + "에 알림이 다시 실행될 예정입니다!", Toast.LENGTH_LONG).show()
Log.w(Common.MY_TAG, "현재시간이 제외시간이기 때문에 ${dateText}에 알림이 다시 실행될 예정입니다!")
//ELAPSED_REALTIME_WAKEUP
triggerTimeMillis = if(Common.ALARM_WAKEUP_TYPE_ELAPSED == 1) {
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
val diffTimeMillis = calendar.timeInMillis - nowCalendar.timeInMillis
diffTimeMillis//-30000 //30000 : 10초전에 알람을 울려 restart를 한다.*/
} else {
calendar.timeInMillis
}
} else if(action == Common.ALARM_DAY_CHANGE_MODE) {
val calendar = MyUtility().getCalendar(0, true) //00시 00분
val dateText = MyUtility().getDateTimeFromCalendar(calendar)
//Toast.makeText(this.mContext, dateText + "에 알림이 다시 실행될 예정입니다!", Toast.LENGTH_LONG).show()
Log.w(Common.MY_TAG, dateText + "에 알림이 다시 실행될 예정입니다!")
//ELAPSED_REALTIME_WAKEUP
triggerTimeMillis = if(Common.ALARM_WAKEUP_TYPE_ELAPSED == 1) {
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
val diffTimeMillis = calendar.timeInMillis - nowCalendar.timeInMillis
diffTimeMillis
} else {
calendar.timeInMillis
}
}
//***알람 설정 ; 파라미터에 알람타입, trigger 시간정보 calendar, 시간 간격(단위, millis이므로 하루: 24 * 60 * 60 * 1000), sender
/*alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP, calendar.timeInMillis,
millisInterval, pendingIntent
)*/
if(triggerTimeMillis > 0) {
//ELAPSED_REALTIME_WAKEUP
if(Common.ALARM_WAKEUP_TYPE_ELAPSED == 1) {
Log.d(Common.MY_TAG, "triggerTimeMillis: ${triggerTimeMillis}, ${triggerTimeMillis/60000.0}분 뒤에 알람 설정")
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ->
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTimeMillis, pendingIntent)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ->
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTimeMillis, pendingIntent)
else ->
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTimeMillis, pendingIntent)
}
} else {
//휴대폰 설정한 시간 기준
val tDate = Date(triggerTimeMillis)
val tDateFormat = SimpleDateFormat("yyyy-MM-dd kk:mm:ss E", Locale("ko", "KR"))
// 현재 시간을 dateFormat 에 선언한 형태의 String 으로 변환
val strDate = tDateFormat.format(tDate)
Log.i(Common.MY_TAG, "${strDate}요일로 알람이 설정되었습니다.")
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ->
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pendingIntent)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ->
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pendingIntent)
else ->
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pendingIntent)
}
}
setTriggerPrefTime(triggerTimeMillis)
}
}
private fun cancelAlarm() {
val alarmManager = this.mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(this.mContext, MyReceiver::class.java)
alarmIntent.action = Common.MY_ACTION_FOOT_PRINTER
var flags = PendingIntent.FLAG_UPDATE_CURRENT//FLAG_NO_CREATE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT//FLAG_NO_CREATE
}
val pendingIntent = PendingIntent.getBroadcast(this.mContext, 0, alarmIntent, flags)
if(pendingIntent != null) {
Log.i(Common.MY_TAG, "알람 ${alarmIntent.action}를 해제합니다.")
alarmManager.cancel(pendingIntent)
} else {
Log.i(Common.MY_TAG, "등록된 알람 ${alarmIntent.action}가 없습니다.")
}
}
fun stopAlarmService(){
Log.i(Common.MY_TAG, "stopAlarmService called")
cancelAlarm()
resetTriggerPrefTime()
resetPostTrialPref()
//Toast.makeText(this.mContext, "service가 중지됩니다.!!", Toast.LENGTH_SHORT).show()
}
알람 설정을 위해 receiver를 등록합니다.
<receiver android:name=".receiver.MyReceiver"
android:exported="false">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="com.kakaroo.footprinterservice.FOOT_PRINTER"/>
</intent-filter>
</receiver>
Receiver 에서 설정한 알람이 울릴 경우, 현재 위치를 서버에 post 합니다.
아래 조건에 해당되는 경우는 post 하지 않습니다.
- 현재시간이 설정한 제외시간/요일에 해당 되는 경우
- 위치 설정이 안 되어 있는 경우
- 위치 값이 이전 위치 값에서 설정한 반경 이내인 경우 (즉, 이전 경로 대비 이동거리가 짧은 경우)
- 서버 오류로 인해 동일시간(분단위)에 전송이 또 되는 경우
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
var recvMode: Int = Common.ALARM_REPEAT_MODE
Log.e(Common.MY_TAG, "onReceive : ${intent?.action}")
when (intent?.action) {
Common.MY_ACTION_FOOT_PRINTER -> {
recvMode = Common.ALARM_REPEAT_MODE
}
Intent.ACTION_SCREEN_ON -> {
recvMode = Common.BOOT_COMPLETE_MODE
}
Common.MY_ACTION_RESTART_SERVICE -> {
recvMode = Common.RESTART_SERVICE_MODE
}
}
if (context != null) {
mContext = context
val mPref = PreferenceManager.getDefaultSharedPreferences(mContext)
val myAlarmService = MyAlarmService(mContext!!, mPref)
if (recvMode == Common.ALARM_REPEAT_MODE) {
if(executeLocationService(mPref, myAlarmService)) {
//알람 반복을 위해 다시 알람을 등록한다.
myAlarmService.startAlarmService(Common.ALARM_RESTART_REPEAT_MODE)
}
}
else if (recvMode == Common.BOOT_COMPLETE_MODE) {
context.startService(Intent(context, RestartService::class.java))
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, RestartService::class.java))
} else {
context.startService(Intent(context, RestartService::class.java))
}*/
val bStartedService = mPref.getBoolean("service_start_key", false)
if(bStartedService) {
val nowCalendar = Calendar.getInstance(TimeZone.getTimeZone(Common.ASIA_TIME_ZONE))
if(timeIsIncludedExceptionTime(nowCalendar, mPref)) { //현재시간이 해제시간에 포함되면
Log.i(Common.MY_TAG, "OnReceive - 부팅후, 현재 해제시간이므로 알람을 재설정합니다.!!")
myAlarmService.startAlarmService(Common.ALARM_RESTART_MODE)
} else {
Log.i(Common.MY_TAG, "OnReceive - 부팅후, 서비스 알람을 재설정합니다.!!")
executeLocationService(mPref, myAlarmService) //바로 HTTP 시작
myAlarmService.startAlarmService(Common.ALARM_RESTART_REPEAT_MODE)
}
} else {
Log.i(Common.MY_TAG, "OnReceive - 서비스가 켜져 있지 않았습니다.")
}
} else if(recvMode == Common.RESTART_SERVICE_MODE) {
Log.i(Common.MY_TAG, "OnReceive - RestartService will be started")
val bStartedService = mPref.getBoolean("service_start_key", false)
//Log.i(Common.MY_TAG, "RESTART_SERVICE_MODE:: bStartedService:${bStartedService}")
//알람 반복을 위해 다시 알람을 등록한다.
if(bStartedService) {
myAlarmService.startAlarmService(Common.RESTART_SERVICE_MODE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, RestartService::class.java))
} else {
context.startService(Intent(context, RestartService::class.java))
}
}
} else {
Log.e(Common.MY_TAG, "OnReceive - context is not initialized yet!!")
}
}
서버에 data post는 Retrofit lib를 활용해 보겠습니다.
dependencies {
//...
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.9.0'
}
<application
android:usesCleartextTraffic="true"
Retrofit Service Interface 정의
post 방식만 필요하여 post만 정의했습니다.
package com.kakaroo.footprinterservice.service
import com.kakaroo.footprinterservice.entity.FootPrinter
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Query
//API를 관리해 주는 Interface
interface IRetrofitNetworkService {
@POST("/post")
@Headers("accept: application/json",
"content-type: application/json")
fun postMethod(
@Body footPrinter: FootPrinter//,
//@Query("id") id: Long
): Call<Int> //Int: Server로부터의 response type
}
Retrofit 객체 생성
val retrofit = Retrofit.Builder()
.baseUrl(Common.DEFAULT_URL + Common.URL_SLASH)
.addConverterFactory(GsonConverterFactory.create())
.build()
인터페이스 타입의 서비스 객체 얻기
var networkService: IRetrofitNetworkService = retrofit.create(IRetrofitNetworkService::class.java)
모든 준비가 끝났습니다. 네트워크 통신이 필요한 순간에 Retrofit 객체로 얻은 서비스 객체의 함수를 호출만 해주면 됩니다. 서비스 클래스와 객체는 Retrofit 이 만들어 주지만, 우리가 만든 인터페이스를 구현한 클래스이므로, 인터페이스의 함수를 호출하면 네트워크 통신을 시도합니다.
//ex) 최종 output 형태는 2022-03-01 07:01:52 이어야 한다.
val current = LocalDateTime.now()
val formatted = current.format(DateTimeFormatter.ISO_DATE_TIME) //출력형식: 2019-03-22T23:56:36.408
val pTime = formatted.replace("T", " ")
val lastIndex = pTime.lastIndexOf(".")
val time = if(lastIndex == -1) pTime else pTime.substring(0, lastIndex)
Log.i(Common.MY_TAG, "postMethod:: $time, 위도:${mCurrentLocation.latitude}, 경도:${mCurrentLocation.longitude}")
val data = FootPrinter(id = 1, time = time, latitude = mCurrentLocation.latitude, longitude = mCurrentLocation.longitude)
networkService.postMethod(data).enqueue(object : Callback<Int> {
override fun onResponse(call: Call<Int>, response: Response<Int>) {
Log.d(Common.MY_TAG, response.toString())
Log.d(Common.MY_TAG, "Post response: "+ response.body().toString())
}
override fun onFailure(call: Call<Int>, t: Throwable) {
// 실패
Log.d(Common.MY_TAG, t.message.toString())
Log.d(Common.MY_TAG, "fail")
}
})
Task가 kill 되고 나면 알람이 정상적으로 동작하지 않기 때문에 앱을 죽지 않게 하기 위해 서비스를 하나 등록해 줍니다.
<service
android:name=".RestartService"
android:enabled="true"
android:exported="true"/>
해당 서비스는 destory가 불릴때 service를 다시 start 해주기 위해 아래 intent-filter를 alarm에 등록합니다.
<action android:name="com.kakaroo.footprinterservice.RESTART.PERSISTENTSERVICE" />
create가 호출되면 alarm을 해제하고, destroy가 호출되면 alarm을 등록합니다.
private fun unregisterRestartAlarm() {
val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(this, MyReceiver::class.java)
alarmIntent.action = Common.MY_ACTION_RESTART_SERVICE
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT//FLAG_UPDATE_CURRENT
}
val pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, flags)
if(pendingIntent != null) {
alarmManager.cancel(pendingIntent)
}
}
private fun registerRestartAlarm() {
val alarmIntent = Intent(this, MyReceiver::class.java)
alarmIntent.action = Common.MY_ACTION_RESTART_SERVICE
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT//FLAG_UPDATE_CURRENT
}
val pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, flags)
val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager
/*
val firstTime = SystemClock.elapsedRealtime() + Common.RESTART_SERVICE_ALARM_PERIOD
alarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, Common.RESTART_SERVICE_ALARM_PERIOD, pendingIntent)
*/
val triggerTimeMillis: Long = Common.RESTART_SERVICE_ALARM_PERIOD
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ->
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTimeMillis, pendingIntent)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ->
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTimeMillis, pendingIntent)
else ->
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTimeMillis, pendingIntent)
}
}
source code : https://github.com/kakarooJ/Android-Kotiln-Location-Monitor
'Kotlin' 카테고리의 다른 글
backing field <custom getter(), setter() 설정> (0) | 2022.03.26 |
---|---|
val, const val (0) | 2022.03.25 |
람다, 고차함수, 함수합성, 클로저 (0) | 2022.03.25 |
NewsFeed - JSoup / Nested RecyclerView (0) | 2022.03.16 |
2. Android - JPA Client with location DB (0) | 2022.03.01 |