본문 바로가기
카테고리 없음

[2023.05.22 ~ 2023.06.04] 당신이 잠든 사이에 리뉴얼 출시 이후..

by Hevton 2023. 6. 5.
반응형

이번주도 저번주도 저저번주도 디버깅과 개선의 일화들!

 

해결한 문제

 

1. 알림이 보이지 않음

: Android 13 (API 33) 부터 모든 앱의 알람이 기본적으로 차단된다.

POST_NOTIFICATIONS 권한을 Manifest에 기재하고, 런타임으로 요청해줘야한다.

단, targetSdk를 32로 낮춰주면 간단하게 해결될 수 있다.

 

Android 13(API 레벨 33) 이상에서는 알림 권한을 거부할 경우 FGS(Forground Services) 작업 관리자에 포그라운드 서비스와 관련된 알림이 계속 표시되지만 알림 드로어에는 표시되지 않습니다.

[궁금해서 실험해본 것]
1. startService -> startForeground 해도 FGS 동작 변함없음
2. 알림 권한 거부해도 포그라운드 서비스는 동작. FGS 에 등록될 뿐 알림에는 안나타는 것 뿐.
3. stopForeground 하지 않아도 stopSelf로 서비스를 종료할 수 있으므로 FGS에서 없어지는건 매한가지.

 

이에 대한 내용은 여기에 더 기록해놨다 : https://hevton.tistory.com/907

 

2. 알림을 onGoing 형식으로 사용하지 않고 있었다.

: 수정 완료. Android 31부터인가는 포그라운드 서비스의 노티피케이션이 기본적으로 onGoing이 해제된다. 따라서 onGoing을 하고싶으면 추가 설정을 해줘야 한다.

 

3. Splash 처리

: Splash를 액티비티 단으로 하는 것 보다, Fragment로 하거나 layout 하는게 나을 수도 있다.
또한 ViewModel init() 완료되면 컨텐츠를 보여지게 하게 activity나 fragmemt 단에서 collect 하여 뷰 반영해주는 방식.

 

 

4. 앱 내의 alertDialog가 순간 순간마다 등장한다.

: PreferenceDataStore 같은 경우에, Key가 다르더라도 같은 파일을 이용한다면

서로 다른 Key에 대한 data가 변경되더라도 다른 Key의 Flow emit에도 영향을 미친다.

따라서 Key A에 대한 flow를 collect하고 있는데, key B에 대한 값을 변경해도 A에 대한 collect가 갱신된다는 문제가 있었다.

해결 방법은 다음과 같이 a, b 두 가지 방법이 있다.

a. 파일 분리
b. distinctUntilChanged() 이용 -> collect 시, 값이 변하지 않았다면 재 호출하지 않음.

 

5. 시간 재설정

TimerService로 넘겨주는 시간의 기준이 '앞으로 남은 시간' 이었는데, 이렇게 하면

앱 재설치 시 타이머를 재등록해주는 과정에서의 어려움이 있으므로 메커니즘을 변경하였다.

 

 

6. startForeground() 이후 알림이 나타나기까지 시간 지연
Android 12부터는, 특별한 제한사항이 없는경우 startForeground() 실행 이후 Notification이 등록되기까지 몇 초가량 소요된다.

해결법 1. 옵션값을 준다.
해결법 2. NotificationManager를 이용해서 임의로 notify해놓는다.
// Android 12 (API 31) 부터 startForeground() 이후 Notification 등록까지 수 초가 딜레이된다.
// 해결법 1. NotificationManager를 이용해서 임의로 미리 생성해도 됨. nm.notify(321, noti); startForeground(321, noti);
// 해결법 2. 아래 옵션을 준다.
https://stackoverflow.com/questions/74151910/foreground-service-notification-takes-a-few-seconds-to-appear-show-up

 

7. 타이머 등록 후 앱 업데이트 시?!

 

MY_PACKAGE_REPLACED 이용

 

 

재설치 이후 상황

-> 알람은 남아 있음, 포그라운드 서비스는 종료됨.

인자를 넘겨주는 방식인 stopSelf(startId)를 사용하면, 이전에 실행한 서비스를 이후에 종료하기가 어려워짐.

starId 는 서비스 실행 주체를 구분하기 위함이기 때문이다.

