2017년 10월 9일 월요일

안드로이드 8.0 오레오 동작 변경사항

안드로이드 8.0 오레오 동작 변경사항

모든 타겟버전에 대한 동작변화

백그라운드 실행 제한

배터리수명을 향상시키기 위해서, 시스템은 활성 Component 없이 캐시상태로 존재하는 앱들의 wakelock을 해제한다.
그리고, 포그라운드에서 동작하지 않는 앱들의 몇몇 동작을 제한한다.
  • 백그라운드로 동작하는 앱에서는 백그라운드 서비스를 자유롭게 접근하지 못하도록 제한이 있다.
  • 매니페스트에 등록된 대부분의 암시적 브로드캐스트를 수신할 수 없다.
    기본적으로 이 제한들은 타겟을 8.0버전으로 하는 앱들만 적용된다.
또한, 안드로이드 8.0에서는 다음의 메서드 동작에 대해 변화가 있다.
  • 백그라운드 서비스 생성이 허용되지 않는 상황에서, 8.0으로 타겟팅된 앱이 startService() 메서드를 호출하면 IllegalStateException 예외를 던진다.
  • 8.0부터 새로 생긴 Context.startForegroundService()를 사용해서 포그라운드 서비스를 시작할 수 있다. 앱이 백그라운드상태일 때에도 이 메서드를 호출할 수 있지만, 서비스가 생성되고 5초 이내에 생성된 서비스의 startForeground()를 반드시 호출해야 한다.
더 자세한 내용을 보려면 이 링크를 참고한다.

백그라운드에서 위치갱신 제한

배터리수명을 향상시키기 위해서, 8.0 디바이스에서 동작하는 앱들은 백그라운드 상태일 때 위치정보를 자주 갱신하지 않도록 변경되었다. 이 변경사항은 구글플레이 서비스를 포함하여 위치정보를 이용하는 모든 앱에게 영향이 있다.
다음의 API들이 직접적으로 영향을 받는다.
  • Fused Location Provider (FLP)
  • Geofencing
  • GNSS Measurements
  • Location Manager
  • Wi-Fi Manager
앱이 생각했던대로 동작하는지 확인하려면, 다음의 사항을 확인한다.
  • 앱의 로직을 검토하고 최신 API를 사용하고 있는지 점검한다.
  • 각 사용사례에 맞게 앱이 예상대로 동작하는지 점검한다.
  • 사용자의 현재 위치에 따른 사용케이스를 다루려면 Fused Location Provider(FLP)나 Geofencing을 사용한다.
더 자세한 내용을 보려면 이 링크를 참고한다.

앱 단축키

앱 Shortcut에 대해 8.0에서 다음의 변경사항이 있다.
  • com.android.launcher.action.INSTALL_SHORTCUT 브로드캐스트는 이제 비공개이고 암시적으로 변경되어, 더이상 영향을 끼치지 않는다. 대신 ShortcutManager 클래스의 requestPinShortcut() 메서드를 이용하여 앱 숏컷을 생성할 수 있다.
  • ACTION_CREATE_SHORTCUT 인텐트를 이용하면 ShortcutManager 클래스를 통해 관리할 수 있는 앱 숏컷을 생성할 수 있다. 또한, 이 인텐트를 이용하면 ShortcutManager 클래스와 상호작용하지 않는 레거시 숏컷도 생성할 수 있다.
  • requestPinShortcut() 메서드로 만들어진 숏컷이나, ACTION_CREATE_SHORTCUT 인텐트를 처리하는 엑티비티에서 만들어진 숏컷은 완전히 동작하는 숏컷이다. 따라서, ShortcutManager의 메서드를 이용해 숏컷을 업데이트 할 수 있다.
  • 레거시 숏컷은 이전 OS버전의 기능을 그대로 유지하고 있지만, 앱에서는 새로운 숏컷 API로 수동으로 변경해야 한다.
더 자세한 내용을 보려면 이 링크를 참고한다.

언어와 국제화

안드로이드 7.0에서 기본 Category Locale을 지정할 수 있는 개념을 도입했지만, 몇몇 API가 기본 DISPLAY Category Locale를 대신 사용해야 하는 경우에, 계속해서 인수없는 제네릭 Locale.getDefault() 메서드를 사용할 수 있다. 안드로이드 8.0에서는 다음의 메서드에서 Locale.getDefault() 메서드 대신 Locale.getDefault(Category.DISPLAY)를 사용한다.
  • Currency.getDisplayName()
  • Currency.getSymbol()
  • Locale.getDisplayScript()
