Android 8.0 동작 변경사항

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

이러한 변경사항의 대부분은 타겟팅하는 Android 버전과 관계없이 모든 앱에 영향을 미칩니다. 그러나 몇몇 변경사항은 Android 8.0을 타겟팅하는 앱에만 영향을 미칩니다. 최대한 명확성을 기하기 위해 이 페이지는 모든 앱의 변경사항Android 8.0을 타겟팅하는 앱의 변경사항이라는 두 섹션으로 나뉩니다.

모든 앱의 변경사항

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

백그라운드 실행 제한

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

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

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

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

Android 8.0 (API 수준 26)에는 다음과 같은 특정 메서드에 대한 변경사항도 포함되어 있습니다.

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

자세한 내용은 백그라운드 실행 제한을 참고하세요.

Android 백그라운드 위치 제한

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

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

  • FLP(Fused Location Provider)
  • 지오펜싱
  • GNSS Measurements
  • Location Manager
  • Wi-Fi Manager

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

  • 앱의 로직을 검토하고 최신 위치 API를 사용하고 있는지 확인합니다.
  • 각 사용 사례에 맞게 앱이 예상대로 동작하는지 테스트합니다.
  • 사용자의 현재 위치에 종속되는 사용 사례를 처리하려면 통합 위치 정보 제공자 (FLP) 또는 지오펜싱을 사용하는 것이 좋습니다.

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

앱 바로가기

Android 8.0 (API 수준 26)에서는 앱 단축키에 다음과 같은 변경사항이 있습니다.

  • 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 (API 수준 26)에서 다음 메서드는 이제 Locale.getDefault() 대신 Locale.getDefault(Category.DISPLAY)를 사용합니다.

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

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

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

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

  • Android 8.0 (API 수준 26)에서는 ICU의 버전을 버전 58로 업데이트합니다.

경고 창

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

이런 창은 항상 TYPE_APPLICATION_OVERLAY 창 유형을 사용하는 창 아래에 나타납니다. 앱이 Android 8.0 (API 수준 26)을 타겟팅하는 경우 앱은 TYPE_APPLICATION_OVERLAY 창 유형을 사용하여 경고 창을 표시합니다.

자세한 내용은 Android 8.0을 타겟팅하는 앱의 동작 변경사항 내에서 경고 창을 위한 일반적인 창 유형 섹션을 참고하세요.

입력 및 탐색

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

특히 요소 포커스 동작을 다음과 같이 변경했습니다.

  • View 객체 (포그라운드 또는 백그라운드 드로어블)의 포커스 상태 색상을 정의하지 않았다면 이제 프레임워크에서 View의 기본 포커스 하이라이트 색상을 설정합니다. 이 포커스 하이라이트는 활동의 테마를 기반으로 하는 리플 드로어블입니다.

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

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

또한 Android 8.0의 모든 툴바 요소는 자동으로 키보드 탐색 클러스터가 되므로 사용자가 각 툴바를 전체적으로 더 쉽게 탐색할 수 있습니다.

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

웹 양식 자동완성

Android 자동 완성 프레임워크가 자동 완성 기능을 위한 내장 지원을 제공하므로 Android 8.0 (API 수준 26)을 실행하는 기기에 설치된 앱에 대해 WebView 객체와 관련된 다음 메서드가 변경되었습니다.

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

접근성

Android 8.0 (API 수준 26)에는 접근성 관련 다음과 같은 변경사항이 포함되어 있습니다.

  • 이제 접근성 프레임워크가 모든 더블탭 동작을 ACTION_CLICK 작업으로 변환합니다. 이번 변경을 통해 TalkBack이 다른 접근성 서비스와 더 유사하게 작동합니다.

    앱의 View 객체가 맞춤 터치 처리를 사용하는 경우 TalkBack과 계속 작동하는지 확인해야 합니다. View 객체에서 사용하는 클릭 핸들러를 등록하기만 하면 됩니다. TalkBack에서 이러한 View 객체에서 실행된 동작을 계속 인식하지 못하면 performAccessibilityAction()를 재정의합니다.

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

앱의 접근성을 높이는 방법을 자세히 알아보려면 접근성을 참고하세요.

네트워킹 및 HTTP(S) 연결

Android 8.0 (API 수준 26)에는 네트워킹 및 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 에코 프로토콜로 대체합니다.
    • google.com과 같이 포트 7 (TCP Echo)을 차단하는 호스트는 이제 ICMP Echo 프로토콜을 허용하는 경우 연결 가능한 호스트가 될 수 있습니다.
    • 실제로 연결할 수 없는 호스트의 경우, 이런 변경 사항이 의미하는 바는 호출 반환까지 두 배의 시간이 소요된다는 점입니다.

