Android OS 9 Pie (Developer)

참고 : https://developer.android.com/about/versions/pie/android-9.0-changes-all

동작 변경사항: 모든 앱

요약


전원 관리와 관련하여 앱 대기 버킷 추가 및 배터리 세이버 개선

개인정보 보호정책 변경

  • 백그라운드 센서 액세스 제한
  • 통화 로그/전화번호 액세스 제한
  • Wi-Fi 관련 액세스 제한

리플렉션이나 JNI를 이용한 비 SDK 인터페이스 사용 제한

보안 동작 관련하여 암호화 알고리즘 구현 변경 및 ASEC 지원 제거됨

ICU 라이브러리 60 업데이트로 시간대 및 통화 텍스트 처리가 일부 변경됨

Android 테스트를 위한 JUnit 기반 클래스 재구성

Java UTF 디코더에서 엄격한 Unicode 표준 준수

인증서를 사용한 서버 호스트 이름 확인

StrictMode를 통한 네트워크 주소 조회시 발생하는 네트워크 위반 감지

소켓 태그 제거 및 사용 가능 바이트 크기 보고 변경

xt_qtaguid 폴더의 파일을 더 이상 앱에 직접 사용 불가함

비 액티비티 컨텍스트에서 FLAG_ACTIVITY_NEW_TASK 전달하지 않을 시 액티비티 시작 안됨

화면회전 모드인 세로 회전 모드가 회전 잠금 기능으로 변경

비표준 ClassLoader를 사용하여 Apache HTTP 클라이언트 사용 제한

전/후방 카메라가 복수일 것을 가정한 카마레 리스트 지원


전원 관리

Android 9에서 기기 전원 관리 개선을 위한 새로운 기능이 도입되며 다음의 두가지 범주로 구분됨.

  • 앱 대기 버킷 : 사용 패턴에 따른 앱의 기기 리소스 액세스 제한. Android 9의 새 기능.
  • 배터리 세이버 개선 : 활성화시 시스템이 모든 앱에 제한 적용. Android 9에서 개선된 기존 기능.

앱 대기 버킷

새로운 배터리 관리 기능.

최근에 사용 빈도에 따라 앱의 리소스 요청에 우선순위를 부여하고 사용에 제한을 부여함.

  • Active : 현재 사용 중인 앱. 시스템은 앱의 작업, 알람 또는 FCM 메시지에 어떤 제한도 적용하지 않음.
    • 앱이 액티비티를 시작함.
    • 앱이 포그라운드 서비스를 실행 중.
    • 앱에 포그라운드 앱이 사용하는 콘텐츠 프로바이더와 동기화 어댑터가 있음.
    • 사용자가 앱에서 알림을 클릭함.
  • Working Set : 앱이 자주 실행되지만 현재는 활성이 아닌 앱. 작업을 실행하고 알람을 트리거 하는 기능에 가벼운 제한을 적용함.
    • 사용자가 거의 매일 실행하는 소셜 미디어 앱은 Working Set일 가능성이 높음.
    • 앱이 간접적으로 사용될 경우에도 Working Set으로 승격됨.
  • Rare : 자주 사용되지 않는 앱. 작업을 실행하고 알람을 트리거하고 우선순위가 높은 FCM 메시지를 수신하는 앱의 기능에 엄격한 제한을 적용함. 앱의 인터넷 연결 기능도 제한함.
    • 사용자가 특정 호텔에 머무는 동안에만 실행되는 호텔 앱은 Rare 버킷에 있을 수 있음.
  • Never : 설치되었지만 한번도 실행되지 않음 앱. 매우 엄격한 제한을 적용함.

시스템은 각 앱이 얼마나 자주 사용될지 판별하기 위해 머신러닝을 사용하는 사전 로드된 앱을 사용할 수도 있음.

아닌 경우 최근 사용되었는지에 따라 앱을 정렬함.

버킷은 아래 것들을 판별함.

  • 앱 작업이 얼마나 자주 실행되는지
  • 앱이 얼마나 자주 알람을 트리거 하는지
  • 우선순위가 높은 FCM 메시지를 얼마나 자주 수신할 수 있는지

이러한 제한은 기기가 배터리 전원 사용 중인 경우만 적용되며, 충전 중일 때는 제한을 적용하지 않음.

각 제조사가 버킷에 할당되는 방식에 대해 자체적인 기준을 설정할 수도 있음.

앱에서 UsageStateManager.getAppStandbyBucket()을 호출하여 현재 어떤 버킷인지 확인 가능함.

Doze모드 허용 목록에 있는 앱은 버킷 기반의 제한을 받지 않음.

모범사례
  • 앱을 특정 버킷에 넣기 위해 시스템 조작을 시도하지 말 것.

  • 앱에 시작 액티비티가 없는 경우 앱이 Active 버킷 승격되지 않을 수 있음.

  • 앱의 알림이 실행되지 않을 경우 사용자가 알림과의 상호작용을 통해 Active 버킷으로 앱 승격을 트리거할 수 없음.

  • 우선순위가 높은 FCM 메시지 수신시 앱이 알림을 표시하지 않는다면 사용자가 앱과 상호작용하여 앱을 Active 버킷으로 승격시킬 기회가 부여되지 않음.

    참고 : 사용자가 반복해서 알림을 해제하는 경우 시스템은 향후에 알림을 차단하는 옵션을 사용자에게 제공함.

  • 앱이 여러 패키지에 분할된 경우 이들 패키지가 다른 버킷에 잇을 수 있으며, 그 결과 액세스 레벨이 달라질 수 있음.

배터리 세이버 개선

Android 9에서 배터리 세이버 모드가 개선되었으며, 제조사가 적용되는 정확한 제한사항을 결정함.

AOSP 빌드에서 시스템은 아래 제한을 적용함.

  • 시스템은 유휴 상태가 되기를 기다리는 대신 적극적으로 앱을 대기모드에 둠.
  • 백그라운드 실행 제한은 대사 API 레벨 상관없이 모든 앱에 적용됨.
  • 화면이 꺼지면 위치 서비스가 비활성화될 수도 있음.
  • 백그라운드 앱에는 네트워크 액세스 권한이 없음.

테스트

ADB를 사용하여 여러가지 전원 관리 기능을 테스트할 수 있음.

Doze 모드 전환 등은 링크 참조 : https://developer.android.com/training/monitoring-device-state/doze-standby

앱 대기 버킷
  • 앱의 버킷 변경시

    $ adb shell am set-standby-bucket [packagename] active|working_set|frequent|rare
  • 여러 패키지 동시 설정시

    $ adb shell am set-standby-bucket package1 bucket1 package2 bucket2...
  • 앱이 어떤 버킷에 있는지 확인

    $ adb shell am get-standby-bucket [packagename]

    [packagename] 파라미터를 전달하지 않을 경우 모든 앱의 버킷을 나열함. 런타임에서는 UsageStatsManager.getAppStandbyBucket()을 호출하여 찾을 수 있음.

배터리 세이버

저전력 조건하에서 앱 동작 테스트를 위한 명령들

참고 : 기기의 Settings > Battery saver 화면을 사용하여 기기를 배터리 세이버 모드로 전환 가능함.

  • 기기의 플러그를 뽑고 시뮬레이션할 경우

    $ adb shell dumpsys battery unplug
  • 저전력 조건 하에서 기기 테스트

    $ adb shell settings put global low_power 1
  • 테스트 종료 후, 수동 기기 설정 실행취소시

    $ adb shell dumpsys battery reset

개인정보 보호정책 변경사항

백그라운드 앱의 기기 센서 액세스를 제한.

Wi-Fi 스캔으로 검색되는 정보를 제한.

전화동화/상태, Wi-Fi 스캔과 관련하여 새로운 권한 규칙 과 권한 그룹을 적용

백그라운드에서 센서 액세스 제한

  • 앱이 마이크나 카메라 액세스 금지
  • 연속 보고 모드를 사용하는 센서(가속도계 및 자이로스코프 등)는 이벤트 수신하지 않음.
  • 변경시 또는 원샷 보고 모드를 사용하는 센서는 이벤트를 수신하지 않음.

Android 9이 실행되는 기기에서 앱이 센서 이벤트를 감지해야하는 경우 포그라운드 서비스 사용해야 함.

통화 로그 액세스 제한

CALL_LOG 권한 그룹 도입.

READ_CALL_LOGWRITE_CALL_LOGPROCESS_OUTGOING_CALLS 권한이 이동함. (기존엔 PHONE 권한 그룹)

앱에서 통화 로그 액세스나 발신 통화 처리시 CALL_LOG 권한 그룹으로부터 명시적으로 권한 요청을 해야함.

그렇지 않으면 SecurityException 발생함.

참고 : 이런 권한은 런타임에 부여되므로 사용자가 앱에서 액세스 못하도록 거부할 수 있으므로 적절히 처리해야 함.

전화번호 액세스 제한

수신 및 발신 통화와 관련된 전화번호 READ_CALL_LOG 권한이 있어야 함.

  • PHONE_STATE 인텐트 액션에서 전화번호를 읽으려면 READ_CALL_LOGREAD_PHONE_STATE 권한 모두 필요함.
  • onCallStateChanged()에서 전화번호를 읽으려면 READ_CALL_LOG 권한만 필요함.

Wi-Fi 위치 및 연결 정보 액세스 제한

Android 9 은 이전보다 앱이 Wi-Fi 스캔하는데 필요한 권한 요구사항이 엄격해짐.

제한이 getConnectionInfo() 메서드에도 적용되며 이 메서드는 현재 Wi-Fi 위치를 설명하는 WifiInfo객체를 반환함.

앱에 아래 권한이 있을 경우 이 객체의 메서드만 사용하여 SSID 및 BSSID 값을 검색할 수 있음.

  • ACCESS_FINE_LOCATION 또는 ACCESS_COARSE_LOCATION
  • ACCESS_WIFI_STATE

Wi-Fi 서비스 메서드에서 정보 삭제

다음 이벤트와 브로드캐스트는 사용자 위치나 개인 식별 데이터와 관련된 정보를 수신하지 않음.

  • WifiManager의 getScanResults()와 getConnectionInfo() 메서드
  • WifiP2pManager의 discoverServices()와 addServiceRequest() 메서드
  • NETWORK_STATE_CHANGED_ACTION 브로드캐스트

NETWORK_STATE_CHANGED_ACTION 시스템 브로드캐스트에는 SSID(EXTRA_SSID), BSSID(EXTRA_BSSID) 또는 연결정보(EXTRA_NETWORK_INFO)가 포함되지 않음.

위 정보가 필요할 경우 getConnectionInfo() 호출.

기기 위치 설정에 따라 전화정보 제공

기기 위치를 비활성화 했다면, 아래 메서드는 결과를 제공하지 않음.

  • getAllCellInfo()
  • listen()
  • getCellLocation()
  • getNeighboringCellInfo()

비 SDK 인터페이스 사용에 대한 제한

플랫폼에서는 일부 비 SDK 메서드와 필드의 사용을 제한함.

리플레션을 통한 직접 액세스를 시도하든 아니면 JNI를 사용하든 간에 모두 적용됨.

보안 동작 변경사항

기기 보안 변경사항

#####TLS 구현 변경사항

  • SSLSocket의 인스턴스가 생성되는 동안 연결에 실패할 경우, 시스템에서 NullPointerException 대신 IOException 발생
  • 어떤 close_notify 경고가 발생하더라도 SSLEngine 클래스가 깔끔하게 처리함.

#####더욱 엄격해진 SECCOMP 필터

Android 8.0(API 26)에 포함된 SECCOMP 필터의 확장 프로그램. 권한을 가진 syscall을 사용하는 앱에만 영향.

암호화 변경사항

#####매개 변수 및 알고리즘의 Conscrypt 구현

Android 9은 Conscrypt 에서 알고리즘 매개변수의 추가적인 구현을 제공함.

매개변수에는 AES, DESEDE, OAEP, EC가 포함됨.

위 매개변수의 Bouncy Castle 버전과 다수의 알고리즘이 Android 9에서 지원이 중단됨.

참고 : Conscrypt의 EC 매개변수 구현에서는 명명된 곡선만 지원함.

Android 8.1(API 27) 이하를 대상으로 하는 앱의 경우, 지원 중단된 알고리즘 중 하나인 Bouncy Castle 구현을 요청한다면 Android 9에서는 NoSuchAlgorithmException을 발생함.