Locale 인수에 대해 지정된 displayScript 값을 사용할 수 없을 때, Locale.getDisplayScript(Locale) 메서드는 Locale.getDefault() 로 대체된다.
추가적인 언어 & 국제화에 대한 변경사항은 다음과 같다.
  • Currency.getDisplayName(null) 호출은 문서에 설명된 대로 NullPointerException을 던진다.
  • 타임존 이름파싱이 변경되었다. 이전에는 샘플링한 시스템 Clock 값을 사용하여, 날짜/시간 파싱에 사용되는 시간대 이름을 안드로이드 기기가 부팅할 때 캐시했다. 그래서, 부팅 시간에 시스템 Clock이 잘못되었거나, 극히 드문 경우지만 다른 시간대로 설정되어 있는 경우, 파싱에 부정적인 영향을 미칠 수도 있었다.
    이제는, 일반적으로 날짜/시간 파싱로직에 파싱시점의 시스템 Clock과 ICU를 사용한다. 이러한 변화를 통해, 날짜/시간 파싱결과를 더 정확하게 얻을 수 있지만, SimpleDateFormat등의 클래스는 기존 OS버전에서의 결과와 달라질 수 있다.
  • 안드로이드 8.0에서는 ICU의 버전을 버전 58로 업데이트합니다.

Alert Window

만약 앱에서 SYSTEM_ALERT_WINDOW 권한을 사용하고, 다음의 윈도우 타입으로 다른 앱 / 시스템윈도우 위에 Alert을 띄우려고 하는 경우
  • TYPE_PHONE
  • TYPE_PRIORITY_PHONE
  • TYPE_SYSTEM_ALERT
  • TYPE_SYSTEM_OVERLAY
  • TYPE_SYSTEM_ERROR
이럴 때는, 이 Alert 윈도우들은 TYPE_APPLICATION_OVERLAY 윈도우 타입을 사용하는 윈도우 아래에 나타난다. 만약 타겟이 8.0인 앱이라면, TYPE_APPLICATION_OVERLAY 타입으로 Alert 윈도우를 화면에 띄운다.
더 자세한 내용을 보려면 이 링크를 참고한다.

입력 및 탐색

크롬OS, 태블릿과 같은 대형의 폼팩터에서 안드로이드 앱이 사용되면서, 안드로이드 앱 내에서 키보드로 네비게이팅을 수행하는 경우가 늘어나고 있다. 안드로이드 8.0에서는 화살표와 탭 기반의 네비게이션 방식으로, 더 안정적이고 예측가능한 네비게이션 입력장치로써 키보드를 이용하도록 변경하였다.
특히, 요소 포커스 동작에 대해 다음과 같이 변경하였다.
  • 뷰 객체에 대해 포그라운드/백그라운드 상관없이 포커스 상태에 대한 색이 지정되어 있지 않다면, 프레임워크는 뷰에 대해 디폴트 포커스 하이라이트 컬러를 지정한다.
    이 포커스 하이라이트는 엑티비티 Theme에 기반한 Ripple 드로어블이다. 이 기본설정을 끄고싶다면, xml파일에서 android:defaultFocusHighlightEnabled 속성을 false로 주거나, 코드에서 해당 뷰의 setDefaultFocusHighlightEnabled(false)를 호출한다.
  • 키보드의 입력이 UI 요소 포커스에 어떻게 영향을 미치는지 테스트하려면, 디바이스의 개발자옵션 설정에서 Drawing -> Show Layout Bounds 항목을 사용한다. 8.0 디바이스에서는 현재 포커스를 가진 요소의 위에ㅐ X아이콘으로 표시될 것이다.
또한, 8.0에서 툴바의 요소들은 자동으로 Keyboard Navigation Cluster가 되므로, 사용자가 더 쉽게 툴바의 항목들에 포커스를 주고받을 수 있다.
더 자세한 내용을 보려면 이 링크를 참고한다.

웹 양식 자동완성

안드로이드 Autofill Framework가 자동완성 기능을 내장으로 제공하므로, 안드로이드 8.0 기기에 설치된 앱에서 WebView 객체와 관련된 다음 메서드가 변경되었다.
  • WebSettings
    • getSaveFormData() 메서드는 이전에 true를 반환했지만, 이제는 false를 반환한다.
    • setSaveFormData()를 호출해도 더 이상 아무런 효과가 없습니다.
  • WebViewDatabase
    • clearFormData()를 호출해도 더 이상 아무런 효과가 없습니다.
    • hasFormData() 메서드는 이전에 양식에 데이터가 포함되어 있을 때 true를 반환했지만, 이제는 false를 반환한다.

접근성