문제는 지금 재설치 시에 '알람은 남아 있고, 포그라운드 서비스는 종료된 상황' 에서 어떻게 마이그레이션할지에 초점을 둬야함.

 

새로 알게된 점은, 안드로이드 스튜디오에서 앱을 덮어쓰는 방식으로 재설치되는 'Run' 은 위 브로드캐스트를 발생시키지 않는다는 점이다.

 

Generate sigend apk 해서 직접 설치하는 것이, 발생시킬 수 있는 방법이다.

debug용으로 앱을 만들고, 설치해 놓은 다음에

adb -s emulator-5554 install -r app-debug.apk

 

        <receiver
            android:name=".receiver.PackageReplaceReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
            </intent-filter>
        </receiver>
class PackageReplaceReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action.equals(Intent.ACTION_MY_PACKAGE_REPLACED, true)) {
            //이벤트를 받을시 처리할 로직
            Log.d("PackageReplaceReceiver", "곧바로 된다")
        }
    }
}

MY_PACKAGE_REPLACED 를 수신하는 리시버를 등록하고, 다시 설치하면 곧바로 Log가 뜬다.

 

 

흐름

재우기 :  알람 등록 & 포그라운드 서비스 시작

취소 :  알람 취소 & 실행중인 포그라운드 서비스 종료

완료 : 포그라운드 서비스 종료

재설치 : 취소 & 재우기

 

지금 package_replace 개념을 도입하여 알람만 삭제해주는 메커니즘을 추가로 넣었고,

이전 버전과의 타이머 동작 마이그레이션 테스트까지 완료했다.

 

-- 여기까지 하고 테스트--

 

이전 버전과의 타이머 동작 마이그레이션 테스트까지 완료했다. (예뮬레이터 기준)

내 기기 & 실 기기 테스트 예정

급한건아니니 충분히 더 검토 후에 퍼블리싱 할 예정!

Branch : hotfix/replaced

 

이전 50버전은 애드몹을 테스트로 돌려놓은 임시 브랜치가 hotfix/admob_test 이다.

즉 테스트할때

hotfix/admob_test -> hotfix/replaced 로 마이그레이션 테스트 하면 된다.

 

내기기 & API 33 기기 & 예뮬레이터

마이그레이션 타이머 테스트 완료

알림설정 허용하지 않은 상황에서 마이그레이션 테스트 완료

테마 적용 상황에서 마이그레이션 테스트 완료

타이머 설정하지 않은 상황에서 마이그레이션 테스트 완료

Doze모드 테스트 완료

 

-> '지연알림 해제' commit 버전 기준으로 테스트 한 것임.

 

-------------------------

 

8. base.apk!libmonochrome_64.so

: 특정 기기에서 비정상 종료가 발생해서, 뭐가 문제인가 싶었다. 이는 내 앱의 문제가 아니라, 기기에서의 문제였다.

Android WebView를 시스템적으로 업데이트해줘야 문제를 해결할 수 있다.

https://stackoverflow.com/questions/66361472/app-crashed-with-base-apklibmonochrome-so

 

9. Canvas: trying to draw too large(165888000bytes) bitmap

: 저사양 기기에서 나타나는 문제. Splash 이미지를 소화하지 못해서 발생하는 문제다.

 

10. 간혹 있는 비정상 종료들은 대체적으로 OutOfMemory 관련이다.

: drawable을 분기해주지 않은 점이 문제가 되는 것으로 보인다. 이미지 크기들이 크기 때문.

 

11. 방해금지모드 On 일때, '고마워용' 누를 때 popBackStack 동작 코드 미기재

: 계속 무한루프 돈다. 사용자 경험 저하.. 이 부분 수정

 

12. 전면광고 제거

: 당분간은 전면광고를 제거한다..!

 

13. 리뷰요청

: 리뷰 요청 마케팅 도입..! 앱을 이용해주시는 분들 대상으로 리뷰 안내를 드린다!

: 자세한 내용 : https://hevton.tistory.com/915

 

14. BOOT_COMPLETED & startForegroundService

: 알람을 등록시켜놓고 휴대폰 전원을 꺼버리면, 알람과 포그라운드 서비스는 꺼지지만 '동작 중' 이라는 파일 데이터값은 그대로이기 때문에