기타 변경사항
  • PBE 키를 사용하는 경우, Bouncy Castle이 초기화 벡터(IV)를 예상하는데 앱이 이것을 제공하지 않으면 경고 표시됨.
  • Conscrypt의 ARC4 암호화 구현에서는 ARC4/ECB/NoPadding 또는 ARC4/NONE/NoPadding을 지정할 수 있음.
  • Crypto JCA(Java Cryptography Architecture) 제공자는 제거됨. SecureRandom.getInstance("SHA1PRNG", "Crypto") 를 호출하면 NoSuchProviderException이 발생함.
  • 앱이 키 구조보다 더 큰 버퍼로부터 RSA 키를 파싱하는 경우, 더 이상 예외 발생하지 않음.

Android 보안 암호화된 파일은 지원되지 않음

Android 9에서는 Android 보안 암호화 파일(ASEC)에 대한 지원이 완전히 제거됨.

Android 2.2(API 8)에서 Android는 SD 카드의 앱 기능을 지원하기 위해 ASEC 도입.

Android 6.0(API 23)에서 플랫폼은 개발자가 ASEC 대신 사용할 수 있는 어댑터블 스토리지 기기 기술을 도입함.

ICU 라이브러리에 대한 업데이트

Android 9는 ICU 라이브러리 버전 60을 사용함. Android 8.0(API 26), Android 8.1(API 27)은 ICU 58 사용.

ICU는 android.icu package 아래에 공개 API를 제공하는데 사용하고 Android 플랫폼 국제화 지원에 사용함.

ex) java.utiljava.textandroid.text.format 에서 Android 클래스 구현시 사용됨.

ICU 60 업데이트에는 Emoji 5.0 데이터 지원 및 개선된 날짜/시간 형식등의 변경사항이 다수 포함됨.

  • 플랫폼 시간대 처리 방식 변경
    • GMT 및 UTC 처리로 둘은 더이상 동의어가 아님. "GMT", "Etc/GMT", "UTC", "Etc/UTC", "Zulu" 같은 시간대의 서식 및 파싱엥 영향
    • java.text.SimpleDateFormat에서 ICU 사용하여 UTC/GMT 표시 이름 제공 zzzz 파싱시 "Universal Coordinated Time", "Greenwich Mean Time" 과 같은 문자열 인식함.
    • java.text.DateFormatSymbols.getZoneStrings() 동작이 변경됨.
    • 아시아/하노이 시간대 인식 안됨. java.util.TimeZones.getAvailabeIds()는 값을 반환하지 않으며, java.util.TimeZone.getTimeZone()은 이를 인식 못함.
  • android.icu.text.NumberFormat.getInstance(ULocale, PLURALCURRENCYSTYLE).parse(String) 메서드의 경우 합법적 통화 텍스트를 파싱할 때도 ParseException 발생할 수 있음. 이 문제를 피하려면 스타일의 통화 텍스트에 NumberFormat.parseCurrency를 사용함. 이 메서드는 Android 7.0(API 24) 이후 버전에서 PLURALCURRENCYSTYLE을 사용할 수 있음.

Android 테스트 변경사항

프레임워크에서 라이브러리 제거

Android 9에서는 JUnit 기반 클래스를 android.test.baseandroid.test.runnerandroid.test.mock 세가지 라이브러리로 재 구성함.

테스트 모음 빌드 변경사함

TestSuiteBuilder 클래스의 addRequirements() 메서드가 제거되고 클래스가 지원 중단됨.

Java UTF 디코더

UTF-8은 Android 기본 문자 집합이며, UTF-8 바이트 시퀀스는 String 생성자에 의해 디코딩될 수 있음.

Android 9의 UTF-8 디코더는 이전보다 엄격하게 Unicode 표준을 준수함.

  • 비최단(non-shortest) 형식의 UTF-8(예: <C0, AF>)은 잘못된 형식으로 간주됨.
  • 대리(surrogate) 형식의 UTF-8(예: U+D800..U+DFFF)는 잘못된 형식으로 간주됨.
  • 최대 서브파트는 단일 U+FFFD로 대체. 예를 들어, 바이트 시퀀스 "41 C0 AF 41 F4 80 80 41"에서 최대 서브파트는 "C0," "AF" 및 "F4 80 80". "F4 80 80"은 "F4 80 80 80"의 초기 서브시퀀스가 될 수 있지만, "C0"은 올바른 형식의 코드 단위 시퀀스의 초기 서브시퀀스가 될 수 없으므로 출력은 "A\ufffd\ufffdA\ufffdA"여야 함.
  • 수정된 UTF-8 / CESU-8 시퀀스를 Android 9 이상에서 디코딩하려면 DataInputStream.readUTF() 메서드나 NewStringUTF() JNI 메서드를 사용.

인증서를 사용하여 호스트 이름 확인

호스트 이름을 확인하기 위해 서버는 일치하는 SAN이 포함된 인증서를 제공해야 함. (CN을 사용한 대체 불가)

호스트 이름과 일치하지 않는 SAN이 포함된 인증서는 더 이상 신뢰할 수 없음.

네트워크 주소 조회시 네트워크 위반 발생 가능

이름 확인을 요구하는 네트워크 주소 조회에는 네트워크 I/O가 포함될 수 있으며 이 조회는 차단 작업으로 간주됨.

메인 스레드의 차단 작업은 일시 중지나 버벅거림 현상을 유발할 수 있음.

StrictMode 클래스는 개발자가 코드에서 문제를 감지하도록 돕는 개발도구.

Android 9 이상에서 StrictMode는 이름 확인을 요구하는 네트워크 주소 조회로부터 발생하는 네트워크 위반을 감지함.

StrictMode를 활성화한 상태로 앱을 출시하면 안됨.

숫자 IP 주소 확인은 차단 작업으로 간주되지 않음.

소켓 태그 지정

기존 Android에서는 setThreadStatsTag()메서드를 사용하여 소켓에 태그를 다는 경우 바인더 IPC를 ParcelFileDescriptor컨테이너와 함께 사용하여 또 다른 프로세스로 전송시 이 소켓의 태그가 해제됨.

Android 9 이상에서는 바인더 IPC 사용하여 또 다른 프로세스로 전송시 소켓 태그가 유지됨.

이러한 변경은 네트워크 트랙픽 통계에 영향을 미칠 수 있음. (queryDetailsforUidTag() 메서드 사용시)

다른 프로세스로 전송한 소켓의 태그를 제거하고 싶다면 untagSocket()을 호출.

소켓에서 사용 가능한 바이트 크기가 보고됨

shutdownInput() 메서드를 호출한 다음 available() 메서드를 호출하면 0이 반환됨.

xt_qtaguid 폴더의 파일을 더 이상 앱에 사용 불가함

Android 9부터는 앱에서 /porc/net/xt_qtaguid 폴더에 있는 파일을 직접 읽을 수 없음.

이러한 파일을 사용하는 공개 API는 계속 작동함. (TrafficStatsNetworkStatsManager 등)

qtaguid_tagSocket() 같은 지원되지 않는 cutils 함수는 작동하지 않을 수 있음.

FLAG_ACTIVITY_NEW_TASK 요구사항 적용됨.

Android 9에서는 인텐트 플래그 FLAG_ACTIVITY_NEW_TASK를 전달하지 않을 경우 비 액티비티 컨텍스트에서 액티비티를 시작할 수 없음.

이 플래그를 전달하지 않고 액티비티를 시작하려고 하면 액티비티가 시작되지 않고, 시스템이 로그 출력함.

참고 : Android 7.0(API 24) 이전 버전부터 적용되었으나 Android 7.0 버그로 인해 적용되지 않았음.

화면 회전 변경사항

Android 9 부터 세로 회전모드가 회전 잠금으로 변경되었고 이 모드는 자동 회전이 꺼진 경우 활성화됨.

기존에는 자동 회전 및 세로 회전 모드 였으며, 자동 회전 모드에는 변화 없음.

특정 방향을 요청하는 Activity(screenOrientation=landscape같은) 는 사용자 잠금 기본설정을 무시하고 동작됨.

화면 방향동작
unspecified, user자동 회전 및 회전 잠금 모드에서 Activity가 세로 모드 또는 가로 모드(및 반전)로 렌더링될 수 있습니다. 세로 모드 및 가로 모드 레이아웃을 모두 지원할 것으로 예상됩니다.
userLandscape자동 회전 및 회전 잠금 모드에서 Activity가 가로 모드 또는 반전 가로 모드로 렌더링될 수 있습니다. 가로 모드 레이아웃만을 지원할 것으로 예상됩니다.
userPortrait자동 회전 및 회전 잠금 모드에서 Activity가 세로 모드 또는 반전 세로 모드로 렌더링될 수 있습니다. 세로 모드 레이아웃만을 지원할 것으로 예상됩니다.
fullUser자동 회전 및 회전 잠금 모드에서 Activity가 세로 모드 또는 가로 모드(및 반전)로 렌더링될 수 있습니다. 세로 모드 및 가로 모드 레이아웃을 모두 지원할 것으로 예상됩니다. 회전 잠금 모드 사용자에게는 반전 세로 모드(대개 180º 반전)로 잠글 수 있는 옵션이 제공됩니다.
sensor, fullSensor, sensorPortrait, sensorLandscape회전 잠금 모드 기본 설정이 무시되고 자동 회전이 활성화된 것처럼 처리됩니다. 이 동작은 UX를 매우 신중하게 고려하여 예외적인 경우에만 사용해야 합니다.

Apache HTTP 클라이언트 지원 중단은 비표준 ClassLoader가 있는 앱에 영향을 미침

Android 6.0에서 Apache HTTP 클라이언트에 대한 지원이 제거됨.

비표준 ClassLoader를 사용하여 org.apache.http.* 에서 클래스를 찾을 때 앱 ClassLoader에 권한을 위임해야 함.

이 경우 Android 9 이상에서는 NoClassDefFoundError 가 발생함.

카메라 나열

getCameraIdList()를 호출하면 모든 이용 가능한 카메라를 찾아낼 수 있음.

기기에 후방/전방 카메라 하나만 있는 것으로 가정하지 말아야 함. 각 카메라가 2개 이상일 수 있음.

Android OS 9 Pie (Developer)

참고 : https://developer.android.com/about/versions/pie/android-9.0

기능 및 API

요약


Wi-Fi RTT를 이용한 실내 위치추적 지원

디스플레이 컷아웃 지원

알림 기능 향상 및 채널별 알림 일시정지 단순화

멀티 카메라 및 외부 카메라 지원

이미지 디코더 추가 및 애니메이션 이미지 지원 추가

HDR VP9 프로파일 2 지원, HEIF 이미지 인코딩 추가, MediaDRM/AAudio/AudioEffect 기능 추가

JobScheduler 네트워크 관련 기능 강화

Neural Networks API 1.1 확장 및 개선

자동 완성 프레임워크 개선

보안 강화 및 기능 추가

  • 앱 내 중요한 의사 확인용 Android Protected Confirmation 추가
  • 통합 생체 인식 다이얼로그 지원
  • HW 모듈인 StrongBox Keymaster 지원
  • Keystore로 키 가져오기 보홈
  • 키 순환을 포함한 APK 서명 지원
  • 잠금 해제된 장치에서만 키 암호 해독을 허용하는 옵션 추가
  • Triple Data Encryption Algorithm(Triple DEA) 지원

클라이언트 쪽 암호화된 백업 지원, 백업을 위한 기기 컨디션 지정

클라이언트 측 암호화 백업 및 백업에 필요한 기기 조건 정의 추가

접근성 관련 기능 추가 및 향상

  • 탐색 의미 체계
    • 접근성 창 제목
    • 제목 기반 탐색
    • 그룹 탐색 및 출력
  • 도움말과 전역 작업 등 추가된 편의 작업
  • 창 변경 세부 정보

회전을 고정 및 수동으로 제어할 수 있는 기능 추가(시스템바 버튼)

텍스트 관련하여 렌더링 개선, 돋보기, Smart Linkify 등의 기능을 플랫폼에서 지원


Wi-Fi RTT를 이용한 실내 위치추적

Android 9에서는 Wi-Fi Round-Trip-Time(RTT-) 로 알려진 IEEE 802.11mc Wi-Fi 프로토콜 지원.

앱에서 실내 위치 추적에 활용 가능.

하드웨어 지원이 되는 Android 9 기기는 RTT API를 사용하여 인근한 RTT 가능 Wi-Fi AP 까지의 거리를 측정할 수 있음. (위치 서비스 사용 설정되어 있어야 하고 앱에서 ACCESS_FINE_LOCATION 권한 필요)

RTT를 사용하기 위해 AP와 연결할 필요 없음. 기기만 AP와 거리를 측정할 뿐 AP에는 정보가 없음.