안드로이드 8.0에서는 접근성에 대해 다음의 변화가 있다.
  • 접근성 프레임워크는 모든 더블탭 제스쳐를 ACTION_CLICK로 변환한다. 이 변화는 TalkBack이 다른 접근성 서비스처럼 더 동작하도록 한다.
    만약, 뷰가 터치핸들링을 커스텀하게 한다면, 반드시, TalkBack을 킨 채로도 정상동작하는지 검증해야 한다. 어쩌면 뷰가 Click 핸들러를 등록해야 할 수도 있다. Click 핸들러를 등록했지만 TalkBack 제스쳐가 제대로 인식되지 않는다면, 뷰의 performAccessibilityAction() 메서드를 오버라이드하여 처리한다.
  • 접근성 서비스는 앱의 텍스트뷰 객체 내의 모든 ClickableSpan 인스턴스를 인식한다.
더 자세한 내용을 보려면 이 링크를 참고한다.

네트워킹 & HTTP(S) 연결

안드로이드 8.0은 HTTP(S) 연결에 대해 다음의 변화를 가지고 있다.
  • 이전에는 Body가 없는 OPTIONS 리퀘스트는 Content-Length 헤더가 없었지만, 이제는 Content-Length 헤더값이 0으로 채워진다.
  • HttpURLConnection은 슬래시가 있는 호스트 or 기관 이름 뒤에 슬래시를 추가하여 빈 경로를 포함하는 URL을 정규화한다. 예를 들어, http://example.com을 http://example.com/으로 변환한다
  • ProxySelector.setDefault()를 통해 커스텀 프록시 Selector는 요청된 URL의 주소 (Scheme, host 및 port)만 대상으로 합니다. 결과적으로, 프록시 선택은 이 값들을 기반으로 되어야 한다. 커스텀 프록시 Selector에 전달된 URL은 요청된 URL의 경로, 쿼리 파라미터, Fragment를 포함하지 않는다.
  • URI은 빈 레이블을 포함할 수 없다.
    이전에는 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. 8.0에서는 이 방법을 더 이상 지원하지 않으므로, 잘못된 URI에 대해서는 시스템이 null을 리턴할 것이다.
  • 8.0의 HttpsURLConnection 구현체는 안전하지 않은 TLS/SSL 프로토콜 버전의 Fallback을 수행하지 않는다.
  • HTTP(S) 연결의 터널링 핸들링은 다음과 같이 변경되었습니다.
    • HTTPS의 연결을 걸쳐서 터널링되는 경우, 이전에는 CONNECT 라인에서 포트번호만 발생했었는데, 이제는 시스템은 데이터를 중간 서버로 보낼 때 호스트라인에서 443 포트를 올바르게 배치한다.
    • 프록시 서버로 터널링된 Request에서, 시스템은 user-agent, proxy-authorization 헤더를 더 이상 보내지 않는다.
      터널을 설정할 때, 시스템에서는 더 이상 터널링된 Http(s)URLConnection을 통해 프록시로 proxy-authorization 헤더를 보내지 않는다. 그 대신, 시스템은 proxy-authorization 헤더를 생성하고 그 프록시가 초기 Request에 대한 응답으로 HTTP 407을 보낼 때 이 헤더를 프록시로 보낸다.
      마찬가지로, 시스템은 터널링된 Request에서 터널을 설정하는 프록시 Request으로 user-agent 헤더를 더 이상 복사하지 않는다. 대신에 라이브러리가 그 Request에 대한 user-agent 헤더를 생성한다.
  • 이전에 실행한 connect() 메서드가 실패한 경우 send(java.net.DatagramPacket) 메서드는 SocketException을 발생시킨다.
    • 내부 오류가 있는 경우, DatagramSocket.connect() 는 pendingSocketException을 설정한다. 안드로이드 8.0 이전에는 send()메서드가 성공했더라도, 추후의 recv() 메서드를 호출하면 SocketException을 던졌지만, 이제는 일관성을 위해 send(), recv() 메서드 모두 SocketException을 던진다.
  • InetAddress.isReachable() 은 TCP Echo 프로토콜로 폴백하기 전에 ICMP를 시도합니다.
    • google.com과 같이, port7 (TCP Echo)를 차단하는 몇몇 호스트는 ICMP 프로토콜을 허용하는 경우라면 이제 연결가능한 호스트가 된다.
    • 실제로 연결할 수 없는 호스트일 경우, 이 변화는 메서드 호출반환까지 2배의 시간이 소요됨을 의미한다.

블루투스

Android 8.0에서는 ScanRecord.getBytes() 메서드가 검색하는 데이터의 길이가 다음과 같이 변경되었다.
  • getBytes() 메서드는 수신되는 바이트 수에 관해 어떤 가정도 하지 않는다. 그러므로 반환되는 바이트 수의 어떤 최대 / 최소값에도 앱은 의존하면 안된다. 대신, 결과 배열의 길이를 가지고 계산해야 한다.
  • 블루투스 5 호환기기는 이전의 최대 바이트 수인 60바이트를 초과하는 길이의 데이터를 반환할 수도 있다.
  • 원격 기기가 스캔 Response를 지원하지 않을 경우, 60바이트보다 적은 데이터가 반환될 수도 있다.