블루투스

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

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

원활한 연결

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

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

보안

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

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

또한 Android 8.0 (API 수준 26)에서는 알 수 없는 소스에서 알 수 없는 앱을 설치하는 것과 관련하여 다음과 같은 변경사항을 도입합니다.

알 수 없는 앱 설치에 관한 자세한 내용은 알 수 없는 앱 설치 권한 가이드를 참고하세요.

앱의 보안을 강화하는 방법에 관한 추가 가이드라인은 Android 개발자를 위한 보안을 참고하세요.

개인정보 보호

Android 8.0 (API 수준 26)은 플랫폼에 다음과 같은 개인 정보 보호 관련 변경사항을 적용합니다.

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

      다른 기기 제조업체는 ANDROID_ID를 계속 제공해야 합니다.

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

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

앱이 기본 Thread.UncaughtExceptionHandler를 호출하지 않는 Thread.UncaughtExceptionHandler를 설치하는 경우 시스템은 포착되지 않은 예외가 발생해도 앱을 종료하지 않습니다. Android 8.0 (API 수준 26)부터 시스템은 이러한 상황에서 예외 스택 트레이스를 로깅합니다. 이전 버전의 플랫폼에서는 시스템이 예외 스택 트레이스를 로깅하지 않았습니다.

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

findViewById() 서명 변경

findViewById() 메서드의 모든 인스턴스는 이제 View 대신 <T extends View> T를 반환합니다. 이 변경사항은 다음과 같은 영향을 미칩니다.

  • 이제 기존 코드의 반환 유형이 모호해질 수 있습니다. findViewById() 호출의 결과를 사용하는 someMethod(View)someMethod(TextView)가 모두 있는 경우를 예로 들 수 있습니다.
  • Java 8 소스 언어를 사용하는 경우 반환 유형에 제약이 없다면 View로 명시적으로 변환해야 합니다 (예: assertNotNull(findViewById(...)).someViewMethod())).
  • 최종이 아닌 findViewById() 메서드 (예: Activity.findViewById())를 재정의하면 반환 유형을 업데이트해야 합니다.

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

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

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

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

컬렉션 처리

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

Android 엔터프라이즈

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

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

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

Android 8.0을 타겟팅하는 앱

이 동작 변경사항은 Android 8.0 (API 수준 26) 이상을 타겟팅하는 앱에만 적용됩니다. 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 (API 수준 26)은 Android 8.0을 타겟팅하는 앱에서 ContentResolver.notifyChange()registerContentObserver(Uri, boolean, ContentObserver)의 동작 방식을 변경합니다.

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

뷰 포커스

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

브라우저 감지에서 사용자 에이전트 일치

Android 8.0 (API 수준 26) 이상에는 빌드 식별자 문자열 OPR이 포함됩니다. 일부 패턴 일치로 인해 브라우저 감지 로직이 Opera가 아닌 브라우저를 Opera로 잘못 식별할 수 있습니다. 이러한 패턴 일치의 예는 다음과 같습니다.

if(p.match(/OPR/)){k="Opera";c=p.match(/OPR\/(\d+.\d+)/);n=new Ext.Version(c[1])}

이러한 오인으로 인해 발생하는 문제를 방지하려면 Opera 브라우저의 패턴 일치로 OPR 이외의 문자열을 사용합니다.

보안

다음 변경사항은 Android 8.0 (API 수준 26)의 보안에 영향을 미칩니다.

  • 앱의 네트워크 보안 구성에서 일반 텍스트 트래픽 지원을 선택 해제하는 경우 앱의 WebView 객체가 HTTP를 통해 웹사이트에 액세스할 수 없습니다. 각 WebView 객체는 대신 HTTPS를 사용해야 합니다.
  • 알 수 없는 소스 허용 시스템 설정이 삭제되었습니다. 대신 알 수 없는 앱 설치 권한이 알 수 없는 소스의 알 수 없는 앱 설치를 관리합니다. 이 새로운 권한에 관해 자세히 알아보려면 알 수 없는 앱 설치 권한 가이드를 참고하세요.

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

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

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

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

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

개인정보 보호

다음 변경사항은 Android 8.0 (API 수준 26)의 개인 정보 보호에 영향을 미칩니다.

  • 시스템 속성 net.dns1, net.dns2, net.dns3, net.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 (API 수준 26) 이전에는 앱이 런타임에 권한을 요청하고 권한이 부여된 경우 시스템 역시 같은 권한 그룹에 속하고 매니페스트에 등록된 나머지 권한을 앱에 잘못 부여했습니다.

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

