Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Android 8.0 동작 변경 사항

Android 8.0에는 새로운 기능 및 특징과 더불어 다양한 시스템 및 API 동작 변경 사항이 포함되어 있습니다. 이 문서에서는 여러분이 앱에서 숙지하고 고려해야 하는 몇 가지 주요 변경 사항을 소개하겠습니다.

이런 변경 사항은 대부분 대상으로 삼는 Android 버전과는 상관없이 모든 앱에 영향을 미칩니다. 그러나 몇몇 변경 사항은 Android 8.0를 대상으로 하는 앱에만 영향을 미칩니다. 최대한 명확성을 기하기 위해 이 페이지는 모든 API 레벨을 대상으로 하는 앱Android 8.0를 대상으로 하는 앱의 두 섹션으로 구분됩니다.

모든 API 레벨을 대상으로 하는 앱

이 동작은 Android 8.0 플랫폼에서 실행되는 모든 앱 에 적용되며, 대상으로 하는 API 레벨과는 상관이 없습니다. 모든 개발자는 이러한 변경 사항을 검토해야 하며 이를 적절히 지원하도록 앱을 수정해야 합니다.

백그라운드 실행 제한

배터리 수명을 개선하기 위해 Android 8.0에서 도입된 변경 사항 중 하나입니다. 앱이 캐시된 상태로 진입하고 활성 구성 요소가 없으면 시스템은 앱이 보유한 모든 가동 잠금(wakelock)을 해제합니다.

또한, 기기 성능을 개선하기 위해 시스템에서는 포그라운드에서 실행 중이지 않은 앱의 특정한 동작을 제한합니다. 구체적인 사항은 다음과 같습니다.

  • 백그라운드에서 실행 중인 앱은 이제 백그라운드 서비스에 얼마나 자유롭게 액세스할 수 있는지에 대한 제한이 있습니다.
  • 앱은 대부분의 암시적 브로드캐스트(즉, 앱을 특정해서 대상으로 하지는 않는 브로드캐스트)에 등록할 때 앱의 매니페스트를 사용할 수 없습니다.

기본적으로, 이들 제한은 O를 대상으로 하는 앱에만 적용됩니다. 그러나 앱이 O를 대상으로 하지 않더라도 사용자가 Settings 화면에서 이들 앱에 대한 제한을 활성화할 수 있습니다.

Android 8.0에는 또한 다음과 같은 특정 메서드에 대한 변경 사항이 있습니다.

  • 백그라운드 서비스 생성이 허용되지 않는 상황에서 Android 8.0를 대상으로 하는 앱이 startService() 메서드를 사용하려고 시도할 경우 이제 이 메서드가 IllegalStateException을 발생합니다.
  • 새로운 Context.startForegroundService() 메서드가 포그라운드 서비스를 시작합니다. 앱이 백그라운드에 있는 중에도 시스템은 앱이 Context.startForegroundService()를 호출하도록 허용합니다. 그러나 앱은 서비스가 생성된 후 5초 이내에 해당 서비스의 startForeground() 메서드를 호출해야 합니다.

자세한 정보는 백그라운드 실행 제한을 참조하세요.

Android 백그라운드 위치 제한

배터리, 사용자 환경 및 시스템 상태를 유지하기 위해 Android 8.0를 실행하는 기기에서 백그라운드 앱 사용 시 위치 업데이트를 받는 빈도가 줄어듭니다. 이 동작 변경 사항은 Google Play 서비스를 포함한 위치 업데이트를 수신하는 모든 앱에 영향을 미칩니다.

이런 변경 사항은 다음 API에 영향을 미칩니다.

  • FLP(Fused Location Provider)
  • Geofencing
  • GNSS Measurements
  • Location Manager
  • Wi-Fi Manager

앱이 예상대로 실행되도록 보장하려면 다음 단계를 완료하세요.

  • 앱의 로직을 검토하고 최신 위치 API를 사용 중인지 확인합니다.
  • 각 사용 사례에 맞게 앱이 예상대로 동작하는지 테스트합니다.
  • 사용자의 현재 위치에 따라 사용 사례를 처리하려면 FLP(Fused Location Provider)나 지오펜싱 사용을 고려합니다.

이런 변경 사항에 대한 자세한 내용은 백그라운드 위치 제한을 참조하세요.

앱 단축키

Android 8.0에서는 앱 단축키에 다음과 같은 변경 사항이 있습니다.

  • com.android.launcher.action.INSTALL_SHORTCUT 브로드캐스트는 이제 비공개의 암시적 브로드캐스트이므로 더 이상 앱에 아무런 영향도 주지 못합니다. 대신에 ShortcutManager 클래스에서requestPinShortcut() 메서드를 사용하여 앱 단축키를 만들어야 합니다.
  • 이제 ACTION_CREATE_SHORTCUT 인텐트가 앱 단축키를 만들 수 있으며, 이 단축키는 ShortcutManager 클래스를 사용하여 관리합니다. 이 인텐트는 또한 ShortcutManager와 상호작용하지 않는 이전 런처 단축키를 만들 수도 있습니다. 이전에는 이 인텐트로 이전 런처 단축키만 만들 수 있었습니다.
  • requestPinShortcut()을 사용하여 만들어진 단축키와 ACTION_CREATE_SHORTCUT 인텐트를 처리하는 액티비티에서 만들어진 단축키는 이제 완벽한 기능을 갖춘 앱 단축키입니다. 따라서 앱이 ShortcutManager에 있는 메서드를 사용하여 단축키를 업데이트할 수 있습니다.
  • 이전 단축키는 Android의 이전 버전에서 사용하던 기능을 그대로 유지하고 있지만, 앱에서 이전 단축키를 앱 단축키로 수동 변환해야 합니다.