원활한 연결

안드로이드 8.0에서는 Wifi를 쉽게 선택하도록 하여 더 나은 UX를 제공하도록 Wifi 설정이 개선되었다.
  • 안정성 & 신뢰성 개선
  • 직관적인 UI
  • 하나로 통합된 Wi-Fi Preferences 메뉴
  • 호환된 기기에서 저장된 고품질의 네트워크가 근처에 있을 때, 자동으로 Wifi 활성화

보안

안드로이드 8.0에서는 다음의 보안관련 변경사항을 포함하고 있다.
  • 플랫폼은 더 이상 SSLv3를 지원하지 않는다.
  • TLS 프로토콜 버전 협상을 잘못 구현한 서버와 HTTPS 연결을 설정할 때, HttpsURLConnection은 더 이상 이전 TLS 프로토콜 버전으로 폴링하고 재연결하려는 시도를 하지 않는다.
  • 안드로이드 8.0는 Secure Computing(SECCOMP) 필터를 모든 앱에 적용한다. 허용되는 syscall의 목록은 바이오닉을 통해 노출되는 항목으로 제한된다. 하위호완성을 위해 여러 다른 syscall이 존재하지만, 사용하지 않는 것이 좋다.
  • 앱의 웹뷰객체는 이제 멀티프로세스 모드에서도 동작한다. 보안 강화를 위해, 앱을 포함한 프로세스와는 별개로 격리된 프로세스에서 웹 콘텐츠가 처리된다.
  • 이름이 -1 / -2로 끝나는 디렉터리 안에 APK들이 존재한다고 더 이상 가정할 수 없다. 앱은 APK가 설치된 디렉토리를 얻어오기 위해 ApplicationInfo.sourceDir 이용해야 하고, 디렉터리 포맷을 직접적으로 의존해서는 안된다.
  • 네이티브 라이브러리의 보안강화 사항에 대해 확인하려면 다음의 링크를 확인한다.
추가적으로, 안드로이드 8.0에서는 출처를 모르는 앱을 인스톨하는 것에 관하여 다음과 같은 변경사항을 포함하고 있다.
  • INSTALL_NON_MARKET_APPS 레거시 세팅의 값은 이제 항상 1이다. 불분명한 출처에서 패키지 인스톨러를 이용해 앱을 인스톨하는지의 여부를 확인하기 위해, PackageManager. canRequestPackageInstalls() 메서드의 반환값을 이용하도록 변경해야 한다.
  • DevicePolicyManager.setSecureSetting() 메서드를 이용해 INSTALL_NON_MARKET_APPS 필드의 값을 바꾸려고 시도하면, 시스템은 UnsupportedOperationException 을 던질 것이다. 알수 없는 출처로부터의 앱 인스톨링을 막으려면, DISALLOW_INSTALL_UNKNOWN_SOURCES 유저 제한을 적용한다.
  • 안드로이드 8.0기기에서 새로 생성되는 프로파일들은 자동으로 DISALLOW_INSTALL_UNKNOWN_SOURCES제한을 활성화한다. 8.0으로 업그레이드 전에 생성되었던 프로파일들은 프로필 소유자가 INSTALL_NON_MARKET_APPS을 1로 설정하여 이 제약을 명시적으로 비활성화할 때까지 DISALLOW_INSTALL_UNKNOWN_SOURCES을 자동으로 활성화한다.
알려지지 않은 앱 설치에 대해 추가 사항들을 확인하려면 이 링크를 참고한다.
앱의 보안가이드에 대한 추가 정보를 확인하려면 이 링크를 참고한다.

개인정보 보호정책

안드로이드 8.0은 개인정보 관련하여 다음의 변경사항이 있다.
  • 플랫폼에서 이제 식별자를 다르게 처리한다.
    • 안드로이드 8.0 OTA 이전에 설치된 앱의 경우, OTA 이후에 앱을 제거했다가 다시 설치하지 않는 한 ANDROID_ID의 값이 그대로 유지됩니다. OTA 후에 언인스톨로 인해 삭제되는 값을 유지하려면, 개발자는 Key/Value 백업을 이용해 이전값과 새로운 값을 연결해주면 된다.
    • 안드로이드 8.0 디바이스에 설치된 앱의 경우, ANDROID_ID값의 범위는 이제 사용자뿐 아니라 앱 서명키별로 지정된다. ANDROID_ID의 값은 앱 서명 키, 사용자, 기기의 각 조합에 대해 유니크하다. 그러므로, 같은 디바이스에서 실행되지만 서명 키가 다른 앱들은 더이상 같은 ANDROID_ID를 확인할 수 없다.
    • 서명 키가 같고, OTA 이전에 설치된 앱이 아니라면, ANDROID_ID값은 패키지를 삭제 / 재설치해도 변하지 않는다.
    • 시스템 업데이트로 인해 패키지 서명 키가 바뀌더라도 ANDROID_ID의 값은 변경되지 않는다.
    • 구글플레이서비스를 지원하는 디바이스에서, 간단하고, 기본 시스템에서 수익을 추구하는 앱이라면 Advertising ID를 이용한다. Advertising ID는 광고를 위한 유니크하고 유저가 재설정할 수 있는 ID이다.
      다른 디바이스 제조사들은 ANDROID_ID를 계속 제공해주어야 한다.
  • net.hostname 시스템 속성을 쿼리하는 문장은 아마 null값이 리턴될 것이다.