3개 이상의 AP의 거리를 측정하는 경우 약 1~2미터 내의 오차로 기기 위치를 측정할 수 있음.

이런 정확도를 이용하여 건물 내 탐색 및 미세 위치 기반 서비스 등의 새로운 경험 구현 가능함.

디스플레이 컷아웃 지원

Android 9에서는 카메라와 스피커를 위한 디스플레이 컷아웃이 포함된 edge-to-edge 스크린 지원함.

DisplayCutout 클래스를 사용하여 내용을 표시하지 않는 영역의 위치와 모양을 찾을 수 있음.

새 윈도우 레이아웃 속성인 layoutInDisplayCutoutMode 를 사용하여 기기 컷아웃 주변의 콘텐츠를 배치할 수 있음.

  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

개발자 옵션에서 디스플레이 컷아웃 시뮬레이션 사용 가능.

Developer options -> Drawing -> Simulate a display with a cutout

알림

향상된 메시지 환경

Android 7.0 (API 24)부터 메시지에 회신하거나 알림에서 직접 텍스트 입력이 가능했음.

Android 9는 다음 기능을 통해 기능을 향상시킴.

  • 단순화된 대화 참여자 지원 : Person 클래스는 대화 참여자 식별에 쓰임. addMessage()와 같은 다른 많은 API는 이제 CharSequence 대신 Person 클래스 사용함. Person 클래스는 Builder 패턴도 지원함.

  • 이미지 지원 : 메시징 알림에 이미지 표시함. 메시지에서 setData()를 사용하여 이미지 표시 가능

    // Create new Person.
    Person sender = new Person()
            .setName(name)
            .setUri(uri)
            .setIcon(null)
            .build();
    // Create image message.
    Message message = new Message("Picture", time, sender)
            .setData("image/", imageUri);
    Notification.MessagingStyle style = new Notification.MessagingStyle(getUser())
            .addMessage("Check this out!", 0, sender)
            .addMessage(message);

  • 초안같은 응답들 저장 : 사용자가 우연히 메시징 알림을 닫으면 앱에서 EXTRA_REMOTE_INPUT_DRAFT를 검색할 수 있음. 이 기능을 사용하여 사용자가 답장을 완료할 수 있도록 앱의 텍스트를 미리 채울 수 있음.

  • 그룹대화 식별자 : setGroupConversation()을 사용하여 대화가 그룹인지 식별할 수 있음.

  • 인텐트를 위한 의미론적 동작을 설정 : setSemanticAction() 메서드를 사용하면 mark as readdeletereply 같은 의미론적인 의미를 가진 동작을 부여할 수 있음.

  • 스마트 응답 : 메시징 앱에서 RemoteInput.setChoices() 를 사용하여 일련의 표준 응답을 제공할 수 있음. 

채널 설정, 브로드캐스트 및 알림 일시중지

Android 8.0 에서 알림 채널이 도입되어 각 알림 유형에 대해 사용자가 맞춤 설정할 수 있는 채널을 만들 수 있음.

Android 9에서는 이러한 변경을 통해 알림 채널 설정을 단순화함.

  • 채널 그룹 차단 : 사용자는 앱의 알림 설정에서 전체 채널 그룹을 차단할 수 있음. isBlocked() 메서드로 식별할 수 있으며, 이 경우 이 그룹에 있는 채널에는 어떤 알림도 보내지 않음. 앱에서는 getNotificationChannelGroup() 메서드를 사용해 현재 채널 그룹 설정을 쿼리할 수 있음.
  • 새로운 브로드캐스트 인텐트 타입 : Android 시스템은 이제 알림 채널 및 채널 그룹의 차단 상태가 변경되면 브로드캐스트 인텐트를 보냄. 차단된 채널 또는 그룹을 소유한 앱은 이러한 인텐트를 받아 대응할 수 있음.
  • NotificationManager.Policy에는 3가지 Do-Not-Disturb 우선순위 카테고리가 있음.
    • PRIORITY_CATEGORY_ALARMS : 알람의 우선시 함.
    • PRIORITY_CATEGORY_MEDIA : 미디어 및 음성 탐색과 같은 미디어 소스의 사운드를 우선시 함.
    • PRIORITY_CATEGORY_SYSTEM : 시스템 사운드 우선시 함.
  • NotificationManager.Policy에는 시각적 반응을 막는 7가지 Do-Not-Disturb 상수가 있음.
    • SUPPRESSED_EFFECT_FULL_SCREEN_INTENT : 알림이 전체화면 액티비티 실행을 차단.
    • SUPPRESSED_EFFECT_LIGHTS : 알림 라이트 차단.
    • SUPPRESSED_EFFECT_PEEK : 알림이 뷰로 일시적으로 슬라이딩(피킹)하지 못하게 함.
    • SUPPRESSED_EFFECT_STATUS_BAR : 상태표시줄을 지원하는 장치에서 상태표시줄에 알림 표시 차단.
    • SUPPRESSED_EFFECT_BADGE : 배지를 지원하는 기기에서 배지 차단.
    • SUPPRESSED_EFFECT_AMBIENT : 주변 디스플레이를 지원하는 기기의 알림 차단.
    • SUPPRESSED_EFFECT_NOTIFICATION_LIST : 알림표시 나 락스크린과 같이 목록보기 지원하는 기기의 목록보기에 알림 차단.

멀티 카메라 지원 및 카메라 업데이트

2개 이상의 물리적 카메라에서 동시에 스트림에 액세스할 수 있음.

듀얼 프론트나 듀얼-백 카메라를 가진 기기에서는 완벽한 줌, bokeh(흐릿한 효과의 필터), 스테레오 비전같은 기능이 가능함.

초기 캡처 중 지연을 줄이는데 도움이 되는 추가적인 세션 파라미터가 포함됨.

서페이스 공유는 카메라 클라이언트가 카메라 스트리밍을 시작/정지할 필요 없이 사용할 수 있음.

디스플레이 기반 플래시 지원.

앱-레벨 이미지 안정화와 특수 효과를 위한 OIS 타임스탬프 액세스.

멀티 카메라 API는 FULL 또는 LIMITED 기능(카메라 HW 지원 레벨)을 가진 기기를 위해 흑백 카메라를 지원함.

흑백 출력은 YUV_420_888 형식임. Y는 그레이스케일, U(Cb)는 128, V(Cr) 128.

외부 USB/UVC 카메라를 지원하는 기기를 지원함.

드로어블 및 비트맵용 ImageDecoder

이미지 디코딩을 위한 제공되는 ImageDecoder 클래스 도입됨.

BitmapFactory 및 BitmapFactory.Options API 대신 이 클래스 사용 권장.

ImageDecoder는 바이트 버퍼, 파일, URI에서 Drawable 또는 Bitmap을 생성해줌.

이미지를 디코드하려면 createSoource() 호출.

그리고 decodeDrawable() 또는 decodeBitmap()에 ImageDecoder.Source 객체를 전달해 Drawable 또는 Bitmap 생성.

기본 설정을 변경하려면 OnHeaderDecodedListener를 decodeDrawable() 이나 decodeBitmap()에 전달.

ImageDecoder는 알게 된 이미지의 기본 너비와 높이 등을 가지고 onHeaderDecoded()를 호출.

인코드한 이미지가 애니메이션 GIF나 WebP라면, decodeDrawable()이 AnimatedImageDrawable 클래스의 인스턴스인 Drawable을 반환함.

이미지 속성을 설정하는 여러가지 방법이 있음.

  • 디코드한 이미지를 정확한 크기로 조정한다면 etTargetSize()로 대상 dimensions를 전달함. 샘플 사이즈를 통해 이미지를 조정할 수도 있음. 샘플 크기를 setTargetSampleSize()에 직접 전달해야 함.
  • 크기가 조정된 이미지에서 이미지를 자르려면 setCrop()을 호출.
  • 변경가능한 비트맵을 생성하려면, setMutableRequired()에 true를 전달.

ImageDecoder를 사용하여 둥근 모서리나 원형 마스크와 같은 사용자 정의와 복잡한 효과를 이미지에 추가할 수 있음. PostProcessor 클래스의 인스턴스와 함께 setPostProcessor()를 사용하여 원하는 어떤 그리기 명령도 실행하면 됨.

AnimatedImageDrawable을 후-처리하면 애니메이션의 모든 프레임에 적용됨.

애니메이션

Android 9에는 GIF 및 WebP 애니메이션 이미지를 그리거나 표시하기 위해 AnimatedImageDrawable 클래스가 도입됨.

AnimatedImageDrawable은 렌더링 스레드가 AnimatedImageDrawable의 애니메이션을 구동한다는 것이AnimatedVectorDrawable 와 유사하게 동작함.

렌더링 스레드는 워커 스레드를 사용하여 디코딩함으로 렌더링 스레드의 다른 작업을 방해하지 않음.

이 구현으로 앱은 업데이트 관리나 앱의 UI 스레드의 다른 이벤트를 방해하지 않고 애니메이션 이미지를 표시함.

AnimatedImageDrawable은 ImageDecoder 인스턴스를 사용하여 디코딩할 수 있음.

private void decodeImage() throws IOException {
    Drawable decodedAnimation = ImageDecoder.decodeDrawable(ImageDecoder.createSource(getResources(), R.drawable.my_drawable));
    if (decodedAnimation instanceof AnimatedImageDrawable) {
        // Prior to start(), the first frame is displayed.
        ((AnimatedImageDrawable) decodedAnimation).start();
    }
}

ImageDecoder 에는 이미지를 수정할 수 있는 몇가지 방법이 있음.

예로 setPostProcessor() 메서드를 사용하여 원 마스크나 둥근 모서리 적용 등 이미지 모양을 수정할 수 있음.

HDR VP9 동영상, HEIF 이미지 압축, 미디어 API

Android 9에서는 HDR(High Dynamic Range) VP9 프로파일 2에 대한 지원기능이 추가됨.

YouTube, Play 무비 그리고 다른 소스들을 HDR 지원 기기들에서 HDR 지원 영화를 사용자에게 제공할 수 있음.

HEIF (heic) 이미지 인코딩이 플랫폼에 추가됨.

HEIF 스틸 이미지 샘플들은 MediaMuxer 와 MediaExtrator 클래스들에서 지원함.

HEIF는 저장소 및 네트워크 데이터를 아끼기 위해 압축을 향상시킴.

플랫폼에서 지원하므로 백엔드 서버에서 HEIF 이미지를 보내고 사용하기가 쉬움.

공유와 표시를 위해 앱이 데이터 형식과 호환되는지 확인 후 HEIF를 앱의 이미지 저장 형식으로 사용함.

ImageDecoder나 BitmapFactory를 사용하여 jpeg에서 bitmap을 얻고

HeifWriter를 사용하여 YUV, 바이트 버퍼, Surface에서 HEIF 스틸 이미지를 쓸 수 있음.

Media metrics는 AudioTrackAudioRecord 그리고 MediaDrm 클래스들로부터 사용할 수 있음.

MediaDRM 클래스에 메서드 추가하여 메트릭, HDCP 레벨, 보안 레벨 그리고 세션 수를 얻고,

보안 레벨과 보안 정지에 대한 제어 기능을 추가함.

AAudio API에 사용, 콘텐츠 타입 그리고 입력 사전설정을 위한 AAudioStream 속성이 포함됨.

이 속성들을 사용하여 VoIP나 캠코더 앱에 맞게 조정된 스트림을 만들 수 있음.

효과를 포함할 수 있는 서브 믹스를 가진 AAudio 스트림을 연결하도록 SessionID를 설정할 수 있음.

DynamicsProcessing을 위한 AudioEffect API를 포함함.

이 클래스는 채널 기반 오디오 효과를 만들 수 있음.

(이퀄라이제이션, 멀티밴드 압축 그리고 리미터를 포함한 다양한 타입의 여러 단계로 구성됨)

밴드와 활성 스테이지의 수는 구성이 가능하며, 대부분의 파라미터들은 실시간으로 제어 가능함.

JobScheduler의 데이터 비용 민감도

Android 9에서부터 JobScheduler는 통신 사업자가 제공한 네트워크 상태 신호를 사용하여 네트워크 관련 작업을 개선함.

작업은 예상 데이터 크기, 신호 프리 페치 선언하고 자세한 네트워크 요구사항 지정할 수 있음.

JobScheduler는 네트워크 상태에 따라 작업을 관리함.

예를들어 네트워크 신호가 혼잡할 경우 JobScheduler는 대규모 네트워크 요청을 지연시킴.