앱 단축키의 변경 사항에 대해 자세히 알아보려면 단축키 및 위젯 고정 미리보기 기능 가이드를 참조하세요.

언어와 국제화

Android 7.0(API 레벨 24)에서는 기본 Category Locale을 지정할 수 있다는 개념을 도입했지만, 일부 API는 기본 DISPLAY Category Locale을 대신 사용해야 하는 경우에 인수 없이 제네릭 Locale.getDefault() 메서드를 계속 사용했습니다. 이제 Android 8.0에서는 다음과 같은 메서드가 Locale.getDefault() 대신 Locale.getDefault(Category.DISPLAY)를 사용합니다.

Locale 인수에 대해 지정된 displayScript 값을 사용할 수 없을 때 Locale.getDisplayScript(Locale) 역시 Locale.getDefault()로 대체합니다.

추가적인 언어 및 국제화 관련 변경 사항은 다음과 같습니다.

  • Currency.getDisplayName(null)을 호출하면 NullPointerException이 발생하며, 이는 문서에 설명된 동작과 일치합니다.
  • 시간대 이름 파싱이 바뀌었습니다. 이전에는 Android 기기를 부팅할 때 샘플링한 시스템 클록 값을 사용하여 날짜 및 시간 파싱에 사용되는 시간대 이름을 캐시했습니다. 따라서 부팅 시간에 시스템 클록이 잘못되었거나 드물기는 하지만 다른 시간대로 설정되어 있는 경우 파싱이 부정적인 영향을 줄 수 있었습니다.

    이제는 일반적인 경우에 시간대 이름을 파싱할 때 파상 로직에서 ICU와 현재 시스템 클록 값을 사용합니다. 이렇게 변경함으로써 더 정확한 결과를 얻을 수 있으며, 앱이 SimpleDateFormat 같은 클래스를 사용할 때 이전 Android 버전과 다를 수 있습니다.

  • Android 8.0에서는 ICU의 버전을 버전 58로 업데이트합니다.

경고 창

앱이 SYSTEM_ALERT_WINDOW 권한을 사용하고 다음과 같은 창 유형 중 하나를 사용하여 다른 앱 및 시스템 창 위에 경고 창을 표시하려고 할 경우

이런 창은 항상 TYPE_APPLICATION_OVERLAY 창 유형을 사용하는 창 아래에 나타납니다. Android 8.0를 대상으로 하는 앱의 경우, TYPE_APPLICATION_OVERLAY 창 유형을 사용하여 경고 창을 표시합니다.

자세한 정보는 Android 8.0를 대상으로 하는 앱에 대한 동작 변경 사항 내에서 경고 창을 위한 일반적인 창 유형 섹션을 참조하세요.

입력 및 탐색

Chrome OS 및 태블릿과 같은 기타 대형 폼 팩터에서 Android 앱의 출현과 함께 Android 앱 내에서 키보드 탐색을 사용하는 경우가 늘고 있습니다. 저희는 Android 8.0 내에서 키보드를 탐색 입력 기기로 사용하기로 했으며, 그 결과 화살표와 탭 기반의 탐색을 위한 더욱 안정적이고 예측 가능한 모델이 구현되었습니다.

특히 요소 포커스 동작에 다음과 같은 변경 사항을 적용했습니다.

  • View 객체(포그라운드 또는 백그라운드 드로어블)에 대해 포커스 상태 색상이 정의되지 않은 경우, 이제 프레임워크는 View에 대해 기본 포커스 하이라이트 색상을 설정합니다. 이 포커스 하이라이트는 액티비티의 테마를 기반으로 하는 리플 드로어블입니다.

    View 객체가 포커스를 받을 때 이 기본 하이라이트를 사용하지 않도록 하려면, View가 포함된 레이아웃 XML 파일에서 android:defaultFocusHighlightEnabled 특성을 false로 설정하거나, 앱의 UI 로직에서 falsesetDefaultFocusHighlightEnabled()로 전달합니다.

  • 키보드 입력이 UI 요소 포커스에 어떤 영향을 미치는지 테스트하려면, Drawing > Show layout bounds 개발자 옵션을 사용하면 됩니다. Android 8.0에서 이 옵션은 포커스가 있는 현재 요소 위에 "X" 아이콘을 표시합니다.