예를 들어 앱이 매니페스트에 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE를 모두 나열한다고 가정해 보겠습니다. 앱이 READ_EXTERNAL_STORAGE를 요청하고 사용자가 이를 허용합니다. 앱이 API 수준 25 이하를 타겟팅하는 경우 시스템은 WRITE_EXTERNAL_STORAGE도 동시에 부여합니다. 같은 STORAGE 권한 그룹에 속하고 매니페스트에도 등록되기 때문입니다. 앱이 Android 8.0 (API 수준 26)을 타겟팅하는 경우 시스템은 그 시점에 READ_EXTERNAL_STORAGE만 부여합니다. 그러나 앱이 나중에 WRITE_EXTERNAL_STORAGE를 요청하면 시스템은 사용자에게 메시지를 표시하지 않고 즉시 이 권한을 부여합니다.

미디어

  • 프레임워크는 자체적으로 자동 오디오 더킹을 실행할 수 있습니다. 이 경우 다른 애플리케이션이 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK로 포커스를 요청하면 포커스가 있는 애플리케이션은 볼륨을 낮추지만 일반적으로 onAudioFocusChange() 콜백을 수신하지 않으며 오디오 포커스를 잃지 않습니다. 더킹 대신 일시중지가 필요한 애플리케이션의 경우 새 API를 사용하여 이 동작을 재정의할 수 있습니다.
  • 사용자가 전화를 받을 때, 통화하는 동안 활성 미디어 스트림의 사운드가 음소거됩니다.
  • 모든 오디오 관련 API는 오디오 재생 사용 사례를 설명하기 위해 오디오 스트림 형식이 아닌 AudioAttributes를 사용해야 합니다. 볼륨 컨트롤에 대해서만 오디오 스트림 형식을 계속 사용하세요. 스트림 유형을 다른 용도로 사용해도 작동하긴 하지만(예: 지원 중단된 AudioTrack 생성자의 streamType 인수) 시스템은 이를 오류로 기록합니다.
  • AudioTrack를 사용할 때 애플리케이션이 충분히 큰 오디오 버퍼를 요청하면 프레임워크는 딥 버퍼 출력을 사용하려고 시도합니다(사용 가능한 경우).
  • Android 8.0 (API 수준 26)에서는 미디어 버튼 이벤트의 처리가 다릅니다.
    1. UI 활동에서 미디어 버튼을 처리하는 방식은 변경되지 않았습니다. 포그라운드 활동은 미디어 버튼 이벤트를 처리할 때 여전히 우선순위가 높습니다.
    2. 포그라운드 활동이 미디어 버튼 이벤트를 처리하지 않으면 시스템은 로컬에서 가장 최근에 오디오를 재생한 앱으로 이벤트를 라우팅합니다. 어떤 앱이 미디어 버튼 이벤트를 수신하는지 확인할 때 미디어 세션의 활성 상태, 플래그, 재생 상태는 고려되지 않습니다.
    3. 앱의 미디어 세션이 해제되었다면 시스템은 미디어 버튼 이벤트를 앱의 MediaButtonReceiver(있는 경우)로 전송합니다.
    4. 다른 모든 경우에, 시스템이 미디어 버튼 이벤트를 삭제합니다.

네이티브 라이브러리

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

자세한 내용은 쓰기 가능 및 실행 가능 세그먼트를 참고하세요.

링커 변경 사항은 앱이 대상으로 하는 API 레벨과 연계됩니다. 타겟팅된 API 수준에서 링커 변경 사항이 있으면 앱이 라이브러리를 로드할 수 없습니다. 링커 변경이 발생하는 API 수준보다 낮은 API 수준을 타겟팅하는 경우 logcat에 경고가 표시됩니다.

컬렉션 처리

Android 8.0 (API 수준 26)에서는 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 (API 수준 26) 이전의 플랫폼 버전에서 ArrayList를 반복하고 반복 중간에 sort()를 호출하면 List.sort()를 호출하여 정렬이 완료되면 ConcurrentModificationException이 발생했습니다. Collections.sort()는 예외를 발생시키지 않았습니다.

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

클래스 로드 동작

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

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

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

이것은 java/lang/String이 유효한 정규화된 이름인 FindClass()에 대한 JNI 호출과 다릅니다.

Android 8.0 (API 수준 26)에서는 여러 개의 클래스 로더가 동일한 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 (API 수준 26) 미만의 플랫폼 버전에서 이러한 가정을 위반하면 동일한 클래스가 여러 번 정의되고 클래스 혼동으로 인한 힙 손상 및 기타 원치 않는 결과가 발생할 수 있습니다.