모바일 앱 개발
주머니 속 컴퓨터는 배터리·작은 화면·끊기는 네트워크·엄격한 권한이라는 조건 아래서 돈다. iOS와 안드로이드의 구조, OS가 쥔 앱 생명 주기, 크로스플랫폼의 선택지, 화면 단위와 프레임 예산, 오프라인 동기화와 푸시 알림, 서명과 심사까지 작은 예를 직접 계산한 그림으로 따라간다.
목차 (14)
모바일이 다른 점
휴대폰 앱도 결국 프로그램이지만, 데스크톱이나 웹과 같은 습관으로 만들면 곧 벽에 부딪힌다. 네 가지 조건이 다르다. 첫째, 배터리. CPU·화면·무선 칩이 쓰는 전기가 곧 사용자의 하루이고, 그래서 OS는 화면에 없는 앱의 실행을 적극적으로 멈추거나 끊는다. 둘째, 화면과 손가락. 화면은 작고 손끝은 마우스 포인터보다 훨씬 굵다. 세로·가로 회전, 접는 화면, 분할 화면도 있다. 셋째, 네트워크. 지하철·엘리베이터에서 연결은 수시로 끊기고, 이동 통신은 느리거나 요금이 든다. 넷째, 권한과 통제. 카메라·위치·연락처는 사용자의 허락을 받아야 하고, 배포는 앱 스토어의 심사를 거친다.
이 노트는 두 플랫폼이 이 조건을 어떻게 다루는지에 집중한다. 웹 기술 자체는 웹 개발 노트, 터치 목표 크기 같은 사용성 원리는 HCI와 UX 노트에 있다.
iOS의 구조: Swift, SwiftUI와 UIKit, 샌드박스
iOS 앱은 주로 Swift로 쓴다(오래된 코드에는 Objective-C가 남아 있다). 화면을 만드는 틀은 둘이다. 오래 써 온 UIKit은 뷰 객체를 만들고 속성을 바꾸며 화면을 직접 조작하는 명령형 방식이고, SwiftUI는 "상태가 이러면 화면은 이렇다"를 선언하면 상태가 바뀔 때 프레임워크가 다시 그리는 선언형 방식이다. 둘은 한 앱 안에서 섞어 쓸 수 있어, 새 화면은 SwiftUI로 짜고 오래된 화면은 UIKit으로 두는 일이 흔하다. 개발은 macOS의 Xcode에서 한다.
보안의 중심은 샌드박스다. 앱마다 자기 컨테이너 폴더가 있고, 앱은 그 밖의 데이터를 읽거나 쓸 수 없다. 앱 심사 지침도 앱이 정해진 컨테이너 밖에 읽고 쓰지 말라고 못 박는다. 다른 앱의 파일은 물론 사진·연락처 같은 공용 데이터도 파일로 직접 열지 못하고, 권한을 받아 시스템 API를 통해서만 얻는다. 그래서 iOS에서는 "파일을 아무 데나 저장하는" 데스크톱식 설계가 처음부터 안 된다.
안드로이드의 구조: Kotlin, Jetpack Compose, ART
안드로이드 앱의 주 언어는 Kotlin이다(Java 코드도 함께 돈다). 화면은 XML 레이아웃과 View 객체로 만드는 옛 방식과, 안드로이드가 권장하는 최신 UI 툴킷인 Jetpack Compose가 있다. Compose는 SwiftUI처럼 선언형이라, UI를 Kotlin 함수로 묘사하면 상태가 바뀔 때 화면이 저절로 갱신된다. 개발 도구는 Android Studio다.
실행 쪽은 다르다. 소스는 JVM 바이트코드를 거쳐 DEX(Dalvik 실행 파일) 형식으로 묶이고, 기기의 ART(Android Runtime)가 이를 실행한다. ART는 설치 때 dex2oat 도구로 DEX를 그 기기의 기계어로 미리 컴파일(AOT)해 실행을 빠르게 한다. 그 아래에는 안드로이드 프레임워크와 리눅스 커널이 있고, 앱마다 별도의 리눅스 사용자와 프로세스로 돌아 서로 격리된다. 제조사와 기기가 매우 다양하다는 점도 iOS와 다른 큰 조건이다. 화면 크기·밀도·OS 판이 제각각이라 테스트 범위가 넓어진다.
앱 생명 주기: 앱을 끄는 것은 OS다
데스크톱 프로그램은 사용자가 닫을 때 끝난다. 모바일 앱은 아니다. 사용자는 홈으로 나갈 뿐이고, 앱을 언제 멈추고 언제 메모리에서 지울지는 OS가 정한다. 앱이 할 일은 상태가 바뀔 때 OS가 불러 주는 콜백에서 알맞게 저장하고 정리하는 것이다.
- iOS: 화면 단위인 장면(scene)이 연결 전 → 포그라운드(비활성·활성) → 백그라운드 → 정지 상태를 오간다. 비활성은 화면에는 보이지만 입력을 받지 않는 잠깐의 상태(알림 센터를 내렸을 때 등)다. 사용자가 앱을 떠나면 장면은 백그라운드를 거쳐 정지 상태가 되고, 시스템은 자원이 필요하면 백그라운드나 정지 상태의 장면을 언제든 끊어 연결 전 상태로 돌린다.
- 안드로이드: 액티비티가
onCreate→onStart→onResume로 화면에 나오고,onPause→onStop으로 물러난다. 메모리가 모자라면 시스템은 액티비티 하나가 아니라 프로세스를 통째로 정리하며, 백그라운드(Stopped)에 있는 앱이 먼저 대상이 된다. 사용자가 돌아오면 저장해 둔 인스턴스 상태로 액티비티를 다시 만든다.
사용자가 한 일에 따라 두 플랫폼의 상태가 어떻게 바뀌는지 본다. 색칠된 칸이 지금 상태다.
안드로이드에는 함정이 하나 더 있다. 화면 회전 같은 구성 변경이 일어나면 기본적으로 액티비티를 없애고 새로 만든다. 액티비티 객체에 든 상태는 그대로 사라지므로, 상태는 회전을 넘어 살아남는 ViewModel에 두고, 가벼운 UI 상태는 프로세스가 죽어도 복원되는 rememberSaveable에 둔다. onPause는 아주 짧게 끝나야 하므로 무거운 저장은 onStop에서 한다. 두 플랫폼 모두 결론은 같다. "앱이 언제든 말없이 죽을 수 있다"고 가정하고 만든다.
네이티브, 크로스플랫폼, PWA
두 플랫폼에 같은 앱을 내려면 코드를 두 벌 짜거나, 한 벌로 두 곳을 덮는 도구를 쓴다. 고르는 기준은 "무엇을 공유하고, 화면은 누가 그리는가"다. Flutter는 Dart로 쓰고 자체 엔진(네이티브 플랫폼에서는 Impeller)이 모든 픽셀을 직접 그리며, 출시 빌드는 기계어로 컴파일한다. React Native는 React로 쓰지만 실제 화면은 플랫폼의 네이티브 뷰다. Kotlin Multiplatform은 구글이 공식 지원하는, 안드로이드와 iOS 사이에 비즈니스 로직을 나눠 쓰는 방식으로, 로직만 공유하고 UI는 각자 네이티브로 둘 수 있다(JetBrains의 Compose Multiplatform으로 UI까지 공유할 수도 있다). PWA는 아예 앱 스토어 밖에서, 설치할 수 있고 오프라인에서도 도는 웹 앱이다.
| 방식 | 언어 | 화면 | 강점 | 약점 |
|---|---|---|---|---|
| 네이티브 | Swift(iOS), Kotlin(안드로이드) | 플랫폼 UI 툴킷 | 새 OS 기능을 바로 쓰고, 플랫폼다운 느낌과 성능 | 두 벌의 코드와 두 팀 |
| Flutter | Dart | 자체 엔진(Impeller)이 모든 픽셀을 그림 | 두 플랫폼에서 똑같은 화면, 핫 리로드 | 플랫폼 컨트롤의 모양·동작을 따라 그려야 함 |
| React Native | JavaScript·TypeScript(React) | 네이티브 뷰 | 웹 React 경험과 생태계를 그대로 | JS와 네이티브 사이 경계를 이해해야 함 |
| Kotlin Multiplatform | Kotlin(공유 로직) | 각 플랫폼 네이티브 UI(원하면 Compose Multiplatform으로 공유) | 로직만 나눠 쓰고 UI는 네이티브로, 조금씩 도입 가능 | iOS 쪽 팀도 Kotlin 코드를 다뤄야 함 |
| PWA | HTML·CSS·JavaScript | 브라우저 | 스토어 없이 링크로 배포, 한 벌의 코드 | 기기 기능과 백그라운드 동작이 브라우저가 허락하는 만큼 |
정답은 팀과 제품에 달려 있다. 카메라·AR·위젯처럼 OS 기능을 깊게 쓰거나 새 OS 기능을 첫날부터 써야 하면 네이티브가 편하다. 화면 위주의 업무 앱을 작은 팀이 두 플랫폼에 내야 한다면 크로스플랫폼이 경제적이다. 스토어 심사 없이 바로 고치고 링크로 나눠 주는 것이 중요하면 PWA를 검토한다. 서비스 워커·매니페스트 같은 PWA의 재료는 웹 개발 노트에 있다.
화면 밀도와 단위: pt와 dp
휴대폰 화면은 같은 크기에 픽셀 수가 제각각이다. 단추를 픽셀로 정하면 고밀도 화면에서는 손톱만 해진다. 그래서 두 플랫폼은 밀도와 무관한 단위로 레이아웃을 짠다. 안드로이드의 dp는 160dpi(mdpi)를 기준으로 1dp = 1px이 되는 단위이고, 실제 픽셀은 px = dp × (dpi / 160)이다. 48dp 단추는 xhdpi(약 320dpi)에서 96px, xxhdpi(약 480dpi)에서 144px가 된다. iOS의 pt(포인트)도 같은 생각으로, 기기의 배율(@2x·@3x)만큼 픽셀이 된다. 44pt는 @2x에서 88px, @3x에서 132px다.
그래서 비트맵 이미지는 밀도별로 여러 벌(안드로이드는 3:4:6:8:12:16 비율, iOS는 @2x·@3x)을 준비하거나, 크기에 상관없이 선명한 벡터로 둔다. 코드에서 dp를 px로 바꿀 때 공식을 직접 쓰지 말고 플랫폼이 주는 변환 함수를 쓰라는 것도 안드로이드 문서의 당부다. 화면 크기와 비율을 미리 보는 도구는 화면 비율 미리보기에 있다.
상태와 오프라인: 캐시와 동기화 충돌
네트워크가 끊길 수 있다면, 화면이 서버 응답을 직접 기다리게 짜서는 안 된다. 안드로이드의 오프라인 우선 지침은 로컬 저장소를 진실의 원천으로 둔다. 화면은 늘 로컬 데이터베이스(Room 등)를 읽고, 네트워크는 그 로컬 데이터를 갱신하는 일만 한다. 화면 위쪽 층이 네트워크와 직접 이야기하지 않으니, 연결 상태가 바뀌어도 화면이 보는 데이터는 일관된다.
쓰기는 성격에 따라 나눈다. 송금처럼 실시간으로 확인해야 하는 것은 온라인에서만, 분석 로그처럼 늦어도 되는 것은 큐에 쌓았다가, 할 일 목록처럼 잃으면 안 되는 것은 먼저 로컬에 쓰고 나중에 서버에 보낸다(안드로이드에서는 WorkManager가 연결을 기다렸다가 재시도한다). 마지막 경우에 충돌이 생긴다. 오프라인에서 고친 내용과 그사이 다른 기기가 고친 내용이 만나는 것이다. 모바일에서 흔한 해법은 쓰기마다 시각을 붙여 늦은 쪽을 남기는 마지막 쓰기 승리(last write wins)다.
잃으면 곤란한 데이터라면 필드 단위로 합치거나, 충돌을 사용자에게 보여 고르게 하거나, 순서와 무관하게 합쳐지는 자료 구조(CRDT)를 쓴다. 여러 복제본이 어긋나는 문제의 일반론은 분산 시스템 노트에서 다룬다.
푸시 알림: APNs와 FCM
앱이 꺼져 있을 때 소식을 전하려면 서버가 기기를 깨워야 한다. 그런데 앱마다 서버와 연결을 열어 두면 배터리가 버티지 못한다. 그래서 플랫폼이 하나의 연결을 대신 유지한다. 애플의 APNs(Apple Push Notification service)와 구글의 FCM(Firebase Cloud Messaging)이다. 앱 서버는 기기의 주소 대신 기기 토큰을 알고, 토큰과 메시지를 푸시 서비스에 맡긴다. FCM은 애플 기기로 보낼 때 각 플랫폼의 전송 서비스를 거친다.
네 참여자 사이를 오가는 메시지. 단계를 넘기면 한 번에 하나씩 지나간다.
몇 가지 실무 규칙이 있다. 애플 문서는 토큰을 기기에 저장해 두지 말라고 한다. 백업에서 복원하거나, 새 기기에 설치하거나, OS를 다시 깔면 토큰이 바뀌므로 실행할 때마다 새로 받아 서버에 보낸다. 한 사용자가 기기를 여러 대 쓰니 서버는 계정마다 토큰을 여럿 보관한다. 메시지에는 사용자에게 보이는 알림과, 앱 코드가 처리하는 데이터 메시지가 있다. 실을 수 있는 양은 작아서(FCM 문서는 메시지당 4096바이트를 예로 든다) 보통 "새 소식이 있다"는 신호만 보내고 내용은 앱이 서버에서 가져온다. 알림은 사용자의 주의를 쓰는 일이라, 너무 잦으면 알림 권한을 끄거나 앱을 지운다.
권한과 개인정보: 앱 추적 투명성
안드로이드는 권한을 세 종류로 나눈다. 시스템과 다른 앱에 영향이 작은 설치 시 권한(인터넷 등)은 설치할 때 자동으로 주어진다. 위치·연락처처럼 개인 데이터에 닿는 런타임 권한(위험 권한)은 쓰기 전에 사용자에게 묻고, 이미 받았다고 가정하지 말고 쓸 때마다 확인해야 한다. 플랫폼과 제조사만 정의하는 특수 권한은 설정의 별도 화면에서 켠다. iOS도 카메라·위치·사진 같은 접근마다 시스템 대화상자로 묻고, 앱은 그 대화상자에 보일 사용 이유 문구를 적어 두어야 한다.
좋은 권한 요청은 필요한 순간에, 이유와 함께 묻는다. 앱을 켜자마자 권한 다섯 개를 연달아 묻는 대신, 사진 첨부를 누른 순간 사진 권한을 묻는다. 거부해도 앱이 멈추지 않게, 기능을 줄이고 나중에 설정에서 켤 길을 알려 준다. 애플 심사 지침도 사용자가 거부하면 대안(주소를 손으로 입력하기 등)을 주고, 꼭 필요한 데이터만 요청하라고 한다.
광고 추적에는 따로 규칙이 있다. iOS·iPadOS 14.5부터 앱이 사용자를 추적하거나 기기의 광고 식별자(IDFA)에 접근하려면 앱 추적 투명성(ATT) 프레임워크로 허락을 받아야 한다. 애플의 정의로 추적은 내 앱에서 모은 사용자·기기 데이터를 다른 회사의 앱·웹사이트·오프라인 데이터와 이어 맞춤 광고나 광고 측정에 쓰는 것, 그리고 데이터 브로커와 나누는 것이다. 허락이 없으면 광고 식별자는 모두 0으로 채워지고, 이메일을 해시해 맞추는 식의 우회도 허락 없이는 안 된다. 추적에 동의해야 기능을 쓸 수 있게 막는 것도 지침 위반이다. 같은 회사 앱끼리의 분석에는 앱 공급자 식별자(IDFV)를 쓸 수 있다.
기기에 남기는 비밀도 개인정보다. 로그인 토큰이나 비밀번호를 일반 설정 저장소(iOS의 UserDefaults, 안드로이드의 SharedPreferences)에 평문으로 두지 않는다. iOS는 작은 비밀을 암호화해 담는 키체인을, 안드로이드는 암호 키를 기기에서 꺼내기 어렵게 보관하고 키 자체는 내보낼 수 없게 하는 Android Keystore를 둔다. 키스토어의 키로 토큰을 암호화해 저장하는 식으로 함께 쓴다.
딥 링크: 유니버설 링크와 앱 링크
딥 링크는 앱의 첫 화면이 아니라 특정 화면(상품 상세, 초대 수락)으로 바로 여는 링크다. 오래된 방식은 myapp://product/42 같은 사용자 정의 스킴인데, 다른 앱도 같은 스킴을 등록할 수 있고 앱이 없으면 링크가 그냥 실패한다. 그래서 두 플랫폼 모두 내 웹 주소(https://example.com/product/42)를 앱과 묶는 방식을 권한다. 앱이 깔려 있으면 앱이 열리고, 없으면 같은 주소의 웹 페이지가 열린다.
- iOS 유니버설 링크: 앱의 연결 도메인(associated domains) 설정과 웹사이트의
/.well-known/apple-app-site-association파일이 서로를 가리키는 양방향 연결을 만든다(Supporting universal links). - 안드로이드 앱 링크: 웹사이트의
/.well-known/assetlinks.json에 패키지 이름과 서명 인증서 지문을 적어 두면, 기기가 그 파일을 받아 검증한다. 검증된 링크는 어느 앱으로 열지 묻는 선택 창 없이 바로 내 앱을 연다. 안드로이드 6 이상, 구글 서비스가 있는 기기에서 동작한다(About deep links). - 링크는 외부 입력이다: 애플 문서는 유니버설 링크가 앱으로 들어오는 공격 경로가 될 수 있으니 URL 매개변수를 모두 검증하고, 링크만으로 사용자 데이터를 위험하게 하는 동작(결제, 삭제)을 실행하지 말라고 경고한다.
성능: 프레임 예산과 배터리
스크롤과 애니메이션이 매끄러우려면 매 화면 갱신마다 새 프레임이 준비되어야 한다. 60Hz 화면이면 프레임 하나에 16.7ms, 90Hz면 11.1ms, 120Hz면 8.3ms다. 안드로이드 문서의 표현대로, 이 창을 1ms만 넘겨도 프레임이 1ms 늦게 나오는 게 아니라 그 프레임을 통째로 놓친다. 안드로이드는 이렇게 느린 프레임과 별도로, 700ms 넘게 걸린 프레임을 멈춘 프레임으로 센다. 메인 스레드가 5초 넘게 막히면 "응답 없음"(ANR)이 된다.
프레임을 지키는 기본은 메인(UI) 스레드에서 오래 걸리는 일을 하지 않는 것이다. 네트워크·디스크·큰 이미지 디코딩은 다른 스레드로 보내고, 목록은 화면에 보이는 칸만 만들어 재사용하며, 레이아웃을 깊게 겹치지 않는다. 화면이 멈춰 있을 때 일을 덜 하는 것은 곧 배터리 절약이기도 하다. 배터리를 가장 많이 먹는 것은 대개 화면, 무선 통신, 위치(GPS)다. 무선 칩은 한 번 깨면 한동안 켜져 있으므로 자잘한 요청 여러 번보다 모아서 한 번 보내는 편이 낫고, 정밀한 위치가 필요 없으면 대략의 위치만 요청하며, 백그라운드 작업은 OS의 작업 예약기(안드로이드의 WorkManager 등)에 맡겨 충전 중·와이파이 연결 때처럼 조건이 좋을 때 몰아서 하게 한다.
빌드, 서명, 심사, 배포
모바일 앱은 서명된 패키지로만 설치된다. 서명은 "이 앱은 이 개발자가 만들었고 중간에 바뀌지 않았다"는 증명이다. iOS는 애플 개발자 계정의 인증서와 프로비저닝 프로필로 서명하고, 안드로이드는 개발자의 키로 서명한다. 구글 플레이는 Play 앱 서명을 쓴다. 개발자는 업로드 키로 서명한 앱 번들(AAB)을 올리고, 구글은 신원을 확인한 뒤 자기 쪽에 보관한 앱 서명 키로 다시 서명해 배포한다. 2021년 8월 이후 만든 앱은 이 방식이 필수다. 업로드 키는 잃어도 재설정할 수 있지만, 직접 관리하던 서명 키를 잃으면 그 앱에 업데이트를 낼 수 없다.
올린 앱은 심사를 거친다. 애플의 앱 심사 지침은 안전·성능·비즈니스·디자인·법 다섯 장으로 되어 있다. 개발자가 자주 걸리는 곳은 개인정보(수집 전 동의, 개인정보 처리방침, 계정을 만들게 했다면 앱 안에서 계정 삭제), 디지털 상품의 인앱 결제, 그리고 앱의 기능을 바꾸는 코드를 내려받아 실행하지 말라는 2.5.2다. 로그인이 필요한 앱이라면 심사자가 모든 기능을 볼 수 있게 데모 계정이나 데모 모드를 함께 내야 하고, 충돌하거나 미완성인 빌드는 거절된다. 출시 전에는 TestFlight(iOS)나 테스트 트랙(구글 플레이)으로 일부 사용자에게 먼저 돌려 본다.
웹은 서버에 올리면 모두가 새 판을 받지만, 앱은 사용자가 업데이트하기 전까지 옛 판이 계속 돈다. 그래서 서버 API는 옛 앱 판도 한동안 받아 주도록 호환성을 지키고, 큰 변경은 원격 설정·기능 플래그로 켜고 끈다. 그래도 보안 문제처럼 옛 판을 더 둘 수 없을 때를 대비해, 앱이 켜질 때 서버에서 ‘최소 지원 판’을 받아 그보다 낮으면 업데이트를 안내하는 장치를 처음부터 넣어 둔다. 구글 플레이는 이를 위한 인앱 업데이트 API를 주는데, 업데이트하고 다시 시작해야만 계속 쓸 수 있는 전체 화면 ‘즉시’ 흐름과, 쓰면서 뒤에서 받는 ‘유연’ 흐름이 있다. 빌드·서명·업로드를 자동화하는 파이프라인은 CI/CD 노트와 같은 원리다.
테스트와 크래시 리포트
모바일 테스트는 층이 같다(테스트 노트). 로직은 기기 없이 도는 단위 테스트로 넓게 덮고, 화면은 플랫폼의 UI 테스트 도구(iOS의 XCTest UI 테스트, 안드로이드의 Espresso·Compose 테스트)로 핵심 흐름만 잡는다. 다른 점은 기기의 다양성이다. 에뮬레이터·시뮬레이터로 대부분을 돌리되, 느린 저가 기기, 작은 화면, 오래된 OS 판, 다른 언어 설정, 큰 글자 설정, 비행기 모드 같은 조건은 실제 기기 또는 클라우드 기기 농장에서 확인한다.
출시 뒤에는 크래시 리포트가 테스트의 연장이다. 앱이 죽으면 SDK가 스택 정보를 모아 다음 실행 때 서버로 보낸다. 출시 빌드는 최적화·난독화되어 스택에 주소만 남으므로, 빌드할 때 만든 기호 파일(애플 플랫폼은 dSYM)로 함수 이름과 줄 번호를 되살린다(기호화). Firebase Crashlytics 같은 도구는 스택과 예외 메시지를 보고 같은 실패 지점의 사건을 이슈 하나로 묶어, 영향받은 사용자가 많은 것부터 보여 준다.
핵심 자료
웹 쪽은 웹 개발 노트, 화면 설계의 원리는 HCI와 UX 노트에 있다.
| 자료 | 종류 | 요지 | 링크 |
|---|---|---|---|
| The activity lifecycle | Android 문서 | 콜백 여섯 개와 상태, 화면 회전 때의 재생성, 메모리가 모자랄 때 프로세스를 정리하는 순서. | Android Developers |
| Managing your app's life cycle | Apple 문서 | iOS 장면(scene)의 연결 전·포그라운드·백그라운드·정지 상태와 옮겨 가는 길. | Apple Developer |
| Support different pixel densities | Android 문서 | dp와 px의 관계, 밀도 묶음(mdpi~xxxhdpi)과 배율, 비트맵을 밀도별로 준비하는 법. | Android Developers |
| Build an offline-first app | Android 문서 | 로컬 저장소를 진실의 원천으로 두는 구조, 쓰기 전략 셋, 끌어오기·밀어주기 동기화와 충돌 해결. | Android Developers |
| Registering your app with APNs | Apple 문서 | 기기 토큰을 받아 서버로 보내는 과정과, 토큰을 기기에 캐시하지 말아야 하는 이유. | Apple Developer |
| User Privacy and Data Use | Apple 안내 | 앱 추적 투명성(ATT)이 말하는 "추적"의 정의, 광고 식별자, 허락 없이 할 수 있는 것과 없는 것. | Apple Developer |
| Slow rendering | Android 문서 | 주사율별 프레임 예산, 느린 프레임과 멈춘 프레임(700ms)의 기준, 원인을 찾는 도구. | Android Developers |
| App Review Guidelines | Apple 지침 | 안전·성능·비즈니스·디자인·법 다섯 장. 개인정보(5.1)와 실행 코드 내려받기 금지(2.5.2)를 꼭 읽는다. | Apple Developer |
- The activity lifecycle — Android Developers
- Managing your app's life cycle — Apple Developer
- Support different pixel densities — Android Developers
- Build an offline-first app — Android Developers
- Registering your app with APNs — Apple Developer
- User Privacy and Data Use — Apple Developer
- Slow rendering — Android Developers
- App Review Guidelines — Apple Developer
- Supporting universal links in your app — Apple Developer
- Supporting associated domains — Apple Developer
- About deep links — Android Developers
- Configure website associations (assetlinks.json) — Android Developers
- Keychain services — Apple Developer
- Android Keystore system — Android Developers
- In-app updates — Android Developers
- Permissions on Android — Android Developers
- Android runtime (ART) — Android Open Source Project
- Jetpack Compose — Android Developers
- Kotlin Multiplatform — Android Developers
- Flutter architectural overview — Flutter
- What are Progressive Web Apps? — web.dev
- Images (scale factors) — Apple Human Interface Guidelines
- Firebase Cloud Messaging — Firebase
- Use Play App Signing — Android Developers
- Firebase Crashlytics — Firebase