고정 요금제 네트워크의 경우에는 JobScheduler는 프리 페치 작업을 동작하여 헤드라인 프리페치 같은 사용자 환경을 개선할 수 있음.

작업을 추가할 때 setEstimatedNetworkBytes()setPrefetch() 그리고 setRequiredNetwork()를 사용하여 JobScheduler가 올바르게 작업을 처리하도록 해야 함.

작업이 실행되면 JobParameters.getNetwork()가 반환한 Network 객체를 사용해야 함.

그렇지 않으면 사용자의 요구사항을 충족하지 못하는 기기의 기본 네트워크를 사용되어 의도하지 않은 데이터 사용이 발생함.

Neural Networks API 1.1

Neural Networks API는 Android 8.1에서 도입되었으며, Android 9에서는 API를 확장하고 개선함.

  • 요소별 수학 연산
    • ANEURALNETWORKS_DIV
    • ANEURALNETWORKS_SUB
  • 배열 연산
    • NEURALNETWORKS_BATCH_TO_SPACE_ND
    • ANEURALNETWORKS_SPACE_TO_BATCH_ND
    • ANEURALNETWORKS_SQUEEZE
    • ANEURALNETWORKS_STRIDED_SLICE
    • ANEURALNETWORKS_TRANSPOSE
    • ANEURALNETWORKS_PAD
    • ANEURALNETWORKS_MEAN

알련진 이슈 : ANEURALNETWORKS_TENSOR_QUANT8_ASYMM 텐서를 Android 9 이상에서 사용할 수있는 ANEURALNETWORKS_PAD 작업에 전달하면 NNAPI의 출력이 TensorFlow Lite와 같은 상위 수준 머신러닝 프레임워크의 결과와 일치하지 않을 수 있음. 문제가 해결 될 때까지 ANEURALNETWORKS_TENSOR_FLOAT32만 전달해야 함.

ANEURALNETWORKS_TENSOR_FLOAT32를 IEEE 754 16 비트 부동 소수점 형식의 범위와 정밀도로 계산할지 여부를 지정할 수있는ANeuralNetworksModel_relaxComputationFloat32toFloat16()이 API에 새 기능으로 추가됨.

자동 완성 프레임워크

폼 작성시 사용자 경험을 향상시키기 위해 자동완성 서비스가 구현할 수 있는 여러 개선사항이 소개됨.

보안기능 향상

아래와 같은 보안 기능이 도입됨.

Android Protected Confirmation

Android 9 이상을 실행하는 지원 기기는 Android Protected Confirmation을 사용할 수 있는 기능을 제공함.

이 워크플로를 사용시 앱은 사용자에게 짧은 성명서를 승인할 것인지 묻는 메시지를 표시함.

이 성명서를 통해 앱에서 사용자가 결제와 같이 중요한 거래를 완료하기를 원하는지를 재확인할 수 있음.

만약 사용자가 성명서를 수락하면 Android Keysotre는 HMAC(Keyed-Hash Message Authentication Code)로 보호되는 암호화 서명을 받아서 저장함. Android Keystore가 메시지의 유효성을 확인한 후 앱은 신뢰할 수 있는 실행 환경(trusted execution environment, TEE)의 trustedConfirmationRequired에서 생성된 키를 사용하여 사용자가 수락한 메시지에 서명할 수 있음. 서명은 매우 높은 신뢰성으로 사용자가 성명서를 보았고 그것에 동의했음을 나타냄.

주의 : Android Protected Confirmation은 사용자에게 보안 정보 채널을 제공하지 않음. 앱은 Android 플랫폼이 제공하는 것 이상의 기밀유지 보장을 할 수 없음. 특히, 이 워크플로를 사용하여 사용자 기기에 일반적으로 표시하지 않는 중요 정보를 표시하지 말아야 함.

통합 생체인식 인증 다이얼로그

Android 9에서는 시스템이 앱 대신 생체 인식 다이얼로그를 제공함.

이 기능은 표준화된 모양, 느낌 그리고 다이얼로그 위치를 생성하여 사용자가 신뢰할 수 있는 생체 인식 검사기에 대해 인증하고 있다는 확신을 줌.

앱이 FingerprintManager를 사용하여 사용자에게 지문인식 다이얼로그를 표시하는 경우 BiometricPrompt를 사용하도록 대신함. BiometricPrompt는 시스템에 의해 인증 다이얼로그를 표시함. 또한 사용자가 선택한 생체 인식 타입에 맞게 동작을 변경함.

참고 : 앱에서 BiometricPrompt를 사용하기 전에 hasSystemFeature() 메소드를 사용하여 기기가 FEATURE_FINGERPRINT, FEATURE_IRIS 또는 FEATURE_FACE를 지원하는지 확인해야 함.

기기가 생체 인증을 지원하지 않는다면, createConfirmDeviceCredentialIntent() 메소드를 사용함으로써 사용자의 PIN, 패턴 또는 비번을 확인할 수 있음.

하드웨어 보안 모듈

Android 9 이상이 동작하는 지원 기기에서는 하드웨어 보안 모듈에 있는 (Keymaster HAL 구현체인) StrongBox Keymaster가 있을 수 있음. 모듈에는 다음이 포함됨.

  • 자체 CPU
  • 안전한 저장장치
  • 순수 난수 생성기
  • 패키지 변조 그리고 앱의 권한 없는 사이드 로딩을 예방할 수 있는 매커니즘

StrongBox Keymaster에 저장된 키를 확인할 때 시스템은 신뢰할 수 있는 실행 환경(TEE)에서 키의 무결성으로 확증함.

Keystore로 키 가져오기 보호

Android 9은 ASN.1로 인코딩된 키 형식을 사용하여 함호화된 키를 Keystore로 안전하게 가져오는 기능을 추가함.

Keymaster가 Keystore에 있는 키를 암호화 해제하므로, 키의 내용이 결코 기기의 호스트 메모리에 일반 텍스트로 나타나지 않음.

참고 : 이 기능은 Keymaster 4 이상과 함께 제공되는 기기에서만 지원함.

키 순환을 포함한 APK 서명 구성표

APK 서명 스킴 v3 지원이 추가됨.

이 스킴에서는 각 서명 인증서의 서명 블록에 순환 증명 레코드를 포함할 수 있는 옵션이 있음.

이 기능은 APK 파일의 과거 서명 인증서를 현재 서명된 것에 연결하여 앱에 새 서명 인증서로 서명이 가능함.

참고 : Android 8.1(API 27) 이하 기기에서는 서명 인증서 변경을 지원하지 않음.

앱의 최소 SDK 버전이 27 이하인 경우 이전 서명 인증서를 사용하여 새 서명으로 앱에 서명해야 함.

잠금 해제된 기기에서만 키 복호화를 허용하는 옵션

Android 9에서는 unlockedDeviceRequired 플래그가 도입됨.

이 옵션은 키를 사용하여 사용 중인 데이터나 저장된 데이터의 암호 복호화를 허용하기 전에 키 저장소에서 화면의 잠금을 해제해야 하는지 여부를 결정함.

이러한 유형의 키는 상태 데이터 또는 기업 데이터와 같이 중요한 데이터를 디스크에 저장하기 위해 암호화하는데 적합함.

이 플래그는 사용자가 폰을 분실하거나 도난당한 경우 장치가 잠긴 동안엔 데이터를 복호화할 수 없다는 높은 확신을 제공함.

참고 : unlockedDeviceRequired 플래그가 활성화되어 있다면 암호화와 서명 확인이 언제든지 발생할 수 있음.

이 플래그는 장치가 잠금 해제될 때 데이터의 복호화만 방지함.

장치가 잠긴 상태에서 키를 안전하게 해독하려면 setUnlockedDeviceRequired() 메서드에 true를 전달하여 플래그를 활성화시킴.

이 단계 완료 후 사용자의 화면이 잠길 때 데이터의 암호를 해독하거나 서명하려는 모든 시도가 실패함.

잠긴 기기는 액세스하기 전에 PIN, 암호, 지문 또는 다른 신뢰할 수 있는 것이 필요함.

레거시 암호화 지원

Keymaster 4가 실린 기기는 Triple Data Encryption Algorithm(Triple DES)이 지원됨.

앱이 Triple DES가 필요한 레거시 시스템과 상호 운용되는 경우 민간함 자격 증명을 암호화할 때 이 유형의 암호를 사용.

Android 백업

백업과 복원과 관련된 새 기능과 개발자 옵션 추가됨.

클라이언트 측 암호화 백업

클라이언트 측 비밀로 안드로이드 백업 암호화를 지원.

다음의 조건이 충족시 자동으로 사용됨.

  • 사용자가 Android 9 이상을 사용하여 백업을 활성화함.
  • 사용자가 잠금을 해제하기 위해 PIN, 패턴, 암호가 필요한 기기의 화면 잠금을 설정함.

이 개인 정보 보호 수단을 활성화하면 사용자의 기기에서 만든 백업에서 데이터를 복원을 위해 기기의 PIN, 패턴, 암호가 필요함.

백업에 필요한 기기 조건 정의

앱 데이터에 민감한 정보나 환경 설정이 포함시, 사용자의 백업이 포함된 앱의 데이터 아래 기기 컨디션을 정의할 수 있음. (예를 들어 클라이언트측 암호화가 활성화 되어있는거나 로컬 기기에서 기기로 전송이 일어나는 것같은)

접근성

탐색 의미 체계

Android 9에는 접근성 서비스, 특히 스크린 리더가 화면의 한 부분에서 다른 곳으로 이동하는 방법에 대해서 쉽게 정의하는 속성 추가. 이 속성은 시각 장애가 있는 사용자가 빠르게 앱 UI의 텍스트를 빠르게 이동하고 선택하는 것을 도와줌.

ex) 쇼핑 앱에서 스크린 리더가 모든 항목을 읽지 않아도 다음 카테고리로 바로 이동할 수 있도록 도와줌.

접근성 창 제목

Android 8.1 (API 27) 이하에서는 접근성 서비스가 화면의 특정 창을 업데이트한 시점을 항상 결정할 수 있지 않음.

(Activity가 Fragment를 바꿀 때와 같은 경우)

창은 논리적 그룹으로 이루어졌고 시각적으로 관계된 UI 요소로 구성됨.

Android 9에서는 접근성 창 제목 또는 개별적으로 식별 가능한 제목을 제공할 수 있음.

창에 액세스 가능한 창 제목이 있을 경우 접근성 서비스가 창 변화시 보다 자세한 정보를 받음.

이 기능을 통해 서비스는 UI에서 무엇이 변경되었는지에 대한 보다 세분화된 정보를 제공받음.

창 제목을 지정하려면 android:accessibilityPaneTitle 속성을 사용.

setAccessibilityPaneTitle() 을 사용하여 런타임에서 바뀌는 UI 창의 제목을 업데이트할 수 있음.

(예를 들어 Fragment 객체의 콘텐츠 영역에 제목을 제공할 수 있음)

제목 기반의 탐색

앱이 논리적 표제를 포함한 텍스트 콘텐츠를 표시하는 경우, 해당 View 인스턴스의 android:accessibilityHeading 속성을 true로 설정.

이 표제들을 추가하면 사용자가 다음 표제로 직접 이동할 수 있는 접근성 서비스가 허용됨.

모든 접근성 서비스는 이 기능을 사용하여 사용자의 UI 탐색 경험을 향상시킬 수 있음.

그룹 탐색 및 출력

스크린 리더는 보통 android:focusable 속성을 사용하여 ViewGroup 또는 View 객체의 모음을 단일 유닛으로 읽어야 하는 시점을 판별해옴. 사용자는 뷰가 서로 논리적으로 관련되어 있음을 이해할 수 잇었음.

Android 8.1 이하에서는 ViewGroup 내에 있는 각 View 객체를 포커스 가능하지 않은 것으로 표시하고, ViewGroup 자체는 포커스 가능한 것으로 표시해야 함. 이러한 배치에서는 View 의 일부 인스턴스가 `포커스 가능`으로 표시되었으며, 이로 인해 키보드 탐색이 더욱 번거롭게 됨.

Android 9부터는 View 객체를 `포커스 가능`으로 만들 경우 원치 않는 결과가 나올수 있는 상황에서 android:focusable 속성 대신 android:screenReaderFocusable 속성을 사용. 스크린 리더는 android:screenReaderFocusable 나 android:focusable을 true로 설정한 모든 요소에 포커스를 둠.

편의 작업

Android 9에서는 사용자를 대신해 편의 작업을 수행하기 위한 지원이 추가됨.

도움말과의 상호작용