또한, Android 8.0의 모든 툴바 요소는 자동으로 키보드 탐색 클러스터가 되므로, 전반적으로 사용자가 각 툴바를 탐색하기가 더 쉬워집니다.

앱 내에서 키보드 탐색 지원을 개선하는 방법에 대해 자세히 알아보려면 키보드 탐색 지원 가이드를 참조하세요.

웹 양식 자동완성

Android Autofill Framework가 자동완성 기능을 위한 내장 지원을 제공하므로, Android 8.0를 실행하는 기기에 설치된 앱에 대해 WebView 객체와 관련된 다음 메서드가 변경되었습니다.

WebSettings
  • getSaveFormData() 메서드는 이제 false를 반환합니다. 이전에 이 메서드는 true를 반환했습니다.
  • setSaveFormData()를 호출해도 더 이상 아무런 효과가 없습니다.
WebViewDatabase
  • clearFormData()를 호출해도 더 이상 아무런 효과가 없습니다.
  • hasFormData() 메서드는 이제 false를 반환합니다. 이전에 이 메서드는 양식에 데이터가 포함되어 있을 때 true를 반환했습니다.

접근성

이제 접근성 서비스는 앱의 TextView 객체 내에 있는 모든 ClickableSpan 인스턴스를 인식합니다.

앱의 접근성을 더 높이는 방법에 대해 알아보려면 접근성을 참조하세요.

네트워킹 및 HTTP(S) 연결

Android 8.0는 네트워킹 및 HTTP(S) 연결에 대해 다음과 같은 동작 변경 사항을 포함합니다.

  • 본문이 없는 OPTIONS 요청에 Content-Length: 0 헤더가 있습니다. 이전에는 Content-Length 헤더가 없었습니다.
  • HttpURLConnection은 슬래시가 있는 호스트 또는 기관 이름 뒤에 슬래시를 추가하여 빈 경로를 포함하는 URL을 정규화합니다. 예를 들어, http://example.comhttp://example.com/으로 변환합니다.
  • ProxySelector.setDefault()를 통해 설정되는 사용자설정 프록시 선택기는 요청한 URL의 주소(구성표, 호스트 및 포트)만 대상으로 합니다. 따라서 이런 값만 기준으로 프록시를 선택할 수 있습니다. 사용자설정 프록시 선택기로 전달되는 URL은 요청한 URL의 경로, 쿼리 매개변수 또는 조각을 포함하지 않습니다.
  • URI는 빈 레이블을 포함할 수 없습니다.

    이전에는 플랫폼에서 호스트 이름에 빈 레이블을 허용하는 해결 방법을 지원했는데, 사실 이는 URI를 잘못된 방법으로 사용하는 것입니다. 이 해결 방법은 이전 libcore 릴리스와의 호환성을 위한 것이었습니다. 이 API를 잘못 사용하는 개발자에게는 다음과 같이 ADB 메시지가 표시됩니다. "URI example..com has empty labels in the hostname. This is malformed and will not be accepted in future Android releases." Android 8.0에서는 이 해결 방법이 제거되어 사용할 수 없고, 형식이 잘못된 URI에 대해서는 null이 반환됩니다.

  • Android 8.0의 HttpsURLConnection 구현에서는 안전하지 않은 TLS/SSL 프로토콜 버전 대체를 수행하지 않습니다.
  • HTTP(S) 연결의 터널링 처리는 다음과 같이 변경되었습니다.
    • 연결에 걸쳐 HTTPS 연결을 터널링하는 경우, 시스템에서는 이 정보를 중간 서버로 보낼 때 호스트 라인에서 포트 번호(:443)를 올바로 배치합니다. 이전에는 CONNECT 라인에서 포트 번호만 발생했습니다.
    • 시스템에서는 더 이상 터널링된 요청에서 프록시 서버로 사용자-에이전트 및 프록시-승인 헤더를 보내지 않습니다.

      시스템에서는 터널을 설정할 때 더 이상 터널링된 Http(s)URLConnection을 통해 프록시로 프록시-승인 헤더를 보내지 않습니다. 그 대신, 시스템에서는 프록시-승인 헤더를 생성하고 그 프록시가 초기 요청에 대한 응답으로 HTTP 407을 보낼 때 이 헤더를 프록시로 보냅니다.

      마찬가지로, 시스템에서는 터널링된 요청에서 터널을 설정하는 프록시 요청으로 사용자-에이전트 헤더를 더 이상 복사하지 않습니다. 대신에 라이브러리가 그 요청에 대한 사용자-에이전트 헤더를 생성합니다.

  • 이전에 실행한 connect() 메서드가 실패한 경우 send(java.net.DatagramPacket) 메서드는 SocketException을 발생시킵니다.
    • 내부 오류가 있는 경우 DatagramSocket.connect()는 pendingSocketException을 설정합니다. Android 8.0 이전에는 send() 호출이 성공했더라도 후속적인 recv() 호출 시 SocketException이 발생했습니다. 지금은 일관성을 위해 두 호출에서 모두 SocketException이 발생합니다.
  • InetAddress.isReachable()은 ICMP를 시도한 후에 TCP Echo 프로토콜로 대체합니다.
    • google.com과 같이 포트 7(TCP Echo)을 차단하는 호스트는 이제 ICMP Echo 프로토콜을 허용하는 경우 연결 가능한 호스트가 될 수 있습니다.
    • 실제로 연결할 수 없는 호스트의 경우, 이런 변경 사항이 의미하는 바는 호출 반환까지 두 배의 시간이 소요된다는 점입니다.