포착되지 않는 예외로그

기본 Thread.UncaughtExceptionHandler를 호출하지 않고, 앱에서 Thread.UncaughtExceptionHandler를 설정했다면, 해결되지 못한 예외가 발생했을 때 시스템은 앱을 죽이지 않는다. 안드로이드 8.0 이전버전에서는 이런 상황에서 예외스택 로그를 남기지 않았지만, 8.0부터는 시스템이 예외스택 로그를 남기도록 변경되었다.
커스텀 Thread.UncaughtExceptionHandler를 구현할 때, 항상 기본핸들러를 부르도록 했다면, 위의 변경사항에는 영향을 받지 않는다.

findViewById() 시그니쳐 변경

findViewById() 메서드를 호출하는 모든 인스턴스는 이제 View 대신 <T extends View> T를 리턴하도록 변경되었다. 이 변화는 다음과 같은 영향이 있다.
  • 현재 코드에서 모호한 리턴타입이 발생할 수 있다.
    예시 : findViewById() 의 결과값을 받는 someMethod(View), someMethod(TextView) 2개의 메서드가 존재하는 경우
  • 자바 8에서, 리턴타입에 대해 제약이 없을 때 View로 명시적인 캐스팅이 필요하다.
    예시 : assertNotNull(findViewById(...)).someViewMethod()
  • non-final인 findViewById() 메서드를 상속받은 메서드는 리턴타입을 업데이트할 필요가 있다.

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

안드로이드 이전 버전에서는 개발자가 Contacts Provider 구성 요소를 사용하여 각 연락처에 대한 데이터를 가져올 수 있었다. 이 데이터는 연락처로 연락한 횟수와 마지막으로 연락한 시간을 비롯하여, 해당 연락처와 관련된 각각의 이메일 주소와 전화 번호에 대한 정보를 보여준다. READ_CONTACTS 권한을 요청하는 앱은 이 데이터를 읽을 수 있다.
안드로이드 8.0에서도 앱은 여전히 READ_CONTACTS 권한을 요청해서 이 데이터를 읽을 수 있다. 8.0 이상의 시스템에서는 데이터값을 조회하면 정확한 값 대신 근사값을 반환할 것이다. 안드로이드 시스템은 내부적으로 정확한 값을 관리하므로 자동완성 API에는 영향을 끼치지 않는다.
다음의 쿼리 변수들에는 영향을 끼칠 수 있다.
  • TIMES_CONTACTED
  • TIMES_USED
  • LAST_TIME_CONTACTED
  • LAST_TIME_USED

컬렉션 처리

이전에는 컬렉션이 비어있을 경우에 AbstractCollection.removeAll() / AbstractCollection.retainAll() NullPointerException을 던지지 않았다. 하지만 8.0부터는 항상 NullPointerException을 던진다. 이는 문서에 명시된 동작과 실제 동작이 일치되도록 변경한 것이다.

Android 엔터프라이즈

안드로이드 8.0에서는 DPC(Device Policy Controller)를 비롯하여 엔터프라이즈 앱의 일부 API와 기능에 대한 동작이 변경되었다. 변경 사항은 다음과 같다.
  • 완벽히 관리되는 기기에서 앱이 작업 프로필을 지원하도록 도와주는 새로운 동작.
  • 시스템 업데이트 처리, 앱 확인 및 인증에 대한 변경을 통해 기기와 시스템의 무결성 개선.
  • 프로비저닝, 알림, Recents 화면 및 상시 접속 VPN에 대한 사용자 환경 개선.
엔터프라이즈에 대한 모든 변경사항을 보려면 이 링크를 참고한다.

타켓버전이 8.0인 앱에서만의 동작변화

이 변경사항은 안드로이드 8.0(API 레벨 26)으로 타겟팅한 앱만 영향을 받는다. 앱의 targetSdkVersion을 26이상으로 설정헀다면, 다음의 내용을 확인하여 앱이 적절하게 동작하는지 확인해야 한다.

