Android 8.0 동작 변경사항

Android 8.0 (API 수준 26)에는 새로운 기능 및 특징과 더불어 다양한 시스템 및 API 동작 변경사항이 포함되어 있습니다. 이 문서에서는 개발자가 이해하고 앱에서 고려해야 하는 몇 가지 주요 변경사항을 중점적으로 설명합니다.

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

모든 앱의 변경사항

이러한 동작 변경사항은 타겟팅하는 API 수준과 관계없이 Android 8.0 (API 수준 26) 플랫폼에서 실행되는 모든 앱에 적용됩니다. 모든 개발자는 이러한 변경사항을 검토하고 변경사항을 적절히 지원하도록 앱을 수정해야 합니다(앱에 적용되는 경우).

백그라운드 실행 제한

배터리 수명 개선을 위해 Android 8.0 (API 수준 26)에서 도입된 변경사항 중 하나로, 앱이 활성 구성요소 없이 캐시된 상태로 전환되면 시스템은 앱에서 유지하고 있는 wakelock을 해제합니다.

또한 기기 성능을 개선하기 위해 시스템은 포그라운드에서 실행되지 않는 앱의 특정 동작을 제한합니다. 다음은 구체적인 설명입니다.

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

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

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)에서는 기본 카테고리 언어를 지정할 수 있다는 개념을 도입했지만, 일부 API에서는 대신 기본 DISPLAY 카테고리 언어를 대신 사용해야 하는 경우에도 인수 없이 일반 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를 잘못 사용하는 개발자에게는 'URI example.com의 호스트 이름에 빈 라벨이 있습니다. 잘못된 형식이며 향후 Android 버전에서 허용되지 않습니다.'라는 오류 메시지가 표시될 수 있습니다. 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()은 TCP 에코 프로토콜로 대체하기 전에 ICMP를 시도합니다.
    • 이제 google.com과 같이 포트 7 (TCP Echo)을 차단하는 일부 호스트는 ICMP 에코 프로토콜을 허용하면 연결할 수 있습니다.
    • 실제로 연결할 수 없는 호스트의 경우 이 변경으로 인해 호출이 반환되기 전까지 두 배의 시간이 소요됩니다.

블루투스

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)은 모든 앱에 보안 컴퓨팅 (SECCOMP) 필터를 적용합니다. 허용되는 syscall은 Bionic을 통해 노출되는 syscall로 제한됩니다. 이전 버전과의 호환성을 위해 여러 다른 syscall이 제공되지만 사용하지 않는 것이 좋습니다.
  • 이제 앱의 WebView 객체가 멀티 프로세스 모드에서 실행됩니다. 보안 강화를 위해 웹 콘텐츠는 포함하는 앱의 프로세스와는 별개로 격리된 프로세스에서 처리됩니다.
  • 이름이 -1 또는 -2로 끝나는 디렉터리에 APK가 있다고 더 이상 가정할 수 없습니다. 앱은 sourceDir를 사용하여 디렉터리를 가져와야 하고 디렉터리 형식에 직접 의존해서는 안 됩니다.
  • 네이티브 라이브러리 사용과 관련된 보안 향상에 관한 자세한 내용은 네이티브 라이브러리를 참조하세요.

또한 Android 8.0 (API 수준 26)에는 알 수 없는 소스의 알 수 없는 앱 설치와 관련된 다음과 같은 변경사항이 도입되었습니다.

알 수 없는 앱 설치에 관한 추가 세부정보는 알 수 없는 앱 설치 권한 가이드를 참조하세요.

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

개인 정보 보호

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

  • 이제 플랫폼이 식별자를 다르게 처리합니다.
    • OTA 이전에 Android 8.0 (API 수준 26)(API 수준 26) 버전에 설치된 앱의 경우 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)가 모두 있는 경우입니다.
  • 자바 8 소스 언어를 사용하는 경우 반환 유형에 제약이 없는 경우 View로 명시적으로 변환해야 합니다 (예: assertNotNull(findViewById(...)).someViewMethod())).
  • 최종이 아닌 findViewById() 메서드 (예: Activity.findViewById())를 재정의하면 반환 유형을 업데이트해야 합니다.

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