재부팅 후 앱을 켜면 의미 없는 triggered 화면이 보여지게 될 수 있다.

 

또한 BOOT_COMPLETED가 언제 receive 될지 보장이 안되므로(1분 ~10분 걸리기도 한다), BOOT_COMPLETED를 통해 알람을 취소한다 한들 곧바로 취소되지 않고, 그 텀에 앱을 들어갔을 때 사용자가 어떻게 느끼게 할지 어렵다. 따라서 BOOT_COMPLETED 를 통해 취소가 아닌 등록으로 가자.

 

+ 타이머 동작 여부 체크를 파일데이터 기준으로 결정한 이유

사실 Service에 companion object를 넣어서 전역변수를 이용한 값을 타이머 동작 체크에 사용해도 딱히 문제는 없을 것 같다.

서비스가 비정상적으로 강제종료된다고 하더라도, START_REDELIVER_INTENT 를 리턴값으로 넘겨주면

시스템이 이러한 경우 서비스를 다시 재실행한다고 한들, 기존의 intent값을 그대로 넘겨주기 때문이다.

 

일단 앱 업데이트 시 어차피 이 파일 데이터값으로, 타이머가 triggered 되었는지 여부를 통해 재요청을 해야한다.

그렇기 때문에 파일 데이터 값은 무조건 필요하다.

 

그리고 앱 재부팅 이후 BOOT_COMPLETE가 호출되기 전까지의 동작을 위해서.. 이렇게 유지하긴 했다.

 

※ 또한 검증하기 어렵거나 힘든 것들을 생각하여 망상을 기준으로 생각했을 때에도, 그래도 더 안전한건 파일데이터라고 생각했기에.

 

그렇지만 다른 목적으로, 파일 데이터 기반의 값 외에도 타이머 서비스의 전역변수도 사용하긴 사용하게 되었다.

포그라운드서비스가 현재 동작하고 있지 않을 때, 포그라운드 서비스 취소를 위한 startForegroundService를 호출하면

startForeground()가 호출되지 않을텐데, 이렇게되면 문제가 된다. 자세한 내용은 15번에서 다룬다.

 

현재 방안

BOOT_COMPLETED : 재시작하고, IS_RUNNING(파일데이터) 가 triggered 되어있으면 정보를 토대로 알람 & 포그라운드 재등록.

 

파일데이터기반의 IS_RUNNING의 의미는 isTriggered (트리거 여부)

서비스 기반의 companion object는 isForeground or isRunning (동작 여부)

 

 