Alert Windows

SYSTEM_ALERT_WINDOW 권한을 사용하는 앱은 다른 앱 및 시스템 윈도우 위에 Alert 윈도우를 표시하기 위해 더 이상 다음과 같은 윈도우 유형을 사용할 수 없다.
  • TYPE_PHONE
  • TYPE_PRIORITY_PHONE
  • TYPE_SYSTEM_ALERT
  • TYPE_SYSTEM_OVERLAY
  • TYPE_SYSTEM_ERROR
그대신, 앱은 TYPE_APPLICATION_OVERLAY라는 새로운 윈도우 유형을 사용해야 한다. TYPE_APPLICATION_OVERLAY 윈도우 유형을 사용하여 앱에 대한 Alert 윈도우를 표시할 때 새로운 윈도우 유형의 다음 특징을 염두에 두어야 한다.
  • 앱의 Alert 윈도우는 항상 상태 표시줄 및 IME 같은 중요 시스템 윈도우 아래에 나타난다.
  • 시스템은 화면 표시를 개선하기 위해 TYPE_APPLICATION_OVERLAY 윈도우 유형을 사용하는 윈도우를 이동하거나 크기를 조정할 수 있다.
  • 사용자는 노티피케이션을 열어서 TYPE_APPLICATION_OVERLAY 윈도우 타입으로 열리는 Alert 윈도우를 앱이 보여주지 못하도록 설정할 수 있다.

콘텐츠 변경 알림

안드로이드 8.0를 대상으로 하는 앱에서 ContentResolver.notifyChange() 및 registerContentObserver(Uri, boolean, ContentObserver)가 동작하는 방식이 변경됩니다.
이제 이 API들은 모든 URI에 대해 권한을 가지고있는 유효한 ContentProvider를 정의해야 한다. 관련된 권한을 가진 유효한 ContentProvider를 정의하면 악성 앱으로 인해 콘텐츠가 변경되는 것을 막아주며, 개인 데이터가 악성 앱에 유출될 가능성을 차단한다.

뷰 포커스

이제 클릭가능한 뷰 객체는 기본적으로 포커스를 가질 수 있다. 클릭가능하지만 포커스를 가지지 않도록 뷰 객체를 만드려면, 명시적으로 xml 파일에서 android:focusable 속성을 false로 설정하거나 소스코드에서 setFocusable(false) 메서드를 호출한다.

보안

안드로이드 8.0에서 보안에 대해 다음의 변경 사항이 있다.
  • 앱의 네트워크 보안 구성에서 지원되는 일반 텍스트 트래픽을 opt-out하는 경우, 이 앱의 WebView 객체는 HTTP를 통해 웹사이트에 액세스할 수 없다. 대신, WebView 객체는 HTTPS를 사용해야 한다.
  • 출처를 알수 없는 앱 허용 시스템 설정이 삭제되었다. 대신 그 자리에, 출처를 알 수 없는 앱 설치 설정이 생겨서 출처를 모르는 앱들을 관리한다. 더 많은 정보를 확인하려면 해당 링크를 참고한다.

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

인증자가 계정을 소유하고 있거나 사용자가 계정 액세스 권한을 허용하지 않은 경우, 앱이 더 이상 사용자 계정에 액세스할 수 없다. GET_ACCOUNTS 권한도 더이상 유효하지 않다. 계정에 대해 접근권한을 얻으려면, AccountManager.newChooseAccountIntent()을 이용하거나 인증자에 있는 특정한 메서드를 이용해야 한다. 계정 접근권한을 획득한 후에 비로소 AccountManager.getAccounts()를 이용해 계정에 접근할 수 있다.
안드로이드 8.0에서는 LOGIN_ACCOUNTS_CHANGED_ACTION 브로드캐스트가 Deprecate 되었다. 대신, 런타임에 계정에 대한 변경사항을 얻으려면 addOnAccountsUpdatedListener() API를 이용한다.
계정 액세스와 검색 가능 여부에 대해 추가되는 새로운 API와 메서드에 대한 자세한 정보는 이 링크를 참고한다.

개인정보 보호정책

안드로이드 8.0에서는 개인정보에 대해 다음의 변경사항이 있다.
  • net.dns1, net.dns2, net.dns3, net.dns4 시스템 속성을 더이상 사용할 수 없다.
  • ACCESS_NETWORK_STATE 권한을 가진 앱이 DNS서버와 같은 네트워크 정보를 얻으려면 NetworkRequest혹은 NetworkCallback 객체를 등록한다. (이 클래스들은 안드로이드 5.0에 추가되었다.)
  • Build.SERIAL은 deprecate 되었다. 하드웨어 시리얼 넘버를 알아야 할 앱들은 READ_PHONE_STATE권한을 추가한 후에 Build.getSerial() 메서드를 이용한다.
  • LauncherApps API는 더이상 작업 프로파일의 앱이 기본 프로파일의 정보를 가져올 수 있도록 허용하지 않는다. 작업 프로파일 내의 사용자라면, LauncherApps API는 마치 같은 프로필 그룹 내의 다른 프로필에는 아무런 앱도 설치되어있지 않은 것처럼 동작한다. 이전과 마찬가지로, 관련없는 프로필에 접근하려고 하면 SecurityException 을 던진다.