블루투스

Android 8.0에서는 ScanRecord.getBytes() 메서드가 검색하는 데이터의 길이가 다음과 같이 변경되었습니다.

  • getBytes() 메서드는 수신되는 바이트 수에 관해 어떤 가정도 하지 않습니다. 따라서 앱이 최소 또는 최대 수신 바이트 수에 종속되면 안 됩니다. 그 대신, 결과 배열의 길이를 계산해야 합니다.
  • 블루투스 5 호환 기기는 이전의 최대 바이트 수인 60바이트를 초과하는 길이의 데이터를 반환할 수도 있습니다.
  • 원격 기기가 스캔 응답을 제공하지 않을 경우 60바이트보다 적은 데이터가 반환될 수도 있습니다.

원활한 연결

Android 8.0에서는 다양한 Wi-Fi 설정이 개선되어 최상의 사용자 환경을 제공하는 Wi-Fi 네트워크를 더 쉽게 선택할 수 있습니다. 구체적인 변경 사항은 다음과 같습니다.

  • 안정성 및 신뢰성 개선
  • 더욱 직관적으로 읽을 수 있는 UI
  • 하나로 통합된 Wi-Fi Preferences 메뉴
  • 호환 기기에서 저장된 네트워크 중 신호 품질이 우수한 네트워크가 근처에 있을 때 Wi-Fi 자동 활성화

보안

Android 8.0에는 다음과 같은 보안 관련 변경 사항이 있습니다.

  • 플랫폼에 더 이상 SSLv3를 지원하지 않습니다.
  • TLS 프로토콜 버전 협상을 잘못 구현하는 서버에 대한 HTTPS 연결을 설정할 때 HttpsURLConnection이 더 이상 이전의 TLS 프로토콜 버전으로 대체하여 재시도하는 해결 방법을 시도하지 않습니다.
  • Android 8.0는 Secure Computing(SECCOMP) 필터를 모든 앱에 적용합니다. 허용되는 syscall의 목록은 바이오닉을 통해 노출되는 항목으로 제한됩니다. 이전 버전과의 호환성을 위해 여러 다른 syscall이 제공되지만 사용하지 않는 것이 좋습니다.
  • 이제 앱의 WebView 객체가 다중 프로세스 모드에서 실행됩니다. 보안 강화를 위해, 포함하는 앱의 프로세스와는 별개로 격리된 프로세스에서 웹 콘텐츠가 처리됩니다.
  • 이름이 -1이나 -2로 끝나는 디렉토리에 APK가 있다고 더 이상 간주할 수 없습니다. 앱은 sourceDir을 사용하여 디렉토리를 가져오고 디렉토리 형식에 직접 의존하면 안 됩니다.
  • 네이티브 라이브러리의 사용과 관련된 보안 기능 향상에 대해서는 네이티브 라이브러리를 참조하세요.

더 안전한 앱을 만들기 위한 자세한 지침에 대해서는 Android 개발자를 위한 보안을 참조하세요.

개인정보 보호정책

Android 8.0는 플랫폼에 다음과 같은 개인정보 관련 사항을 변경합니다.

  • 이제 플랫폼이 식별자를 다르게 처리합니다.
    • Android 8.0의 버전(API 레벨 26)에 OTA 이전에 설치된 앱의 경우, OTA 이후에 앱을 제거했다가 다시 설치하지 않는 한 ANDROID_ID의 값이 그대로 유지됩니다. 개발자는 키/값 백업을 사용하여 기존 값과 새 값을 연결하여 OTA 이후에 제거하는 과정에서 값을 보존할 수 있습니다.
    • Android 8.0를 실행하는 기기에 설치된 앱의 경우 ANDROID_ID의 값은 이제 사용자뿐 아니라 앱 서명 키별로 범위가 지정됩니다. ANDROID_ID의 값은 앱 서명 키, 사용자 및 기기의 각 조합에 대해 고유합니다. 따라서 동일한 기기에서 실행 중인 다른 서명 키가 있는 앱은 (사용자가 동일하더라도) 더 이상 동일한 Android ID를 보지 못합니다.
    • 서명 키가 같고 OTA 이전에 O 버전에 앱이 설치되지 않은 한, ANDROID_ID의 값은 패키지 제거 또는 재설치 시 변경되지 않습니다.
    • 시스템 업데이트로 인해 패키지 서명 키가 바뀌더라도 ANDROID_ID의 값은 변경되지 않습니다.
    • 간단한 표준 시스템에서 앱을 통해 수익을 창출하려면 광고 ID를 사용하세요. 광고 ID는 광고를 위해 사용자가 재설정할 수 있는 고유한 ID로, Google Play 서비스에서 제공됩니다.

  • net.hostname 시스템 속성을 쿼리하면 null 결과가 발생합니다.