접근성 프레임워크에 추가된 기능을 사용하여 앱UI의 도움말에 액세스할 수 있음.

도움말 텍스트를 읽으려면 getTooltipText()를 사용.

View 인스턴스에 지시하려면 ACTION_SHOW_TOOLTIP 및 ACTION_HIDE_TOOLTIP을 사용

추가된 전역 작업

Android 9에서는 AccessibilityService 클래스에서 ㄷ 가지 추가 기기 작업을 위한 지원이 제공됨.

서비스에서 사용자가 각각 GLOBAL_ACTION_LOCK_SCREEN 및 GLOBAL_ACTION_TAKE_SCREENSHOT 액션을 사용하여 기기를 잠그고 스크린 샷을 찍을 수 있음.

창 변경 세부정보

Android 9에서는 앱이 여러 창을 동시에 다시 그릴때 업데이트를 더욱 쉽게 추적할 수 있음.

TYPE_WINDOWS_CHANGED 이벤트가 발생하는 경우, getWindowChanges() API를 사용하여 창이 어떻게 변경되었는지 판별할 수 있음.

여러 창이 업데이트 되는 경우 각 창은 자체 이벤트 집합을 생성함.

getSource() 메서드는 각 이벤트와 관련된 창의 루트 뷰를 반환함.

앱에서 View 객체에 대해 접근성 창 제목을 정의한 경우 서비스는 앱 UI가 언제 업데이트 되는지 식별할 수 있음.

TYPE_WINDOW_STATE_CHANGED 이벤트가 발생하는 경우, getContentChangeTypes() 에 의해 반환되는 유형을 사용하여 창이 어떻게 변경되었는지 판별할수 있음.

ex) 프레임워크에서 창에 새로운 제목이 있거나 창에 새로운 제목이 있거나 창이 사라진 경우를 감지할 수 있음.

회전

원치 않는 회전을 없애기 위해 기기 위치가 바뀌더라도 방향이 고정되는 모드를 추가함.

필요시 사용자가 시스템 바의 버튼을 눌러 수동으로 회전을 트리거할 수 있음.

앱에 대한 호환성의 영향은 대부분 최소한임.

그러나 사용자 회전 기본설정이 항상 세로모드로 설정된 경우 앱에 맞춤형 회전 동작이 있거나 이상한 화면 방향 설정이 사용된다면 새로운 문제가 발생할 수도 있음.

권장하는 방법은 앱의 모든 주요 액티비티에서 회전 동작을 살펴보고 확인하는 것임.

텍스트

Android 9 은 다음과 같은 텍스트 관련 기능을 플랫폼으로 가져옴.

  • Precomputed Text : PrecomputedText 클래스는 보다 빨리 필요한 정보를 계산하고 캐시할 수 있게 함으로써 텍스트 렌더링 성능을 향상시킴. 이 클래스를 사용하면 앱이 메인 스레드를 벗어나 텍스트 레이아웃을 수행하도록 할 수 잇음.
  • Magnifier : Magnifier 클래스는 돋보기 API를 제공하는 플랫폼 위젯으로 모든 앱에서 일관된 돋보기 기능을 사용할 수 있게 해줌.
  • Smart Linkify : Android 9 에서는 TextClassifier 클래스가 머신러닝을 활용하여 선택된 텍스트에서 엔티티를 식별하고 액션을 제안하도록 개선됨. TextClassifier의 기능은 Linkify 클래스의 기능을 대체함. ex) 사용자가 전화 번호를 선택했다는 사실을 앱이 알아차릴 수 있음.
  • Text Layout : 여러가지 메서드와 속성으로 UI 디자인을 더 쉽게 구현할 수 있음. TextView 참조문서 참고.

DEX 파일의 ART AOT(Ahead-Of-Time) 변환

Android 9 이상이 실행되는 기기에서는 ART(Android RunTime) AOT(ahead-of-time) 컴파일러가 앱 패키지에 잇는 DEX(Dalvik Executable) 파일을 더 콤팩트한 표시로 변환하여 압축된 DEX 형식 파일로 더욱 최적화함.

앱이 더 빠르게 시작하고 디스크 공간과 RAM을 덜 소비하게 됨.

특히 디스트 I/O 속도가 느린 저사양 기기에 도움이 됨.

온디바이스 시스템 추적

Android 9 에서는 기기에서 시스템 트레이스를 기록 후 이 기록에 대한 보고서를 개발팀과 공유할 수 있음.

이 보고서는 HTML 을 비롯한 여러 형식을 지원함.

이런 트레이스를 수집함으로써 앱의 프로세스 및 스레드와 관련된 타이밍 데이터를 캡처하고 전체적으로 중요한 다른 유형의 기기 상태도 불 수 있음.

참고 : 트레이스를 기록하기 위해 코드를 계측할 필요는 없지만, 이를 실행해보면 앱 코드의 어떤 부분이 스레드 장애 또는 UI 버벅거림의 원인이 될지 파악하는데 도움이 될 수 있음.


Android OS 9 Pie

참고 : https://www.android.com/versions/pie-9-0/

Accessibility

  • Accessibility Menu : 스크린샷 찍기나 한손 탐색 같은 일반 동작들이 장애가 있는 사용자에게 더 쉬워짐.
  • Select to Speak : 카메라에서 OCR 지원하여 텍스트 선택 및 읽기 지원
  • Sound amplifier : 다양한 환경에서의 오디오 기능 향상을 위해 100개 이상의 설정을 할 수 있음.

Battery & Brightness

  • Battery Saver : Always-0n 디스플레이와 같은 기능을 끄고 충전시간 유지 가능.
  • Adaptive Battery : machine learning을 통해 사용할 앱과 그렇지 않을 앱을 예측하여 배터리 전원을 사용.
  • Adaptive Brightness : 다양한 조명 환경에서 화면 밝기 설정하는 방법을 배워서 자동으로 조정해줌.
  • Background restrictions : 배터리 사용량이 너무 많은 특정 앱을 제한하는 권장사항이 표시되어 배터리를 효율적으로 제어함. 

Camera

  • Multi-camera 지원
  • External camera 지원 (USB/UVC)

Digital Wellbeing

  • Do Not Disturb : 모든 알림과 시각적 기능이 침묵. 별표 표시된 연락처는 전달됨.
  • App dashboard : 폰에서 보낸 시간들을 확인 및 앱 사용 빈도와 알림 수르 확인.
  • Wind Down : 일일 일정을 설정하여 폰을 잠자게 함. 회색으로 화면을 표시하고 알림을 조용히 시킴.
  • App timers : 앱의 일일 시간 제한을 설정하여 한도에 도달시 앱이 일시 중지됨. 

Display

  • Display cutout : 컷 아웃이 있는 장치 지원함.
  • Edge-to-edge screens : 18:9 이상의 비율이나 컷 아웃이 있는 장치를 지원함. 

Enterprise

  • Multiple users on dedicated devices : 교대 근무자나 공용 키오스크에 적합한 단일 장치를 쉽게 공유할 수 있음.
  • Work tab in launcher : 작업용 앱을 시각적으로 분리 후 작업 탭을 탭하여 한 곳에서 볼 수 있음
  • Postpone Over-the-air(OTA) updates : 엔터프라이즈 IT 관리자가 Android OS 업데이트를 못하도록 최대 90일의 동결 기간을 정의할 수 있음.

Media

  • Multiple Bluetooth connections : 최대 5개의 장치에 연결하고 수신 전화는 가능한 모든 장치로 전송됨.
  • Sound delay reporting : 해당 기능이 있는 헤드셋을 지원하여 비디오와 헤드셋의 오디오 동기화 상태를 유지함.
  • Volume memory per Bluetooth device : 각 장치에 대해 마지막으로 설정한 불륨을 기어함.
  • HDR(High Dynamic Range) : HDR VP9 프로파일 2에 대한 지원 내장. 유튜브 등에서 HDR 지원 영화 볼 수 있음.
  • HD Audio : 향상된 성능과 HD 오디오 지원.
  • HEIF : HEIF 사진을 지원함.

Notifications

  • Notification enhancements for messaging : 메시징 앱 알림에 smart replies를 제안할 수 있어 탭으로 회신 가능함. 이동한 경우 모든 인라인 답장 초안이 안사라지며, 알림에서 받은 이미지를 볼 수 있음.
  • Manage Notifications : 앱에서 알림을 신속하게 끌 수 있음.

Privacy & Security

  • Android Backups : 클라이언트 측 비밀번호(기기 PIN, 패턴 또는 비밀번호)로 백업 암호화 가능.
  • Android biometric prompt : 표준 생체 인증 프롬프트를 포함하고, 다양한 새로운 보안 기능을 도입함.
  • Android Protected Confirmation : 호환 가능한 하드웨어에서 앱은 보안 하드웨어가 제어하는 UI를 사용하여 결제와 같은 민감한 거래의 확인을 받을 수 있음.
  • StrongBox : 호환 가능한 하드웨어에서 앱은 개인 키를 보호하기 위해 변조 방지 하드웨어를 활용할 수 있음.
  • Privacy enhancements : 앱이 idle이나 백그라운드에서 실행 중일때 폰의 마이크, 카메라, 기타 센서에 대한 액세스를 제한함. (앱이 센서에 액세스해야 하는 경우 폰에 알림 표시됨.) 모든 웹 통신을 보호하고 개인 웹 서핑을 제공하는 중요한 개선 기능을 제공함.

System Usability Enhancements

  • At-a-Glance on Always-on-Display : 락스크린 및 Always-on-Display에서 캘린더 이벤트나 날씨를 볼 수 있음.
  • Redesigned Quick Settings : 모든 토글을 포함한 퀵 셋팅의 보다 일관된 사용자 환경. 시각 디자인 업데이트 및 설명문구 추가.
  • Volume controls : 더 간단하고 볼륨 컨트롤을 통해 미디어/통화/알림 불륨 등을 바로 제어할 수 있음.
  • Screenshots : 파워메뉴에서 스크린 샷을 쉽게 가져와서 빠르게 그리기, 주석달기, 짜르기 등을 할 수 있음.
  • Rotation : 방향을 고정한 경우에도 기기의 회전을 언제 변경할 것인지 확인하는 간단한 버튼으로 제어 가능.
  • New system navigation : 앱 간 이동이나 검색을 보다 쉽게 만듦. 어디서든 위로 스와이프시 최근 사용한 앱의 전체화면 프리뷰를 볼 수 있음. 
  • App Actions : 사용자의 맥락을 기반으로 다음 원하는 것을 예측하여 표시해줌. 
  • Slices : 좋아하는 앱의 반응형 조각을 구글 검색과 같은 다른 곳에서 볼 수 있음. 
  • Overview Selection : Overview 모드에서 선택한 텍스트나 이미지를 길게 누르면 선택한 항목 기반으로 한 작업을 볼 수 있음.


지난 8월 14일 저녁에 아이들과 잠실 롯데타워에 다녀왔네요.
퇴근 후 저녁시간에 가다보니 8시쯤 도착했지요.
역시 야경이 느긋히 감상을 했다면 아름다워겠지만...
한시도 가만 안있는 녀석들 덕에 전망대 층층이 계속 돌아다녔답니다.
그래도 이제 서울의 상징적인 곳에 다녀간다는 기억을 심어주고 좋았지요.
다음에는 밝은 대낮에도 한번 구경하고 싶네요.

간만에 토끼정에 가봤네요.
메뉴는 사진 순서대로

유즈돈카츠 11,500원
얼음가득 콜라 3,500원
클라우드 생맥주 4,000원
그 저녁 날의 야끼 스파게티 9,300원
달짝지근한 숯불갈비 덮밥 12,300원
기억 속 안지랑 곱창볶음 9,500원
매콤달콤 양념찜닭 14,900원
양송이 크림수프와 숯불떡갈비 밥 11,500원

남자 6명이서 가서 먹은거고요.
대략 10만원이 안나오더군요.
오픈 시간 전에 가니 자리는 잡을수는 맀는데 서빙은 오픈시간에 맞춰 시작되더라고요.
음식 웨이팅도 좀 있고 서빙도 부르기가 쉽지 않다보니 일부 불만이 쌓이기도 했지만..
전체적으로 맛있고 괜찮았습니다.
일행들이 전체적으로 만족스러워 했네요.
재방문할만한 곳 같네요~

Android OS 9 Pie

참고 : https://www.android.com/versions/pie-9-0/