권한

안드로이드 8.0 이전에는 앱이 런타임에 권한을 요청하고 그 권한이 허용되었다면, 시스템은 같은 권한 그룹에 속하고 매니페스트에 등록된 나머지 권한들도 잘못 허용했었다.
안드로이드 8.0을 타겟으로 하는 앱들은 이 동작이 수정되었다. 앱에는 명시적으로 요청한 권한만 허용된다. 하지만 사용자가 앱에 권한을 허용하고 나면 해당 권한 그룹에서 권한에 대한 이후의 모든 요청이 자동으로 허용된다.
예를들어, 매니페스트에 READ_EXTERNAL_STORAGE에 WRITE_EXTERNAL_STORAGE가 모두 등록되어 있다고 가정하자. 앱이 API 레벨 24 이하로 타겟팅되어 있다면, 앱이 READ_EXTERNAL_STORAGE 권한을 요청하고 유저가 이를 허용했을 때 시스템은 WRITE_EXTERNAL_STORAGE도 동시에 허용한다. 앱이 API 레벨 26으로 타겟팅되어 있다면, 시스템은 READ_EXTERNAL_STORAGE 권한만 허용한다. 하지만, 앱이 추후에 WRITE_EXTERNAL_STORAGE 권한을 요청하면 시스템은 사용자에게 추가적으로 묻지 않고 즉시 권한을 허용한다.

미디어

  • 프레임워크는 스스로 자동 오디오 도킹을 수행할 수 있다. 이 경우, 다른 애플리케이션이 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK의 포커스를 요청하면, 포커스를 가지고 있는 앱의 볼륨은 감소하지만, onAudioFocusChange()콜백도 받지 않고, 오디오 포커스도 잃지 않을 것이다. 도킹 대신에 일시정지가 피룡한 앱들은 새로운 API를 상속받아 사용할 수 있다.
  • 유저가 전화를 받았을 때, 전화통화 중에는 동작중이던 미디어 스트림은 음소거된다.
  • 모든 오디오 관련 API는 오디오 재생의 사용케이스를 설명하기 위해 오디오 스트림타입 대신 AudioAttributes을 사용해야 한다. 볼륨조절을 할때만 오디오 스트림타입을 계속 사용한다. 스트림 형식을 다른 용도로 사용하면(예 : 지원 중단된 AudioTrack 생성자의 streamType 인자), 여전히 동작은 하지만 시스템은 이를 에러로 기록할 것이다.
  • AudioTrack을 사용할 때, 앱이 충분히 큰 오디오 버퍼를 요청한다면, 프레임워크는 딥 버퍼 출력을 사용하려고 시도합니다.(가능할 경우)
  • 안드로이드 8.0에서는 미디어 버튼 이벤트의 처리가 달라졌다.
    1. UI 엑티비티에서의 미디어 버튼 처리는 변경되지 않았다. 포그라운드 엑티비티는 여전히 미디어 버튼 처리에 대한 우선순위가 높다.
    2. 포그라운드 엑티비티가 미디어 버튼 이벤트를 처리하지 않는다면, 시스템은 로컬에서 가장 최근에 오디오를 재생한 앱으로 이벤트를 전달한다. 어떤 앱이 미디어 버튼 이벤트를 수신하는지 확인할 때, 미디어 세션의 활성 상태, 플래그, 재생 상태는 더 이상 고려되지 않는다.
    3. 앱의 미디어세션이 해제된 경우에는, 시스템에 미디어 버튼 이벤트가 있으면 이를 앱의 MediaButtonReceiver로 보낸다.
    4. 다른 모든 경우에, 시스템은 미디어 버튼의 이벤트를 삭제한다.

네이티브 라이브러리

안드로이드 8.0을 타겟으로 하는 앱에서, 네이티브 라이브러리는 쓰기 및 실행이 모두 가능한 로드 세그먼트를 포함한 경우에 더 이상 로드되지 않는다. 이 변경 사항으로 인해, 잘못된 로드 세그먼트를 포함한 네이티브 라이브러리가 있는 앱은 작동을 멈출 수 있다. 이는 보안 강화 조치에 따른 것이다.
더 많은 정보를 얻으려면 이 링크를 참고한다.
링커 변경 사항은 앱이 타겟팅하는 API 레벨과 관련이 있다. 타겟팅된 API 레벨에 링커의 변경사항이 있으면 앱은 라이브러리를 로드할 수 없다. 앱이 링커의 변경사항이 발생하는 API 레벨보다 낮은 버전을 타겟팅하고 있다면, 로그캣은 워닝을 보여줄 것이다.