포착되지 않는 예외 로그 기록

기본 Thread.UncaughtExceptionHandler를 호출하지 않는 Thread.UncaughtExceptionHandler를 앱이 설치하는 경우, 시스템은 포착되지 않는 예외가 발생할 때 앱을 중단하지 않습니다. Android 8.0부터, 시스템에서는 이런 상황이 발생할 때 예외 스택 추적을 로그에 기록합니다. 플랫폼의 이전 버전에서는 예외 스택 추적을 기록하지 않았습니다.

사용자설정 Thread.UncaughtExceptionHandler 구현은 항상 기본 핸들러를 호출하는 것이 좋습니다. 이 권장 사항을 따르는 앱은 Android 8.0의 변경 사항에 영향을 받지 않습니다.

연락처 제공자 사용 통계 변경

Android의 이전 버전에서는 개발자가 Contacts Provider 구성 요소를 사용하여 각 연락처에 대한 사용 데이터를 가져올 수 있습니다. 이 사용 데이터는 연락처로 연락한 횟수와 마지막으로 연락한 시간을 비롯하여, 해당 연락처와 관련된 각각의 이메일 주소와 전화 번호에 대한 정보를 보여줍니다. READ_CONTACTS 권한을 요청하는 앱은 이 데이터를 읽을 수 있습니다.

앱이 READ_CONTACTS 권한을 요청하는 경우 이 데이터를 계속 읽을 수 있습니다. Android 8.0를 시작할 때, 사용 데이터를 쿼리하면 정확한 값이 아니라 근사값이 반환됩니다. Android 시스템은 내부적으로 정확한 값을 유지 관리하므로, 이 변경 사항이 자동 완성 API에 영향을 미치는 것은 아닙니다.

이 동작 변경 사항은 다음과 같은 쿼리 매개변수에 영향을 줍니다.

컬렉션 처리

이제 AbstractCollection.removeAll()AbstractCollection.retainAll()은 항상 NullPointerException을 발생시킵니다. 이전에는 컬렉션이 비어있을 때는 NullPointerException이 발생하지 않았습니다. 이런 변경은 동작이 문서와 일치하도록 만듭니다.

Android 엔터프라이즈

Android 8.0에서는 DPC(기기 정책 컨트롤러)를 비롯하여 엔터프라이즈 앱의 일부 API와 기능에 대한 동작이 변경되었습니다. 변경 사항은 다음과 같습니다.

  • 완벽히 관리되는 기기에서 앱이 작업 프로필을 지원하도록 도와주는 새로운 동작.
  • 시스템 업데이트 처리, 앱 확인 및 인증에 대한 변경을 통해 기기와 시스템의 무결성 개선.
  • 프로비저닝, 알림, Recents 화면 및 상시 접속 VPN에 대한 사용자 환경 개선.

Android 8.0의 모든 엔터프라이즈 변경 사항을 확인하고 이 변경 사항이 앱에 미치는 영향을 알아보려면 Android 엔터프라이즈를 참조하세요.

Android 8.0를 대상으로 하는 앱

이 동작 변경 사항은 O 플랫폼 이상을 대상으로 하는 앱에만 적용됩니다. Android 8.0 이상에 대해 컴파일되는 앱이나 targetSdkVersion을 Android 8.0 이상으로 설정하는 앱은 이러한 동작을 적절히 지원하도록 앱을 수정해야 합니다.

경고 창

SYSTEM_ALERT_WINDOW 권한을 사용하는 앱은 다른 앱 및 시스템 창 위에 경고 창을 표시하기 위해 더 이상 다음과 같은 창 유형을 사용할 수 없습니다.

대신에 앱은 TYPE_APPLICATION_OVERLAY라는 새로운 창 유형을 사용해야 합니다.

TYPE_APPLICATION_OVERLAY 창 유형을 사용하여 앱에 대한 경고 창을 표시할 때 새로운 창 유형의 다음 특징을 염두에 두시기 바랍니다.

  • 앱의 경고 창은 항상 주요 시스템 창(예: 상태 표시줄 및 IME) 아래에 나타납니다.
  • 시스템에서 화면 표시를 개선하기 위해 TYPE_APPLICATION_OVERLAY 창 유형을 사용하는 창을 이동하거나 크기를 조정할 수 있습니다.
  • 알림 창을 열면, TYPE_APPLICATION_OVERLAY 창 유형을 사용해 나타나는 경고 창을 앱이 표시하지 못하도록 차단하는 설정을 사용자가 액세스할 수 있습니다.

콘텐츠 변경 알림

Android 8.0에서는 Android 8.0를 대상으로 하는 앱에서 ContentResolver.notifyChange()registerContentObserver(Uri, boolean, ContentObserver)가 동작하는 방식이 변경됩니다.