Accessibility

  • Accessibility Menu : 스크린샷 찍기나 한손 탐색 같은 일반 동작들이 장애가 있는 사용자에게 더 쉬워짐.
  • Select to Speak : 카메라에서 OCR 지원하여 텍스트 선택 및 읽기 지원
  • Sound amplifier : 다양한 환경에서의 오디오 기능 향상을 위해 100개 이상의 설정을 할 수 있음.

Battery & Brightness

  • Battery Saver : Always-0n 디스플레이와 같은 기능을 끄고 충전시간 유지 가능.
  • Adaptive Battery : machine learning을 통해 사용할 앱과 그렇지 않을 앱을 예측하여 배터리 전원을 사용.
  • Adaptive Brightness : 다양한 조명 환경에서 화면 밝기 설정하는 방법을 배워서 자동으로 조정해줌.
  • Background restrictions : 배터리 사용량이 너무 많은 특정 앱을 제한하는 권장사항이 표시되어 배터리를 효율적으로 제어함. 

Camera

  • Multi-camera 지원
  • External camera 지원 (USB/UVC)

Digital Wellbeing

  • Do Not Disturb : 모든 알림과 시각적 기능이 침묵. 별표 표시된 연락처는 전달됨.
  • App dashboard : 폰에서 보낸 시간들을 확인 및 앱 사용 빈도와 알림 수르 확인.
  • Wind Down : 일일 일정을 설정하여 폰을 잠자게 함. 회색으로 화면을 표시하고 알림을 조용히 시킴.
  • App timers : 앱의 일일 시간 제한을 설정하여 한도에 도달시 앱이 일시 중지됨. 

Display

  • Display cutout : 컷 아웃이 있는 장치 지원함.
  • Edge-to-edge screens : 18:9 이상의 비율이나 컷 아웃이 있는 장치를 지원함. 

Enterprise

  • Multiple users on dedicated devices : 교대 근무자나 공용 키오스크에 적합한 단일 장치를 쉽게 공유할 수 있음.
  • Work tab in launcher : 작업용 앱을 시각적으로 분리 후 작업 탭을 탭하여 한 곳에서 볼 수 있음
  • Postpone Over-the-air(OTA) updates : 엔터프라이즈 IT 관리자가 Android OS 업데이트를 못하도록 최대 90일의 동결 기간을 정의할 수 있음.

Media

  • Multiple Bluetooth connections : 최대 5개의 장치에 연결하고 수신 전화는 가능한 모든 장치로 전송됨.
  • Sound delay reporting : 해당 기능이 있는 헤드셋을 지원하여 비디오와 헤드셋의 오디오 동기화 상태를 유지함.
  • Volume memory per Bluetooth device : 각 장치에 대해 마지막으로 설정한 불륨을 기어함.
  • HDR(High Dynamic Range) : HDR VP9 프로파일 2에 대한 지원 내장. 유튜브 등에서 HDR 지원 영화 볼 수 있음.
  • HD Audio : 향상된 성능과 HD 오디오 지원.
  • HEIF : HEIF 사진을 지원함.

Notifications

  • Notification enhancements for messaging : 메시징 앱 알림에 smart replies를 제안할 수 있어 탭으로 회신 가능함. 이동한 경우 모든 인라인 답장 초안이 안사라지며, 알림에서 받은 이미지를 볼 수 있음.
  • Manage Notifications : 앱에서 알림을 신속하게 끌 수 있음.

Privacy & Security

  • Android Backups : 클라이언트 측 비밀번호(기기 PIN, 패턴 또는 비밀번호)로 백업 암호화 가능.
  • Android biometric prompt : 표준 생체 인증 프롬프트를 포함하고, 다양한 새로운 보안 기능을 도입함.
  • Android Protected Confirmation : 호환 가능한 하드웨어에서 앱은 보안 하드웨어가 제어하는 UI를 사용하여 결제와 같은 민감한 거래의 확인을 받을 수 있음.
  • StrongBox : 호환 가능한 하드웨어에서 앱은 개인 키를 보호하기 위해 변조 방지 하드웨어를 활용할 수 있음.
  • Privacy enhancements : 앱이 idle이나 백그라운드에서 실행 중일때 폰의 마이크, 카메라, 기타 센서에 대한 액세스를 제한함. (앱이 센서에 액세스해야 하는 경우 폰에 알림 표시됨.) 모든 웹 통신을 보호하고 개인 웹 서핑을 제공하는 중요한 개선 기능을 제공함.

System Usability Enhancements

  • At-a-Glance on Always-on-Display : 락스크린 및 Always-on-Display에서 캘린더 이벤트나 날씨를 볼 수 있음.
  • Redesigned Quick Settings : 모든 토글을 포함한 퀵 셋팅의 보다 일관된 사용자 환경. 시각 디자인 업데이트 및 설명문구 추가.
  • Volume controls : 더 간단하고 볼륨 컨트롤을 통해 미디어/통화/알림 불륨 등을 바로 제어할 수 있음.
  • Screenshots : 파워메뉴에서 스크린 샷을 쉽게 가져와서 빠르게 그리기, 주석달기, 짜르기 등을 할 수 있음.
  • Rotation : 방향을 고정한 경우에도 기기의 회전을 언제 변경할 것인지 확인하는 간단한 버튼으로 제어 가능.
  • New system navigation : 앱 간 이동이나 검색을 보다 쉽게 만듦. 어디서든 위로 스와이프시 최근 사용한 앱의 전체화면 프리뷰를 볼 수 있음. 
  • App Actions : 사용자의 맥락을 기반으로 다음 원하는 것을 예측하여 표시해줌. 
  • Slices : 좋아하는 앱의 반응형 조각을 구글 검색과 같은 다른 곳에서 볼 수 있음. 
  • Overview Selection : Overview 모드에서 선택한 텍스트나 이미지를 길게 누르면 선택한 항목 기반으로 한 작업을 볼 수 있음.


이번 여름휴가로 3박4일로 가평 늘푸른쉼터 캠핑장을 다녀왔어요.
더위가 피크였던 시기에 해 쨍쟁하던 시기에 간거죠.
전날 날씨 앱에서 서울과 가평이 둘다 40도 찍던 시기였죠.. ;;

일단 서울에선 무지 가까웠습니다. 
올림픽대로를 타고가다가 서울양양 고속도로를 타기만 하면 되는 거였죠.
원래는 설악IC에서 내리면 되지만 그날은 그쪽이 막히는지 Tmap에서 서종IC로 돌리더군요.
그래서 막힘없이 잘 갈수가 있었지요.
(일행으로 같이 가신 부모님은 네비가 설악으로 안내해 많이 지체가 되셨었죠..;;)

첫 인상은 그늘이 생각보다 적어보인다는 것입니다.
이 바로 전에는 푸름유원지 캠핑장에 갔던지라 상대적으로 그늘이 많이 적어보였습니다.
하지만 사이트마다 작지 않은 나무들이 있어 해 위치에 따라 그늘이 생기긴 했습니다.
그리고 사이트는 적당히 넓어 난민촌이 될 일은 없어 보였어요.
저희는 2 사이트를 잡아 한 사이트엔 타프와 타프스크린, 나머지 한 사이트에 텐트 2동을 세웠는데요.
여유 공간이 아주 많았습니다. 차도 세울수 있었지만.. 길가에 가림막으로 넉넉히 세워두었지요.

놀이시설은 유아놀이방, 방방장 2곳, 놀이터 2곳, 수영장, 계곡 2군데 정도가 되겠습니다.
유아놀이방에는 에어컨은 따로 없지만 큰 선풍기 2대가 열심히 돌아가고요.
방방장은 초등생 기준으로 2 곳이 운영되고 있었습니다.
놀이터도 2곳이 있지만, 땡볕에는 역시 사람이 없습니다.
수영장은 꽤 넓직하게 운영되며, 아이들이 제일 신나게 노는 곳이었습니다. 수심은 1m 약간 안되는 정도인거 같았고요.
계곡은 제가 본 곳은 수영장 맞은편에 위치한 곳과 서쪽 끝에 있는 곳이 있었지요.
수영장 맞은 편은 하류쪽은 물을 적당히 막아놓아 아이들이 튜브끼고 놀기에 좋았고요.
상류쪽은 얕은 대신 물은 상대적으로 맑았습니다.
서쪽 끝 계곡도 비슷한 구조였으며, 계곡 건너에 있는 식당 손님들과 같이 쓰는 구조였어요.
계곡들이 수심이 깊지 않아 아이들이 놀기 좋았으며, 피래미 같은 물고기도 잡고 놀기엔 좋았어요.
다만 계곡이다 보니 수면 아래 돌들이 불규칙하게 있어 놀이시에 조심할 필요는 있답니다.

부가시설로는 샤워장, 화장실, 개수대, 매점 등이 있고요.
매점을 제외한 부가시설들은 2곳이 있었습니다.
저희는 서쪽에 위치한지라 가까운 시설들을 주로 이용하였고요.
모든 시설들이 깨끗이 잘 운영되고 있었어요. 소문대로 사장님들이 부지런하십니다;;
다만 아쉬운 점은 샤워장이 다소 협소한 것이 있었습니다.
그리고 화장실 내부에 위치하다보니 문이 열려있는 경우 외부에 샤워장이 살짝 오픈될수도 있을 우려는 있더군요.
대신 동쪽에 위치한 화장실과 샤워장은 이런 문제가 없었습니다.
내부도 더 넓직하고 샤워장 문이 화장실 안쪽 끝에 있어 위와 같은 문제가 없었답니다.

전체적으로 만족스러운 휴가를 보내고 온 캠핑이었습니다.
첫날은 밤에는 선풍기를 틀고 자야 시원한 느낌이었지만,
둘째날은 선풍기를 틀고 자다 추워서 꺼야했고요.
셋째날은 담요를 덮고 잘 정도로 해 떨어진 다음은 시원하게 잘 지냈답니다.
그늘이 부족하긴 하지만 물에 한번 들어갔다 나온뒤 타프나 나무 그늘 아래 있으면 그리 덥지도 않았고요.
기회가 된다면 또 가볼만한 캠핑장 같습니다!!!


가평의 푸름유원지 캠핑장입니다.
넓은 부지에 밤나무 사이트들과 잔디밭 사이트가 있더군요.
밤나무는 하루종일 그늘이 생깁니다. 가장자리라면 아침 저녁 해가 누워서 들어오는 정도고요.
잔디밭 사이트는 저녁부터 운치가 있고 밤하늘에 별이 쏟아지는 낭만이 있습니다.
대신 요즘같은 때는 너무 더워요 ㅠㅠ
부대시설은 화장실은 휴지가 없는 점이 다소 아쉽지만 잘 관리되고 있고요.
식수대도 잘 관리를 해주시네요.
샤워장은 넓찍해서 개인적으로 만족스러웠습니다.
다만 부대시설이 캠핑장 입구에 몰려 있어 구석 사이트에 계신 분들은 다소 불편할 수 있을것 같았습니다.
그리고 물놀이는 정말 좋아요. 깊은 곳도 성인 허리까지 들어가는 곳이 없더라고요.
최근 비가 없어 그런건지 몰라도 아이들 놀기 너무너무 좋았습니다.
담에 기회봐서 또 찾고 싶은 캠핑장입니다!!

인스턴트 앱(Instant App) 샘플

진행할 항목

  1. 샘플 프로젝트 준비
  2. 인스턴트 앱 구조에 맞춰 프로젝트 변경
  3. 안드로이드 스튜디오에서 실행해보기
  4. Heroku 구성

1. 샘플 프로젝트 준비

Android Architecture Blueprints 프로젝트로 샘플 프로젝트 준비

git clone https://github.com/googlesamples/android-architecture.git
git checkout -t origin/todo-mvp

구글의 인스턴트 앱 예제를 보고 싶다면

https://github.com/googlesamples/android-instant-apps

2. 인스턴트 앱 구조에 맞춰 프로젝트 변경

가. 기존 app 모듈명을 base feature명으로 수정 (ex. base_module)

  • Project 창에서 모듈 선택 후 우클릭 Refactor - Rename - Rename module 선택 후 변경

  • app/build.gradle 수정 (apk_module 은 추가할 APK Module명) applicationId, applicationIdSuffix 항목 주석 처리 room schemaLocation 오류 수정을 위해 javaCompileOptions 추가

    // apply plugin: 'com.android.application'
    apply plugin: 'com.android.feature'
    ...
    android {
    	...
        baseFeature true
        ...
        defaultConfig {
            //applicationId "com.example.android.architecture.blueprints.todomvp"
            ...
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
                }
            }
        }
        ...    
        // If you need to add more flavors, consider using flavor dimensions.
        productFlavors {
            mock {
                dimension "default"
                //applicationIdSuffix = ".mock"
            }
            prod {
                dimension "default"
            }
        }
        ...
    }
        
    dependencies {
        application project(":apk_module")
    }
  • base_module/src/main/AndroidManifest.xml 수정

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.android.architecture.blueprints.todoapp">
    ...
    </manifest>