15. startForegroundService 시작 후 (자세히 : https://hevton.tistory.com/908)

(현재 startForeground()로 인해 실행중인 포그라운드 서비스가 없다면) startForegroundService 시작 후에 stopForeground()나 stopSelf만 호출하면 안된다.

 

현재 실행중인 foreground 서비스가 있다면, startForegroundService 시작 후에 stopForeground()나 stopSelf()만을 통해 현재 진행중인 foreground를 닫아주는 역할의 호출이 가능하지만,

 

현재 실행중인 foreground가 없는 상태에서 startForegroundService를 호출했다면 startForeground가 반드시 호출되어야만 한다.

 

이걸 유지성이라고 보면 된다.

Service는 단일 인스턴스라고 했고, 이전에 startForegroundService -> startForeground()로 인해 포그라운드 서비스가 동작중인데,

다시 또 startForegroundService 를 호출하여 현재 실행중인 포그라운드 서비스를 닫고(stopForeground, stopSelf) 끝나는건 문제가 없더라도, 포그라운드 서비스가 동작중이지도 않는데 서비스를 호출해서 일련의 작업을 한다는 것은 에러를 뿜게 되기 마련이다.

 

포그라운드 서비스가 있을 때에는 startForegroundservice 하고 stopForeground, stopSelf만 해도 되는데

포그라운드 서비스가 없을 때에는 startForegroundservice 하고 stopForeground, stopSelf만 하면 안된다

 

: ForegroundSerivce가 동작중이지 않으면, startForegroundService 이후 startForeground 안하면 오류터짐. 실험확인.

 

 

서비스 취소 관련

이유 : startForegroundService는, 현재 startForeground() 되어있는게 없으면 startForeground()를 호출하지 않으면 에러. (포그라운드 서비스가 동작중이지 않기 때문)

방안 1.

startForegroundService로 두되, Service 에서 stopForeground앞에 항상 startForeground를 하나 억지로 둔다.

방안 2.

포그라운드 서비스가 동작중이지 않을 예외가 있을 법한 상황에서, 서비스 중지 목적의 startForegroundService는 startService로 둔다.

 

현재 방안

일단 호출부는 모두 startForegroundService()로 변화한 그대로 이전처럼 유지한다. 위 예외-'서비스가 동작중이지 않은 상황에서 stop을 요청하는 경우' 는 크게 많이 발생하지 않을 것으로 보인다.

그리고 그 예외를 다루기 위해서, Service 전역변수로 isForeground를 도입해서, 현재 포그라운드 서비스가 동작중이지 않은데 startForegroundService -> stop 목적으로 서비스 요청이 들어오면, stop 전에 startForeground를 임의로 생성하게끔 수정했다.

 

isRunning( 파일 시스템 ) -> 전반적인 타이머 동작 여부 판단. 좀 더 의미론적으론 isTriggered의 의미라고 보면 된다.

isForeground( 서비스 companion object) -> stopForeground, stopSelf 목적(취소)의 startForegroundService가 실행되었을 때, 현재 서비스가 포그라운드 상태가 아니라면 startForeground() 호출 없이는 예외가 발생될 것이므로 임의로 startForeground()를 호출해주는 역할.

 

 

Last Test (최종본)

내기기 & API 33 기기 & 예뮬레이터

기본 동작 테스트 (타이머 동작 / 취소)

- 알람 앱 포그라운드 동작, 알람 앱 백그라운드 동작 완료

- 알람 동작 후 앱 다시켜기 완료

- 알람 취소 완료

(알림설정 허용하지 않은 상황에서도 추가 확인 완료)

 

마이그레이션 테스트

- 마이그레이션 타이머 동작 테스트 완료

- 테마 적용 상황에서 마이그레이션 테스트 완료

- 마이그레이션 이후 알람 취소 완료

- 알림설정 허용하지 않은 상황에서 마이그레이션 테스트 완료

- 타이머 설정하지 않은 상황에서 마이그레이션 테스트 완료

 

재부팅 테스트

- 재부팅 이후 알람 취소 완료

- 재부팅 이후 알람 동작 완료

 

Doze모드 테스트

- Doze 모드 진입 후 타이머 제기능 동작 완료

-> 여기서 갑자기 일주일가량 헤매게 되었었다~~! 아래 링크 확인

 

긴 시간 테스트

- 45분 테스트 완료

- 1시간이상 방치 테스트 완료

 

 

이 과정에서 발생한.. 테스트 과정의 시행착오에 대해서는,, 아래 링크로 남겨둔다.

https://hevton.tistory.com/921

 

 

남은일정

내 폰 재부팅, 내 폰 재설치, 내 폰 스포티파이 테스트 후

모든 서브기능 개별로 활성화, 단체로 활성화 테스트 후

딱 이정도만 하고 출시. 단계적 출시로 가자.

 

내 폰, 예전 폰 두 개로

 

예전 폰 업데이트 테스트 완료

예전 폰 재부팅 테스트 완료

예전 폰 백그라운드 기능 테스트 완료

예전 폰 알림형 기능 테스트 완료

예전 폰 방해금지모드 테스트 완료

예전 폰 화면끄기모드 테스트 완료

예전 폰 모든 기능 단체 테스트 완료

 

예전 폰 장기 deep & 2시간 예약 완료

 

이번 폰 업데이트 테스트 완료

이번 폰 재부팅 테스트 완료 

이번 폰 백그라운드 & 알림형 기능 테스트 완료

이번 폰 방해금지모드 테스트 완료

이번 폰 화면끄기모드 테스트 완료

이번 폰 모든 기능 단체 테스트 완료

 

 

3 : 02

1시간 5분 -> 4시 7분 완료

 

4 : 30

1시간 5분 -> 5시 35분 완료

 

11 : 03

1시간 8분 -> 12시 11분 완료

 

4 : 57

2시간 40분 -> 7시 37분 완료

 

 

8 : 03

2시간 40분 -> 10시 43분 완료

 

 

알림설정 안하고 마이그레이션 마지막 완료


 

출시 완료~!

 

 

반응형