이들 API의 경우 이제는 모든 URI에 있는 권한에 대해 유효한 ContentProvider를 정의해야 합니다. 관련된 권한을 가진 유효한 ContentProvider를 정의하면 악성 앱으로 인해 콘텐츠가 변경되는 것을 막아주며, 개인 데이터가 악성 앱에 유출될 가능성을 차단합니다.

뷰 포커스

클릭 가능한 View 객체는 이제 기본적으로 포커스도 가능합니다. View 객체를 클릭할 수는 있지만 포커스를 받을 수 없도록 하려면, View를 포함하는 레이아웃 XML 파일에서 android:focusable 속성을 false로 설정하거나, 앱의 UI 로직에서 setFocusable()false를 전달합니다.

보안

앱의 네트워크 보안 구성에서 지원되는 일반 텍스트 트래픽을 옵트아웃하는 경우, 이 앱의 WebView 객체가 HTTP를 통해 웹사이트에 액세스할 수 없습니다. 각 WebView 객체는 대신 HTTPS를 사용해야 합니다.

더 안전한 앱을 만들기 위한 자세한 지침에 대해서는 Android 개발자를 위한 보안을 참조하세요.

계정 액세스 및 검색 가능 여부

인증자가 계정을 소유하고 있거나 사용자가 계정 액세스 권한을 허용하지 않은 경우에는 앱이 더 이상 사용자 계정에 액세스할 수 없습니다. 이제는 GET_ACCOUNTS 권한만으로는 충분하지 않습니다. 앱이 계정에 액세스할 권한을 얻으려면 AccountManager.newChooseAccountIntent() 또는 인증자에 특정한 메서드를 사용해야 합니다. 계정에 대한 액세스 권한을 얻은 후에는 앱이 AccountManager.getAccounts()를 호출하여 계정에 액세스할 수 있습니다.

Android 8.0는 LOGIN_ACCOUNTS_CHANGED_ACTION의 지원을 중단합니다. 대신에 앱은 addOnAccountsUpdatedListener()를 사용하여 런타임 중에 계정에 대한 업데이트를 얻어야 합니다.

계정 액세스와 검색 가능 여부에 대해 추가되는 새로운 API와 메서드에 대한 자세한 정보는 이 문서의 새 API 섹션에서 계정 액세스와 검색 가능 여부를 참조하세요.

개인정보 보호정책

다음 변경 사항은 Android 8.0에서의 개인정보에 영향을 미칩니다.

  • 시스템 속성 net.dns1, net.dns2, net.dns3net.dns4를 더 이상 사용할 수 없는데, 이런 변화를 통해 플랫폼에서 개인정보 보호를 강화할 수 있습니다.
  • DNS 서버와 같은 네트워킹 정보를 얻기 위해, ACCESS_NETWORK_STATE 권한이 있는 앱은 NetworkRequest 또는 NetworkCallback 객체를 등록할 수 있습니다. Android 5.0(API 레벨 21) 이상에서 이런 클래스를 사용할 수 있습니다.
  • Build.SERIAL은 지원 중단됩니다. 하드웨어 일련번호를 알 필요가 있는 앱은 대신에 READ_PHONE_STATE 권한이 필요한 새로운 Build.getSerial() 메서드를 사용해야 합니다.
  • LauncherApps API는 더 이상 직장 프로필 앱이 기본 프로필에 대한 정보를 획득하도록 허용하지 않습니다. 직장 프로필에 사용자가 있는 경우 LauncherApps API는 마치 같은 프로필 그룹 내의 다른 프로필에는 아무런 앱도 설치되어 있지 않은 것처럼 동작합니다. 전과 마찬가지로, 관련 없는 프로필에 액세스하려고 하면 SecurityException이 발생합니다.

권한

Android 8.0 이전에는 앱이 런타임에 권한을 요청하고 권한이 허용된 경우, 시스템 역시 같은 권한 그룹에 속하고 매니페스트에서 등록된 나머지 권한을 앱에 잘못 허용했습니다.

Android 8.0를 대상으로 하는 앱의 경우 이 동작이 수정되었습니다. 앱에는 명시적으로 요청한 권한만 허용됩니다. 하지만 사용자가 앱에 권한을 허용하고 나면 해당 권한 그룹에서 권한에 대한 이후의 모든 요청이 자동으로 허용됩니다.

예를 들어, 앱이 매니페스트에 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE를 모두 나열한다고 가정해봅시다. 앱이 READ_EXTERNAL_STORAGE를 요청하고 사용자가 이를 허용합니다. 앱이 API 레벨 24 이하를 대상으로 하는 경우 시스템 역시 WRITE_EXTERNAL_STORAGE를 동시에 허용합니다. 같은 STORAGE 권한 그룹에 속하고 매니페스트에도 등록되기 때문입니다. 앱이 Android 8.0를 대상으로 지정하는 경우에는 시스템이 READ_EXTERNAL_STORAGE 만 허용합니다. 하지만 앱이 나중에 WRITE_EXTERNAL_STORAGE를 요청하면 시스템이 사용자에게 묻지 않고 즉시 해당 권한을 허용합니다.