이전 버전의 Android에서는 개발자가 연락처 제공자 구성요소를 사용하여 각 연락처의 사용 데이터를 가져올 수 있습니다. 이 사용 데이터에는 연락처에 연결된 횟수 및 마지막으로 연락한 시간을 포함하여 각 이메일 주소 및 연락처와 연결된 전화번호의 정보가 노출됩니다. 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])}

이러한 오인으로 인한 문제를 방지하려면 OPR 이외의 문자열을 Opera 브라우저의 패턴 일치로 사용하세요.

보안

다음 변경사항은 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는 더 이상 사용할 수 없어 플랫폼의 개인 정보 보호가 개선됩니다.
  • ACCESS_NETWORK_STATE 권한이 있는 앱은 DNS 서버와 같은 네트워킹 정보를 가져오기 위해 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()를 호출하는 목록 구현에서 구조적 수정으로 간주됩니다. 예를 들어 Android 8.0 (API 수준 26) 이전의 플랫폼 버전에서는 ArrayList를 반복하고 반복 과정 중에 sort()를 호출하면 List.sort()를 호출하여 정렬이 완료되면 ConcurrentModificationException이 발생했습니다. Collections.sort()에서 예외가 발생하지 않았습니다.

    이 변경사항으로 인해 플랫폼 동작이 더 일관됩니다. 두 접근 방식 모두 이제 ConcurrentModificationException이 됩니다.

클래스 로드 동작

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

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

플랫폼은 또한 요청된 클래스의 설명자가 올바른지 확인합니다. 이 검사에서는 GetFieldID()와 같은 클래스를 간접적으로 로드하는 JNI 호출을 포착하여 잘못된 설명어를 이러한 클래스에 전달합니다. 예를 들어 서명이 유효하지 않아 서명이 java/lang/String인 필드를 찾을 수 없습니다. 이 서명은 Ljava/lang/String;여야 합니다.

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

Android 8.0 (API 수준 26)은 여러 클래스 로더가 동일한 DexFile 객체를 사용하여 클래스 정의를 시도하는 것을 지원하지 않습니다. 이렇게 하려고 하면 Android 런타임에서 '여러 클래스 로더로 dex 파일 <filename> 등록 시도'라는 메시지와 함께 InternalError 오류가 발생합니다.

DexFile API는 이제 지원 중단되었으며 PathClassLoader 또는 BaseDexClassLoader를 포함한 플랫폼 클래스 로더 중 하나를 대신 사용하는 것이 좋습니다.

참고: 파일 시스템에서 동일한 APK 또는 JAR 파일 컨테이너를 참조하는 여러 클래스 로더를 만들 수 있습니다. 이렇게 하면 일반적으로 많은 메모리 오버헤드가 발생하지 않습니다. 컨테이너의 DEX 파일이 압축되는 대신 저장되면 플랫폼은 파일을 직접 추출하는 대신 mmap 작업을 실행할 수 있습니다. 그러나 플랫폼이 DEX 파일을 컨테이너에서 추출해야 하는 경우 이 방식으로 DEX 파일을 참조하면 많은 메모리가 소비될 수 있습니다.

Android에서 모든 클래스 로더는 병렬 실행이 가능한 것으로 간주됩니다. 여러 스레드가 동일한 클래스 로더로 동일한 클래스를 로드하려고 경합하면 작업을 완료하는 첫 번째 스레드가 이기고 그 결과는 다른 스레드에 사용됩니다. 이 동작은 클래스 로더가 동일한 클래스를 반환했는지, 다른 클래스를 반환했는지, 예외를 발생시켰는지와 관계없이 발생합니다. 플랫폼은 이러한 예외를 자동으로 무시합니다.

주의: Android 8.0 (API 수준 26) 이하 버전의 플랫폼에서 이러한 가정을 위반하면 동일한 클래스를 여러 번 정의하고, 클래스 혼동으로 인한 힙 손상이 발생하거나, 기타 바람직하지 않은 결과가 발생할 수 있습니다.