나. APK Module 추가 (ex. apk_module)

  • Module 추가 (File > New > New Module)

  • Phone & Table Module 선택

  • 모듈명 기입.(ex. apk_module)

    앱 이름 및 최소 SDK 버전 등을 확인해야 함.

  • Add No Activity 선택.

  • values/strings.xml 에서 app_name 리소스 주석 처리

    <resources>
        <!--<string name="app_name">APK Module</string>-->
    </resources>
  • apk_module에 base_module/proguard-rules.pro와 proguardTest-rules.pro를 복사

  • apk_module/build.gradle 수정 buildTypes나 Flavors는 base_module에 있던 내용을 사용 base_module에서 주석처리한 applicationId, applicationIdSuffix 항목 사용

    android {
        ...
        defaultConfig {
            // applicationId "com.mycompany.apk_module"
            applicationId "com.example.android.architecture.blueprints.todomvp"
            ...
        }
        ...
        
        /*
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        */
        buildTypes {
            debug {
                minifyEnabled true
                // Uses new built-in shrinker http://tools.android.com/tech-docs/new-build-system/built-in-shrinker
                useProguard false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardTest-rules.pro'
            }
    
            release {
                minifyEnabled true
                useProguard true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardTest-rules.pro'
            }
        }
    
        flavorDimensions "default"
    
        // If you need to add more flavors, consider using flavor dimensions.
        productFlavors {
            mock {
                dimension "default"
                applicationIdSuffix = ".mock"
            }
            prod {
                dimension "default"
            }
        }
    
        // Remove mockRelease as it's not needed.
        android.variantFilter { variant ->
            if (variant.buildType.name == 'release'
                    && variant.getFlavors().get(0).name == 'mock') {
                variant.setIgnore(true)
            }
        }
    
        // Always show the result of every unit test, even if it passes.
        testOptions.unitTests.all {
            testLogging {
                events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
            }
        }
    }
    ...
    dependencies {
    	implementation project(':base_module')
    }
  • apk_module/src/main/AndroidManifest.xml 수정

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.nobasedev.apk_module">
    </manifest>
  • gradle sync 중 아래 에러 로그시

    Manifest merger failed : Attribute application@allowBackup value=(true) from AndroidManifest.xml:5:9-35
    	is also present at [:base_module] AndroidManifest.xml:28:9-36 value=(false).
    	Suggestion: add 'tools:replace="android:allowBackup"' to <application> element at AndroidManifest.xml:4:5-10:43 to override.

    apk_module/src/main/AndroidManifest.xml의 allowBackup 옵션을 base_module과 동일하게 설정

        <application
            android:allowBackup="false"
            ... />
  • gradle sync 후 apk_module 빌드 및 run 시켜서 동작 확인 아래와 같은 에러들이 발생할 경우 있음.

    Compilation failed; see the compiler error output for details.
    error: constant expression required

    보통 아래와 같은 구문에서 오류로 base_module로 변경된 부분들에서 주로 발생함. 이유는 안드로이드 리소스 id를 switch ~ case 문으로 비교 처리하는 부분인데, base_module로 변경되며 기존 application 모듈이 feature 모듈로 변경되었고, 이에 R.java 값들이 final로 선언되지 않기 때문에 if ~ else로 변경해야 함. (라이브러리 프로젝트에서 주로 발생하는 문제)

    switch문 앞에 커서를 놓고 alt + Enter 후 Replace 'switch' with 'if' 선택

다. Instant App Module 만들기

  • Module 추가 (File > New > New Module)

  • Instant App 선택

  • 모듈명 기입. (ex. instant_module)

  • instantapp 모듈의 build.gradle에 아래와 같이 추가

    apply plugin: 'com.android.instantapp'
    
    android {
        flavorDimensions "default"
        productFlavors {
            prod {
                dimension "default"
            }
        }
    }
    
    dependencies {
        implementation project(':base_module')
    }
  • Instant App에서 사용 가능한 권한 확인

    • ACCESS_COARSE_LOCATION
    • ACCESS_FINE_LOCATION
    • ACCESS_NETWORK_STATE
    • BILLING
    • CAMERA
    • INSTANT_APP_FOREGROUND_SERVICE (API level 26 or higher)
    • INTERNET
    • READ_PHONE_NUMBERS (API level 26 or higher)
    • RECORD_AUDIO
    • VIBRATE

3. 안드로이드 스튜디오에서 실행해보기

  • base_module 매니페스트에 인스턴트 앱 구동할 URL 정의 추가

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.android.architecture.blueprints.todoapp">
        ...
        
            <activity
                android:name="com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity"
                android:theme="@style/AppTheme.OverlapSystemBar">
                ...
                <intent-filter android:autoVerify="true">
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data
                        android:scheme="http"
                        android:host="todoapp.com"
                        android:pathPrefix="/tasks" />
                    <data android:scheme="https" />
                </intent-filter>
            </activity>
        	...    
            <activity
                android:name="com.example.android.architecture.blueprints.todoapp.statistics.StatisticsActivity"
                android:parentActivityName=".tasks.TasksActivity"
                tools:ignore="UnusedAttribute">
                ...
                <intent-filter android:autoVerify="true">
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data
                        android:scheme="http"
                        android:host="todoapp.com"
                        android:pathPrefix="/stats" />
                    <data android:scheme="https" />
                </intent-filter>
            </activity>    
  • Run메뉴의 Edit Configurations... 선택

  • Android App의 instant_module 선택 후 Launch Options의 URL 항목 입력

  • Run 하여 동작 확인.

  • apk_module과 instant_module의 차이는 멀티태스킹 화면에서 확인 가능 (인스턴트 앱의 경우 앱 아이콘 우측 하단의 번개 표시됨)

4. Heroku 구성

  • Heroku 회원가입 및 로그인

  • New - Create new app 선택

  • App name 지정 (ex. testinstant) 후 Create app버튼 클릭

  • Settings 탭 메뉴에서 Buildpacks 메뉴의 Add buildpakc 선택

  • nodejs 선택 후 Save changes 버튼 클릭

  • Deploy 탭 메뉴에서 Heroku Git 선택 후 하단의 설명대로 git 설정

  • Heroku CLI 설치 (https://devcenter.heroku.com/articles/heroku-command-line)

  • 로컬에 저장할 폴더 생성 (ex. heroku_todo_sample) 후 그 경로에서 cmd 띄움.

  • 아래 명령으로 로그인 시도 및 완료

    > heroku login
  • git 초기화

    > git init
    > heroku git:remote -a testinstant
  • nodejs 테스트 코드 작성

    • package.json

      {
        "name": "express-tutorial",
        "version": "1.0.0",
        "dependencies": 
        {
          "express": "~4.13.1",
          "ejs": "~2.4.1"    
        }
      }
    • Procfile

      web: node index.js
    • index.js

      var express = require('express')
      var app = express()
      
      app.set('port', (process.env.PORT || 5000))
      app.use(express.static(__dirname + '/public'))
      app.set('views', __dirname + '/views');
      app.set('view engine', 'ejs');
      app.engine('html', require('ejs').renderFile);
      
      app.get('/', function(request, response) {  
      	console.log("\n *index START* \n");
      	response.send('it\'s index');
      	console.log("\n *index EXIT* \n");
      })
      
      app.get('/.well-known/assetlinks.json',function(request, response){
      	//response.render('assetlinks.json');
      	var fs = require("fs");
      	console.log("\n *START* \n");
      	var content = fs.readFileSync("assetlinks.json");
      	response.setHeader('Content-Type', 'application/json');
      	response.send(content);
      	console.log("Output Content : \n"+ content);
      	console.log("\n *EXIT* \n");
      });
      
      app.listen(app.get('port'), function() {
        console.log("Node app is running at localhost:" + app.get('port'))
      })
  • assetlinks.json 만들기 참고 : https://developers.google.com/digital-asset-links/tools/generator 위 링크에서 Hosting site domainApp package nameApp package fingerprint (SHA256) 등을 입력하고 GENERATE STATEMENT 버튼을 누르면 아래와 같은 형태를 출력해주며, 그 내용을 assetlinks.json 파일로 만들면 됨.

    [{
      "relation": ["delegate_permission/common.handle_all_urls"],
      "target": {
        "namespace": "android_app",
        "package_name": "com.sample.app",
        "sha256_cert_fingerprints":
        ["75:D1:12:1A:85:EC:C7:2E...53:3E:84:E3:8C:0B"]
      }
    }]

    그리고 TEST STATEMENT 버튼을 눌러서 실제 서버에 json파일이 있는지 확인해볼 수 있음. (아래 과정 진행 후 테스트)

  • git commit 및 push (push시 Heroku에서 자동으로 deploy됨)

    > git add .
    > git commit -am "just do it"
    > git push heroku master

    위 과정 중 git push 안될 경우 (회사 보안으로 안되는 경우가 있는듯) 아래 명령 실행 후 token 복사

    > heroku auth:token
    ed2e5b99-97bc-...

    다시 git push heroku master 시도 후 Windows 보안 팝업이 뜨면 사용자 이름 없이 암호만 token 입력 후 확인하면 됨. 

  • 브라우저에서 아래 링크들 확인

  • base_module 매니페스트에서 정의한 URL 인텐트 필터들의 URL 주소를 수정함.

5. 주의할 점

Google Play 배포시 인스턴트 앱을 따로 배포함.

안드로이드 스튜디오의 Buid - Generate Signed APK... 메뉴에서 instant_module을 선택하여 생성함.

인스턴트 앱용 파일은 apk가 아니라 zip 형태로 나오며, 이를 Google Play 인스턴트 앱 배포 메뉴에서 올림.

실제 배포 요청시 base_module 매니페스트에 정의된 인텐트 필터의 URL 경로를 확인함.

해당 URL의 ./well-known/assetlinks.json이 확인 안된다면 배포가 안됨.

배포 후에도 단말마다 되는 경우가 있고, 안되는 경우가 있어 확인이 필요함.

인스턴트 앱(Instant App) 특정 웹 링크 연결

참고링크 :

https://developer.android.com/training/app-links/deep-linking

https://developer.android.com/training/app-links/verify-site-associations

https://developers.google.com/digital-asset-links/tools/generator

인스턴트 앱을 만들기는 했으나, 실제 Google Play 배포까지는 또 다른 문제들이 있음.

인스턴트 앱에서 사용할 URL의 실제 웹 링크 소유자 확인이 가능해야 등록이 가능함.

Google Digital Asset Link 참조 : https://developers.google.com/digital-asset-links/

앱 콘텐츠에 대한 딥 링크 만들기

링크 클릭시나 프로그래밍 요청으로 웹 URI 인텐트가 호출되면 안드로이드 시스템에서 아래 동작을 순차적으로 시도함.

  1. URI가 지정된 경우 URI 처리 가능한 사용자 기본 설정 앱을 실행
  2. URI를 처리할 수 있는 앱 실행
  3. 다이얼로그에서 선택 가능한 앱들에서 사용자가 선택

수신 링크를 위한 인텐트 필터 추가

<action> : ACTION_VIEW 인텐트 액션 지정.

<data> : 하나 이상의 <data> 태그 추가.

각 태그는 URI 형식을 나타내며, 최소한 android:scheme 속성이 포함되어야 함.

속성들을 이용하여 URI 유형을 구체화 할 수 있음. android:path, pathPattern, pathPrefix 등

<category> : BROWSABLE 카테고리 포함. Intent 필터가 웹 브라우저에서 접근하기 위해 필요함.

DEFAULT 카테고리도 포함. 앱이 암시적 인텐트에도 응답할 수 있음.

이 방법이 아니라면 인텐트가 앱 구성 요소명을 지정하는 경우에만 적용이 가능함.

<activity
    android:name="com.example.android.GizmosActivity"
    android:label="@string/title_gizmos" >
    <intent-filter android:label="@string/filter_view_http_gizmos">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
        <data android:scheme="http"
              android:host="www.example.com"
              android:pathPrefix="/gizmos" />
        <!-- note that the leading "/" is required for pathPrefix-->
    </intent-filter>
    <intent-filter android:label="@string/filter_view_example_gizmos">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with "example://gizmos” -->
        <data android:scheme="example"
              android:host="gizmos" />
    </intent-filter>
</activity>

위 2개의 인텐트 필터는 <data> 요소에 의해서만 서로 다름.

동일한 필터에 여러 개의 <data> 요소를 포함할 수 있지만 고유한 URL을 선언하려는 경우 별도의 필터로 만들어야 함.

아래의 경우 https://www.example.com 이나 app://open.my.app 만 지원하는 것처럼 보임.

하지만 실제로는 app://www.example.com 이나 https://open.my.app 형식도 지원하게 됨.

<intent-filter>
  ...
  <data android:scheme="https" android:host="www.example.com" />
  <data android:scheme="app" android:host="open.my.app" />
</intent-filter>

보다 자세한 내용은 developer 문서를...

수신 인텐트에서 데이터 읽기

시스템이 인텐트 필터를 통해 액티비티를 시작한다면, 인텐트가 제공하는 데이터를 사용할 수 있음.

getData()나 getAction() 메소드를 호출하여 사용함. 일반적으로 onCreate()나 onStart() 시기에 사용함.

딥 링크 테스트

adb(Android Debug Bridge)를 통해서 액티비티 매니저 툴로 인텐트 필터 URI 지정한 앱을 테스트할 수 있음.

$ adb shell am start -W -a android.intent.action.VIEW -d <URI> <PACKAGE>
$ adb shell am start -W -a android.intent.action.VIEW -d "example://gizmos" com.example.android

Android 앱 링크 확인

안드로이드 앱 링크는 웹 URL이 앱의 특정 컨텐츠로 바로 연결되도록 해주는 특별한 타입의 딥 링크임.

안드로이드 앱 링크를 앱에 추가하려면 HTTP URL을 사용해 인텐트 필터를 정의하고,

앱과 웹 사이트 URL을 모두 소유하고 있는지 확인이 필요함.

시스템이 사용자의 URL 소유권을 확인하면 자동으로 해당 URL 인텐트를 앱으로 라우팅함.

앱과 웹 소유권을 확인하기 위한 단계

딥 링크와 앱 링크의 차이점

딥 링크는 앱의 특정 활동을 직접 입력할 수 있도록 하는 인텐트 필터임.

이러한 링크 중 하나를 클릭하면 사용자가 주어진 URL을 사용할 수 있는 여러 앱 중 하나를 선택할 수 있음.

안드로이드 앱 링크는 웹에 속한 것으로 확인된 웹 URL을 기반으로 하는 딥 링크임.

이 중 하나를 클릭하면 앱이 즉시 열리며, 앱 선택 화면이 나타나지 않음.

사용자가 링크 처리에 대한 선호도를 변경할 수도 있음.

Deep linksApp links
Intent URL schemehttphttps, or a custom schemeRequires http or https
Intent actionAny actionRequires android.intent.action.VIEW
Intent categoryAny categoryRequires android.intent.category.BROWSABLE and android.intent.category.DEFAULT
Link verificationNoneRequires a Digital Asset Links file served on you website with HTTPS
User experienceMay show a disambiguation dialog for the user to select which app to open the linkNo dialog; your app opens to handle your website links
CompatibilityAll Android versionsAndroid 6.0 and higher

앱 링크 확인 요청하기

앱의 링크 처리 확인을 활성화하려면 앱 매니페스트에서 아래를 포함하는 웹 URL 인텐트 필터 중 하나에서 android:autoVerify="true" 설정을 함.

  • <action> : android.intent.action.VIEW
  • <category> : android.intent.category.BROWSABLE, android.intent.category.DEFAULT
  • <data> : http 또는 https
<activity ...>

    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http" android:host="www.example.com" />
        <data android:scheme="https" />
    </intent-filter>

</activity>

android:autoVerify="true"가 인텐트 필터 중에 존재하는 앱일 경우,

Android 6.0 이상인 기기에 앱이 설치되면 시스템이 앱의 인텐트 필터에 있는 URL과 연결된 호스트를 확인함.

안드로이드 시스템은 위의 인텐트 필터의 각각의 고유한 호스트 이름에 대해 Digital Asset Links 파일을 쿼리함.

ex) https://hostname/.well-known/assetlinks.json