미디어

  • 프레임워크는 오디오 더킹을 수행합니다. AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK의 경우 애플리케이션이 포커스를 잃지 않습니다. 더킹 대신 일시 중지가 필요한 애플리케이션에는 새로운 API를 사용할 수 있습니다. 참고로, Android 8.0 1 릴리스에서는 이 동작이 구현되지 않습니다.
  • 사용자가 전화를 받을 때, 통화하는 동안 활성 미디어 스트림의 사운드가 음소거됩니다.
  • 모든 오디오 관련 API는 오디오 재생 사용 사례를 설명하기 위해 오디오 스트림 형식이 아닌 AudioAttributes를 사용해야 합니다. 볼륨 컨트롤에 대해서만 오디오 스트림 형식을 계속 사용하세요. 스트림 형식을 다른 용도(예: 지원 중단된 AudioTrack constructor)로 사용해도 작동하긴 하지만, 시스템이 이를 오류로 기록합니다.
  • AudioTrack을 사용 중인 경우 애플리케이션이 충분히 큰 오디오 버퍼를 요청하면, 프레임워크는 딥 버퍼 출력을 사용하려고 시도합니다(사용 가능한 경우).
  • Android 8.0에서는 미디어 버튼 이벤트가 다음과 같이 다르게 처리됩니다.
    1. UI 액티비티에서 미디어 버튼 처리가 변경되지 않았습니다. 포그라운드 액티비티는 미디어 버튼 처리에서 여전히 우선순위가 높습니다.
    2. 포그라운드 액티비티가 미디어 버튼을 처리하지 않으면, 시스템은 로컬에서 가장 최근에 오디오를 재생한 앱으로 미디어 버튼을 라우팅합니다. 어떤 앱이 미디어 버튼 이벤트를 수신하는지 확인할 때 미디어 세션의 활성 상태, 플래그 및 재생 상태는 더 이상 고려되지 않습니다. 미디어 세션은 앱이 setActive(false)를 호출한 후에도 미디어 버튼 이벤트를 수신할 수 있습니다.
    3. 앱의 미디어 세션이 해제된 경우에는 시스템에 미디어 버튼 이벤트가 있으면 이를 앱의 MediaButtonReceiver로 보냅니다.
    4. 다른 모든 경우에, 시스템이 미디어 버튼 이벤트를 삭제합니다. 잘못된 앱의 재생을 시작하는 것보다는 아무 것도 재생하지 않는 편이 낫습니다.

    새로운 미디어 버튼 라우팅 로직은 다음 다이어그램에 요약되어 있습니다.

    미디어 버튼 라우팅

네이티브 라이브러리

Android 8.0를 대상으로 하는 앱에서, 네이티브 라이브러리는 쓰기 및 실행이 모두 가능한 로드 세그먼트를 포함한 경우에 더 이상 로드되지 않습니다. 이 변경 사항으로 인해, 잘못된 로드 세그먼트를 포함한 네이티브 라이브러리가 있는 앱은 작동을 멈출 수 있습니다. 이는 보안 강화 조치에 따른 것입니다.

자세한 정보는 쓰기 가능 및 실행 가능 세그먼트를 참조하세요.

이전의 Developer Preview 릴리스와 마찬가지로, Android 8.0 역시 링커와 관련된 모든 문제를 더욱 자세히 볼 수 있습니다. 링커 변경 사항은 앱이 대상으로 하는 API 레벨과 연계됩니다. 대상으로 지정된 API 레벨에서 링커 변경 사항이 있으면 앱이 라이브러리를 로드할 수 없습니다. 링커 변경이 발생하는 API 레벨보다 낮은 API 레벨을 대상으로 하는 경우에는 logcat이 경고를 표시합니다. Preview 릴리스 중에는 링커 관련 문제가 logcat에 나타날 뿐 아니라 알림 메시지로도 나타납니다. 이런 변경 사항 덕분에 이전에 API 레벨에서 경고가 오류가 되는 문제에 대한 가시성이 향상되었습니다.

컬렉션 처리

Android 8.0에서는 Collections.sort()List.sort() 위에 구현됩니다. Android 7.x(API 레벨 24 및 25)에서는 그 반대였습니다. List.sort()의 기본 구현이 Collections.sort()를 호출했습니다.