컬렉션 처리

안드로이드 8.0에서 Collections.sort()는 List.sort() 위에 구현된다. 안드로이드 7.x 버전에서는 반대였다. 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);
  }
}
  • Collections.sort()는 이제 sort()를 호출하는 List 구현에서 구조적 수정으로 간주된다. 예를 들어, Android 8.0 이전의 플랫폼 버전에서 ArrayList를 반복하고 이렇게 반복하는 도중에 sort()를 호출하면, List.sort()를 호출하여 정렬이 수행된 경우 ConcurrentModificationException이 발생했을 것이다.
    이렇게 변경함으로써 플랫폼 동작이 더욱 일관성을 띄게 되었다. 어떤 접근 방식을 취하든, 이제는 ConcurrentModificationException이 발생한다.

클래스 로드 동작

Android 8.0는 클래스 로더가 새 클래스를 로드할 때 런타임 가정을 위반하지 않는지 확인한다. 이 경우 클래스가 Java(forName())로부터 참조되는지, Dalvik 바이트코드로부터 참조되는지 아니면 JNI로부터 참조되는지 확인한다. 플랫폼은 Java에서 loadClass() 메서드로의 직접 호출은 가로채지 않으며, 이러한 호출의 결과를 확인하지도 않는다. 이 동작은 제대로 동작하는 클래스 로더의 기능에는 영향을 미쳐서는 안된다.
플랫폼은 클래스 로더가 반환하는 클래스의 Descriptor가 예상된 Descriptor와 일치하는지를 확인한다. 반환된 Descriptor가 일치하지 않으면, 플랫폼은 NoClassDefFoundError 에러를 던지고, 불일치를 설명하는 상세 메시지를 예외에 저장한다.
플랫폼은 또한 요청된 클래스의 Descriptor가 유효한지 확인한다. 이 검사는 GetFieldID()처럼 이 클래스에 잘못된 Descriptor를 보내어 클래스를 간접적으로 로드하는 JNI 호출을 찾아낸다. 예를 들어, 잘못된 서명때문에 서명이 java/lang/String인 필드를 찾을 수 없는 경우 이 서명은 Ljava/lang/String이 되어야 한다
이것은 java/lang/String이 유효하고 완전한 이름인 FindClass() 에 대한 JNI 호출과 다르다.
안드로이드 8.0에서는 여러 개의 클래스 로더가 동일한 DexFile 객체를 사용하여 클래스 정의를 시도할 수 없다. 그렇게 하려고 시도하면 안드로이드 런타임에서 InternalError 오류가 발생하고 "Attempt to register dex file <filename> with multiple class loaders"라는 메시지가 나타난다.
DexFile API는 Deprecate 되었고, 대신 PathClassLoaderBaseDexClassLoader를 포함한 플랫폼의 클래스로더들 중 하나를 사용하길 권장되고 있다.
  • Note : 파일 시스템에서 동일한 APK 또는 JAR 파일 컨테이너를 참조하는 여러 개의 클래스 로더를 만들 수 있다. 그럴 경우 일반적으로 발생하는 메모리 오버헤드가 많지 않다. 컨테이너의 DEX 파일이 압축되는 대신 저장된 경우, 플랫폼은 파일을 직접 추출하기보다 이 파일에 mmap 연산을 수행할 수 있다. 그러나 플랫폼이 DEX 파일을 컨테이너로부터 추출해야 하는 경우에는 이런 식으로 DEX 파일을 참조하면 상당한 메모리가 소모된다.
안드로이드에서 모든 클래스 로더는 병렬 실행이 가능한 것으로 간주됩니다. 여러 스레드가 동일한 클래스 로더를 가지고 동일한 클래스를 로드하려고 경쟁할 경우, 경우, 연산을 완료하는 최초 스레드가 승자가 되며 그 결과가 다른 스레드에도 사용된다. 이 동작은 클래스 로더가 동일한 클래스를 반환했는지, 다른 클래스를 반환했는지 아니면 예외를 발생했는지 여부에 상관없이 수행된다. 플랫폼은 이러한 예외를 자동으로 무시한다.
  • 주의 : 안드로이드 8.0 이전 버전의 플랫폼에서 이러한 가정을 위반할 경우 동일한 클래스를 여러 번 정의하고, 클래스 혼동으로 인한 힙 손상이 발생하고, 원치 않는 다른 부작용이 발생할 수 있다.

댓글 없음:

댓글 쓰기