여러 호스트를 위한 앱 링크 지원시

시스템이 매니페스트의 모든 호스트에 대해 일치하는 Digital Asset Links 파일을 찾은 경우에만 앱이 지정된 URL 패턴의 기본 처리기로 설정됨.

<application>

  <activity android:name=”MainActivity”>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="http" android:host="www.example.com" />
      <data android:scheme="https" />
    </intent-filter>
  </activity>
  <activity android:name=”SecondActivity”>
    <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="www.example.net" />
    </intent-filter>
  </activity>

</application>

위의 경우 https://www.example.com/.well-known/assetlinks.json 과 https://www.example.net/.well-known/assetlinks.json이 없을 경우 앱 인텐트 필터 확인이 실패함.

여러 서브 도메인을 위한 앱 링크 지원시

Digital Asset Links 프로토콜은 인텐트 필터의 서브 도메인을 유니크한 개별 호스트로 취급함.

서브 도메인이 호스트가 다를 경우 각 도메인에 유효한 assetlinks.json을 가져야 함.

<application>
  <activity android:name=”MainActivity”>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="www.example.com" />
      <data android:scheme="https" android:host="mobile.example.com" />
    </intent-filter>
  </activity>
</application>

위의 경우 https://www.example.com/.well-known/assetlinks.json 와 https://mobile.example.com/.well-known/assetlinks.json가있어야 함.

호스트 명에 와일드카드(ex. *.example.com)를 선언한 경우 루트 호스트에 assethosts.json 파일을 게시해야 함.

<application>
  <activity android:name=”MainActivity”>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="*.example.com" />
    </intent-filter>
  </activity>
</application>

위의 경우 https://example.com/.well-known/assetlinks.json 가 있어야 함.

웹 사이트 연결 선언

Digital Asset Links JSON 파일은 웹 사이트와 연결된 앱을 표시하고 앱 URL 인텐트를 확인하기 위해 게시함.

JSON 파일은 아래 필드들을 사용.

  • package_name : 앱의 build.gradle 에 선언된 앱 ID

  • sha256_cert_fingerprints : 앱의 서명 인증서의 SHA256 fingerprints. 아래 명령으로 얻을 수 있음.

    $ keytool -list -v -keystore my-release-key.keystore

    이 필드는 여러 fingerprints를 지원하며, 디버그와 프로덕션 빌드와 같은 버전의 앱을 지원하는데 사용함.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

웹 사이트를 여러 앱과 연결시

한 assetlinks.json 파일에서 여러 앱과 연결을 선언할 수 있음.

인스턴트 앱의 경우 각 웹사이트 도메인에 하나의 인스턴트 앱만 지정 가능

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.puppies.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
  },
  {
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.monkeys.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

서로 다른 앱이 동일한 웹 호스트에서 여러 리소스에 대한 링크 처리 가능함.

ex) app1은 https://example.com/articles에 대한 인텐트 필터를 선언하고 app2는 https://example.com/videos에 대한 인텐트 필터를 선언 할 수 있음.

여러 웹 사이트를 단일 앱과 연결

여러 웹 사이트는 각가의 assetlinks.json 파일에서 동일한 앱을 선언할 수 있음.

아래는 app1과 example.com 및 example.net의 연결을 선언하는 예제임.

  1. https://www.example.com/.well-known/assetlinks.json

[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.mycompany.app1", "sha256_cert_fingerprints": ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"] } }]

https://www.example.net/.well-known/assetlinks.json 

```json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
  "namespace": "android_app",
  "package_name": "com.mycompany.app1",
  "sha256_cert_fingerprints":  ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]

JSON 확인 파일 게시

아래 위치에 JSON 확인 파일이 게시해야 함.

https://domain.name/.well-known/assetlinks.json

  • assetlinks.json 파일의 content-type을 application/json 형식으로 제공.
  • assetlinks.json 파일은 앱의 인텐트 필터가 HTTPS를 데이터 스킴으로 선언하는지 여부와 관계없이 HTTPS 연결을 통해 액세스할 수 있어야 함.
  • assetlinks.json 파일은 리다이렉션(301 또는 302 리다이렉션 아님) 없이 액세스할 수 있어야 함. 로봇(robots.txt) 에 assetlinks.json파일 크롤링을 허용해야 함.
  • 앱 링크가 여러 호스트 도메인을 지원하는 경우 각 도메인에 assetlinks.json 파일을 게시해야 함.
  • 매니페스트 파일에 dev/test URL이 있는 앱을 공개하지 말 것. 예를 들어 공개 키를 사용하여 액세스 할 수 없는 경우(VPN에서만 액세스 할 수 있는 URL 등) 이러한 경우 빌드 variants를 구성하여 dev 빌드에 대한 다른 매니페스트 파일 생성.

앱 링크 테스트

앱 연결 기능 구현시 시스템이 앱과 웹 사이트를 연결하고 URL 요청을 처리할 수 있는지 확인.

기존 명령문 파일을 테스트 하려면 Statement List Generator and Tester 툴을 사용.

https://developers.google.com/digital-asset-links/tools/generator

확인한 호스트 리스트 확인

앱에서 확인해야 하는 관련 호스트 목록을 확인.

인텐트 필터에 다음 속성 및 요소가 포함된 모든 URL 목록을 만듬.

  • android:scheme 속성 값이 http나 https인 것
  • android:host 속성이 도메인 URL 패턴과 같이 있는 것
  • android.intent.action.VIEW 카테고리 요소
  • android.intent.category.BROWSABLE 카테고리 요소

이 목록을 사용하여 각 호스트 및 하위 도메인에 Digital Asset Links JSON 파일이 제공되는지 확인.

Digital Asset Links 파일 확인

각 웹 사이트에 대해 Digital Asset Links API를 사용하여 JSON 파일이 제대로 호스팅되고 있는지 확인.

https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://<<domain.name:optional_port>>&relation=delegate_permission/common.handle_all_urls

URL 인텐트 테스트

앱과 연결할 웹 사이트 목록 확인 후 호스팅된 JSON 파일이 유효하다는 것을 확인 후 기기에 앱 설치.

비동기 확인 프로세스 완료시까지 최소 20초 대기.

아래 명령을 사용하여 시스템에서 앱을 확인하고 올바른 처리 설정됐는지 확인.

adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "http://<<domain.name:optional_port>>"

링크 정책 확인

테스트 과정으로 링크 처리를 위한 현재 시스템 설정을 확인할 수 있음.

아래 명령을 사용하여 연결된 장치의 모든 앱에 대한 기존 링크 처리 정책 리스트를 가져옴.

adb shell dumpsys package domain-preferred-apps

또는 아래와 같이 함

adb shell dumpsys package d

시스템이 확인 프레세스를 완료할 수 있도록 앱 설치 후 적어도 20초 정도 필요할 수 있음

위 명령은 장치에 정의된 각 사용자 또는 프로필의 목록을 반환하며, 아래 형식의 헤더가 있음.

App linkages for user 0:

위 헤더에 따른 내용은 아래와 같이 나열되며, 도메인과 연결된 앱이 표시됨.

Package: com.android.vending
Domains: play.google.com market.android.com
Status: always : 200000002
  • Package : 매니페스트에 선언된 패키지 명으로 연결된 앱 표시
  • Domains : 앱이 웹 링크를 처리하는 호스트의 전체 목록 표시. 빈 칸으로 구분.
  • Status : 앱의 현재 링크 처리 설정 표시. 인증을 통과했고, 매니페스트에 android:autoVerify=true 가 포함된 앱은 always를 표시함. 이 이후의 16진수는 시스템의 사용자 앱 연결 환경설정에 대한 기록이며, 이 값이 확인이 성공했는지 여부를 나타내지는 않음.

테스트

앱의 인텐트 필터를 지정하고 앱 링크 기준을 충족하는 모든 웹 사이트로 앱을 인증할 수 있어야 함.

<application>

    <activity android:name=”MainActivity”>
        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="www.example.com" />
            <data android:scheme="https" android:host="mobile.example.com" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="www.example2.com" />
        </intent-filter>
    </activity>

    <activity android:name=”SecondActivity”>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="account.example.com" />
        </intent-filter>
    </activity>

      <activity android:name=”ThirdActivity”>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="https" android:host="map.example.com" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="market" android:host="example.com" />
        </intent-filter>
      </activity>

</application>

플랫폼에서 위 목록에서 확인하려는 호스트 목록은 아래와 같음.

확인하지 않으려는 호스트 목록은 아래와 같음.

  • map.example.com (android.intent.category.BROWSABLE 없음)
  • market://example.com (http나 https 스킴이 없음.)


+ Recent posts