이렇게 변경함으로써 Collections.sort()가 최적화된 List.sort() 구현을 이용할 수 있지만, 다음과 같은 제약 조건이 있습니다.

  • List.sort()의 구현이 Collections.sort()를 호출하면 안 됩니다. 호출할 경우 무한 재귀로 인해 스택 오버플로가 발생하기 때문입니다. 대신에 List 구현에서 기본 동작을 원할 경우에는 sort() 재정의를 피해야 합니다.

    상위 클래스가 sort()를 부적절하게 구현하는 경우 보통 List.toArray(), Arrays.sort()ListIterator.set() 위에 빌드한 구현으로 List.sort()를 재정의하는 것은 괜찮습니다. 예:

    @Override
    public void sort(Comparator<? super E> c) {
      Object[] elements = toArray();
      Arrays.sort(elements, c);
      ListIterator<E> iterator = (ListIterator<Object>) listIterator();
      for (Object element : elements) {
        iterator.next();
        iterator.set((E) element);
      }
    }
    

    대부분의 경우, API 레벨에 따라 다른 기본 구현에 위임하는 구현으로 List.sort()를 재정의할 수도 있습니다. 예:

    @Override
    public void sort(Comparator<? super E> comparator) {
      if (Build.VERSION.SDK_INT <= 25) {
        Collections.sort(this);
      } else {
        super.sort(comparator);
      }
    }
    

    모든 API 레벨에서 sort() 메서드를 사용할 수 있도록 하고 싶어 후자만 수행할 경우, sort()를 재정의하는 대신 sortCompat() 같이 고유한 이름을 지정하는 것을 고려해 보세요.

  • Collections.sort()는 이제 sort()를 호출하는 List 구현에서 구조적 수정으로 간주됩니다. 예를 들어, Android 8.0 이전의 플랫폼 버전에서 ArrayList를 반복하고 이렇게 반복하는 도중에 sort()를 호출하면, List.sort()를 호출하여 정렬이 수행된 경우 ConcurrentModificationException이 발생했을 것입니다. Collections.sort()는 예외를 발생하지 않았습니다.

    이렇게 변경함으로써 플랫폼 동작이 더욱 일관성을 띄게 됩니다. 어떤 접근 방식을 취하든, 이제는 ConcurrentModificationException이 발생합니다.

클래스 로드 동작

Android 8.0는 클래스 로더가 새 클래스를 로드할 때 런타임 가정을 위반하지 않는지 확인합니다. 이 경우 클래스가 Java(forName())로부터 참조되는지, Dalvik 바이트코드로부터 참조되는지 아니면 JNI로부터 참조되는지 확인합니다. 플랫폼은 Java에서 loadClass() 메서드로의 직접 호출은 가로채지 않으며, 이러한 호출의 결과를 확인하지도 않습니다. 이 동작은 올바로 동작하는 클래스 로더의 기능에 영향을 미쳐서는 안됩니다.

플랫폼은 클래스 로더가 반환하는 클래스의 설명자가 예상되는 설명자와 일치하는지 확인합니다. 반환된 설명자가 일치하지 않으면 플랫폼은 NoClassDefFoundError 오류를 발생시키고, 불일치를 설명하는 상세 메시지를 예외에 저장합니다.

플랫폼은 또한 요청된 클래스의 설명자가 올바른지 확인합니다. 이 확인에서는 GetFieldID()와 같은 클래스를 간접적으로 로드하는 JNI 호출을 찾아내고, 잘못된 설명자를 해당 클래스로 전달합니다. 예를 들어, 잘못된 서명으로 인해 서명이 java/lang/String인 필드를 찾을 수 없는 경우 이 서명은 Ljava/lang/String;이 되어야 합니다.

이것은 FindClass()에 대한 JNI 호출과 다르며 이 경우 정규화된 올바른 이름은 java/lang/String입니다.

Android 8.0에서는 여러 개의 클래스 로더가 동일한 DexFile 객체를 사용하여 클래스 정의를 시도할 수 없습니다. 그렇게 하려고 시도하면 Android 런타임에서 InternalError 오류가 발생하고 "Attempt to register dex file <filename> with multiple class loaders"라는 메시지가 나타납니다.

DexFile API는 이제 지원이 중단되었으며, 그 대신 PathClassLoader 또는 BaseDexClassLoader를 비롯한 플랫폼 클래스 로더 중 하나를 사용할 것을 적극 권장합니다.

참고: 파일 시스템에서 동일한 APK 또는 JAR 파일 컨테이너를 참조하는 여러 개의 클래스 로더를 만들 수 있습니다. 그럴 경우 일반적으로 발생하는 메모리 오버헤드가 많지 않습니다. 컨테이너의 DEX 파일이 압축되는 대신 저장된 경우, 플랫폼은 파일을 직접 추출하기보다 이 파일에 mmap 연산을 수행할 수 있습니다. 그러나 플랫폼이 DEX 파일을 컨테이너로부터 추출해야 하는 경우에는 이런 식으로 DEX 파일을 참조하면 상당한 메모리가 소모됩니다.

Android에서 모든 클래스 로더는 병렬 실행이 가능한 것으로 간주됩니다. 여러 스레드가 동일한 클래스 로더를 가지고 동일한 클래스를 로드하려고 경쟁할 경우, 연산을 완료하는 최초 스레드가 승자가 되며 그 결과가 다른 스레드에도 사용됩니다. 이 동작은 클래스 로더가 동일한 클래스를 반환했는지, 다른 클래스를 반환했는지 아니면 예외를 발생했는지 여부에 상관없이 수행됩니다. 플랫폼은 이러한 예외를 자동으로 무시합니다.

주의: Android 8.0 이전 버전의 플랫폼에서 이러한 가정을 위반할 경우 동일한 클래스를 여러 번 정의하고, 클래스 혼동으로 인한 힙 손상이 발생하고, 원치 않는 다른 부작용이 발생할 수 있습니다.