Android 7.0 동작 변경사항

Android 7.0에는 새로운 기능과 성능뿐만 아니라 다양한 시스템 및 API 동작 변경사항이 포함되어 있습니다. 이 문서에서는 앱에서 이해하고 고려해야 하는 몇 가지 주요 변경사항을 중점적으로 설명합니다.

이전에 Android용 앱을 게시한 적이 있다면 플랫폼의 이러한 변경사항이 앱에 영향을 미칠 수 있습니다.

배터리 및 메모리

Android 7.0에는 기기의 배터리 수명을 개선하고 RAM 사용량을 줄이기 위한 시스템 동작 변경사항이 포함되어 있습니다. 이러한 변경사항은 앱의 시스템 리소스 액세스뿐만 아니라 앱이 특정 암시적 인텐트를 사용하여 다른 앱과 상호작용하는 방식에도 영향을 미칠 수 있습니다.

잠자기

Android 6.0(API 수준 23)에 도입된 잠자기는 사용자가 기기를 전원에 연결하지 않고 화면을 끈 상태로 움직이지 않을 때 CPU 및 네트워크 활동을 지연시켜 배터리 수명을 개선합니다. Android 7.0에서는 기기가 전원에 연결되지 않고 화면이 꺼져 있지만 반드시 움직이지 않는 것은 아닐 때(예: 사용자의 주머니에 있는 핸드셋이 이동 중일 때) CPU 및 네트워크 제한의 하위 집합을 적용하여 잠자기를 더욱 개선합니다.

도즈 모드가 배터리 수명을 개선하기 위해 시스템 활동 제한의 첫 번째 수준을 적용하는 방법을 보여주는 그림

그림 1. 도즈 모드가 배터리 수명을 개선하기 위해 첫 번째 수준의 시스템 활동 제한을 적용하는 방식을 보여주는 그림

기기가 배터리 전원을 사용하고 화면이 일정 시간 동안 꺼져 있으면 기기가 잠자기 모드로 전환되고 첫 번째 제한사항 하위 집합을 적용합니다. 즉, 앱 네트워크 액세스를 종료하고 작업 및 동기화를 지연합니다. 잠자기에 진입한 후 일정 시간 동안 기기가 정지되어 있으면 시스템은 나머지 잠자기 제한사항을 PowerManager.WakeLock, AlarmManager 알람, GPS, Wi-Fi 검색에 적용합니다. 일부 또는 모든 잠자기 제한이 적용되는지 여부와 관계없이 시스템은 짧은 유지보수 기간 동안 기기를 절전 모드에서 해제합니다. 이 기간 동안 애플리케이션은 네트워크 액세스가 허용되며 지연된 작업/동기화를 실행할 수 있습니다.

기기가 일정 시간 동안 움직이지 않으면 잠자기가 두 번째 수준의 시스템 활동 제한을 적용하는 방식을 보여주는 그림

그림 2. 기기가 일정 시간 동안 움직이지 않으면 잠자기가 두 번째 수준의 시스템 활동 제한을 적용하는 방식을 보여주는 그림

화면을 활성화하거나 기기를 연결하면 잠자기 모드가 종료되고 이러한 처리 제한이 삭제됩니다. 추가 동작은 잠자기 및 앱 대기 모드에 맞게 최적화에서 설명한 대로 Android 6.0(API 수준 23)에 도입된 이전 버전의 잠자기에 앱을 적용하는 데 있어 권장사항에 영향을 미치지 않습니다. Firebase 클라우드 메시징(FCM)을 사용하여 메시지를 주고받는 등의 권장사항을 계속 따르고 추가적인 잠자기 동작을 수용하도록 업데이트를 계획해야 합니다.

프로젝트 Svelte: 백그라운드 최적화

Android 7.0은 메모리 사용과 전력 소비를 모두 최적화하기 위해 세 가지 암시적 브로드캐스트를 삭제합니다. 암시적 브로드캐스트는 백그라운드에서 수신 대기하도록 등록된 앱을 자주 시작하므로 이 변경사항이 필요합니다. 이러한 브로드캐스트를 삭제하면 기기 성능과 사용자 환경이 크게 개선될 수 있습니다.

휴대기기는 Wi-Fi와 모바일 데이터 간에 이동할 때와 같이 연결 상태가 자주 변경됩니다. 현재 앱은 매니페스트에 암시적 CONNECTIVITY_ACTION 브로드캐스트의 수신기를 등록하여 연결 변경사항을 모니터링할 수 있습니다. 많은 앱이 이 브로드캐스트를 수신하도록 등록하므로 단일 네트워크 스위치로 인해 모든 앱이 한 번에 절전 모드에서 해제되고 브로드캐스트를 처리할 수 있습니다.

마찬가지로 이전 버전의 Android에서는 앱이 카메라와 같은 다른 앱에서 암시적 ACTION_NEW_PICTURE 및 ACTION_NEW_VIDEO 브로드캐스트를 수신하도록 등록할 수 있었습니다. 사용자가 카메라 앱으로 사진을 찍으면 이러한 앱이 절전 모드에서 해제되어 브로드캐스트를 처리합니다.

이러한 문제를 완화하기 위해 Android 7.0에서는 다음과 같은 최적화를 적용합니다.

  • Android 7.0(API 수준 24) 이상을 타겟팅하는 앱은 매니페스트에서 broadcast receiver를 선언하면 CONNECTIVITY_ACTION 브로드캐스트를 수신하지 않습니다. Context.registerReceiver()에 BroadcastReceiver를 등록한 상태에서 그 컨텍스트가 여전히 유효하면 앱은 계속 CONNECTIVITY_ACTION 브로드캐스트를 수신합니다.
  • 시스템은 더 이상 ACTION_NEW_PICTURE 또는 ACTION_NEW_VIDEO 브로드캐스트를 전송하지 않습니다. 이러한 최적화는 Android 7.0을 타겟팅하는 앱뿐 아니라 모든 앱에 영향을 미칩니다.

앱에서 이러한 인텐트가 사용되는 경우 최대한 빨리 인텐트 종속 항목을 삭제하여 Android 7.0 기기를 올바르게 타겟팅해야 합니다. Android 프레임워크에서는 이러한 암시적 브로드캐스트의 필요성을 줄이는 여러 솔루션을 제공합니다. 예를 들어 JobScheduler API는 무제한 네트워크에 연결과 같은 지정된 조건이 충족될 경우 네트워크 작업을 예약할 수 있는 강력한 메커니즘을 제공합니다. JobScheduler를 사용하여 콘텐츠 제공자의 변경사항에 반응할 수도 있습니다.

Android 7.0(API 수준 24)의 백그라운드 최적화 및 앱 조정 방법에 관한 자세한 내용은 백그라운드 최적화를 참고하세요.

권한 변경사항

Android 7.0에는 앱에 영향을 미칠 수 있는 권한 변경사항이 포함되어 있습니다.

파일 시스템 권한 변경

비공개 파일의 보안을 개선하기 위해 Android 7.0 이상을 타겟팅하는 앱의 비공개 디렉터리에는 액세스가 제한됩니다(0700). 이 설정은 비공개 파일의 메타데이터(예: 크기 또는 존재 여부)가 유출되는 것을 방지합니다. 이 권한 변경에는 다음과 같은 여러 부작용이 있습니다.

  • 비공개 파일의 파일 권한은 더 이상 소유자가 완화할 수 없으며 MODE_WORLD_READABLE 또는 MODE_WORLD_WRITEABLE를 사용하여 완화하려고 하면 SecurityException가 트리거됩니다.

    참고: 아직 이 제한사항이 완전히 적용되지는 않습니다. 앱은 네이티브 API 또는 File API를 사용하여 비공개 디렉터리에 대한 권한을 수정할 수 있습니다. 하지만 비공개 디렉터리에 대한 권한을 완화하지 않는 것이 좋습니다.

  • 패키지 도메인 외부에서 file:// URI를 전달하면 수신자에게 액세스할 수 없는 경로가 남을 수 있습니다. 따라서 file:// URI를 전달하려고 하면 FileUriExposedException가 트리거됩니다. 비공개 파일의 콘텐츠를 공유하는 권장 방법은 FileProvider를 사용하는 것입니다.
  • DownloadManager는 더 이상 비공개로 저장된 파일을 파일 이름으로 공유할 수 없습니다. 기존 애플리케이션은 COLUMN_LOCAL_FILENAME에 액세스할 때 액세스할 수 없는 경로로 끝날 수 있습니다. Android 7.0 이상을 타겟팅하는 앱은 COLUMN_LOCAL_FILENAME에 액세스하려고 할 때 SecurityException를 트리거합니다. DownloadManager.Request.setDestinationInExternalFilesDir() 또는 DownloadManager.Request.setDestinationInExternalPublicDir()를 사용하여 다운로드 위치를 공개 위치로 설정하는 기존 애플리케이션은 여전히 COLUMN_LOCAL_FILENAME의 경로에 액세스할 수 있지만 이 방법은 사용하지 않는 것이 좋습니다. DownloadManager에서 노출하는 파일에 액세스하는 기본 방법은 ContentResolver.openFileDescriptor()를 사용하는 것입니다.

앱 간 파일 공유

Android 7.0을 타겟팅하는 앱의 경우 Android 프레임워크는 앱 외부에서 file:// URI를 노출하는 것을 금지하는 StrictMode API 정책을 시행합니다. 파일 URI가 포함된 인텐트가 앱을 벗어나면 앱이 FileUriExposedException 예외와 함께 실패합니다.

애플리케이션 간에 파일을 공유하려면 content:// URI를 전송하고 URI에 임시 액세스 권한을 부여해야 합니다. 이 권한을 부여하는 가장 쉬운 방법은 FileProvider 클래스를 사용하는 것입니다. 권한 및 파일 공유에 관한 자세한 내용은 파일 공유를 참고하세요.

접근성 향상

Android 7.0에는 저시력 또는 시각 장애가 있는 사용자를 위해 플랫폼의 사용성을 개선하기 위한 변경사항이 포함되어 있습니다. 이러한 변경사항은 일반적으로 앱에서 코드를 변경할 필요가 없지만, 사용자 환경에 미칠 수 있는 영향을 평가하려면 이러한 기능을 검토하고 앱으로 테스트해야 합니다.

화면 확대/축소

Android 7.0에서는 사용자가 화면의 모든 요소를 확대하거나 축소하는 디스플레이 크기를 설정할 수 있으므로 저시력 사용자의 기기 접근성이 향상됩니다. 사용자는 일반적인 중간 크기 휴대전화인 Nexus 4의 너비인 최소 화면 너비 sw320dp를 초과하여 화면을 확대할 수 없습니다.

Android 7.0 시스템 이미지를 실행하는 기기의 확대되지 않은 디스플레이 크기를 보여주는 화면
Android 7.0 시스템 이미지를 실행하는 기기의 디스플레이 크기를 늘린 효과를 보여주는 화면

그림 3. 오른쪽 화면은 Android 7.0 시스템 이미지를 실행하는 기기의 디스플레이 크기를 늘린 효과를 보여줍니다.

기기 밀도가 변경되면 시스템은 다음과 같은 방식으로 실행 중인 앱에 알립니다.

  • 앱이 대상 API 수준 23 이하를 타겟팅하는 경우 시스템은 모든 백그라운드 프로세스를 자동으로 종료합니다. 즉, 사용자가 이러한 앱에서 전환하여 설정 화면을 열고 디스플레이 크기 설정을 변경하면 시스템은 메모리 부족 상황에서와 동일한 방식으로 앱을 종료합니다. 앱에 포그라운드 프로세스가 있는 경우 시스템은 런타임 변경사항 처리에 설명된 대로 기기의 방향이 변경된 것처럼 구성 변경을 해당 프로세스에 알립니다.
  • 앱이 Android 7.0을 타겟팅하는 경우 런타임 변경사항 처리에 설명된 대로 모든 프로세스(포그라운드 및 백그라운드)에 구성 변경사항이 알림으로 표시됩니다.

앱이 Android 권장사항을 따르는 경우 대부분의 앱은 이 기능을 지원하기 위해 변경할 필요가 없습니다. 확인해야 할 사항:

  • 화면 너비가 sw320dp인 기기에서 앱을 테스트하고 적절하게 작동하는지 확인합니다.
  • 기기 설정이 변경되면 캐시된 비트맵이나 네트워크에서 로드된 리소스와 같은 밀도 종속 캐시 정보를 업데이트합니다. 앱이 일시중지 상태에서 재개될 때 구성 변경사항을 확인합니다.

    참고: 구성 종속 데이터를 캐시하는 경우 해당 데이터에 적합한 화면 크기나 픽셀 밀도와 같은 관련 메타데이터를 포함하는 것이 좋습니다. 이 메타데이터를 저장하면 구성 변경 후 캐시된 데이터를 새로고침해야 하는지 결정할 수 있습니다.

  • px 단위로 크기를 지정하지 마세요. 화면 밀도에 따라 크기가 조정되지 않습니다. 대신 밀도 독립형 픽셀(dp) 단위로 크기를 지정합니다.

설정 마법사의 보기 설정

Android 7.0에는 시작 화면에 시각 설정이 포함되어 있으며, 사용자는 새 기기에서 확대 동작, 글꼴 크기, 디스플레이 크기, TalkBack과 같은 접근성 설정을 지정할 수 있습니다. 이 변경사항으로 인해 다양한 화면 설정과 관련된 버그의 가시성이 향상됩니다. 이 기능의 영향을 평가하려면 이러한 설정을 사용 설정한 상태에서 앱을 테스트해야 합니다. 설정 > 접근성에서 설정을 찾을 수 있습니다.

플랫폼 라이브러리에 연결되는 NDK 앱

Android 7.0부터 시스템은 앱이 NDK가 아닌 라이브러리에 동적으로 연결되지 않도록 합니다. 이로 인해 앱이 비정상 종료될 수 있습니다. 이러한 동작 변경은 플랫폼 업데이트와 다양한 기기에서 일관된 앱 환경을 만들기 위한 것입니다. 코드가 비공개 라이브러리에 연결되지 않을 수 있지만 앱의 서드 파티 정적 라이브러리가 연결될 수 있습니다. 따라서 모든 개발자는 Android 7.0을 실행하는 기기에서 앱이 비정상 종료되지 않는지 확인해야 합니다. 앱에서 네이티브 코드를 사용하는 경우 공개 NDK API만 사용해야 합니다.

앱이 비공개 플랫폼 API에 액세스하려고 시도하는 방법에는 세 가지가 있습니다.

  • 앱이 비공개 플랫폼 라이브러리에 직접 액세스합니다. 이러한 라이브러리의 자체 사본을 포함하도록 앱을 업데이트하거나 공개 NDK API를 사용해야 합니다.
  • 앱이 비공개 플랫폼 라이브러리에 액세스하는 서드 파티 라이브러리를 사용합니다. 앱이 비공개 라이브러리에 직접 액세스하지 않는다고 확신하더라도 이 시나리오에 대해 앱을 테스트해야 합니다.
  • 앱이 APK에 포함되지 않은 라이브러리를 참조합니다. 예를 들어 자체 OpenSSL 사본을 사용하려고 했지만 앱의 APK와 번들로 묶는 것을 잊은 경우에 발생할 수 있습니다. 앱은 libcrypto.so를 포함하는 Android 플랫폼 버전에서 정상적으로 실행될 수 있습니다. 그러나 이 라이브러리가 포함되지 않은 이후 버전의 Android(예: Android 6.0 이상)에서는 앱이 다운될 수 있습니다. 이 문제를 해결하려면 모든 비 NDK 라이브러리를 APK와 함께 번들로 묶어야 합니다.

앱은 NDK에 포함되지 않은 네이티브 라이브러리를 사용해서는 안 됩니다. 이러한 라이브러리는 Android 버전 간에 변경되거나 삭제될 수 있기 때문입니다. OpenSSL에서 BoringSSL로의 전환이 이러한 변경의 예입니다. 또한 NDK에 포함되지 않은 플랫폼 라이브러리에는 호환성 요구사항이 없으므로 기기마다 호환성 수준이 다를 수 있습니다.

이 제한사항이 현재 출시된 앱에 미칠 수 있는 영향을 줄이기 위해, API 수준 23 이하를 타겟팅하는 앱의 경우 Android 7.0(API 수준 24)에서 libandroid_runtime.so, libcutils.so, libcrypto.so, libssl.so와 같이 많이 사용되는 라이브러리 집합에 일시적으로 액세스할 수 있습니다. 앱이 이러한 라이브러리 중 하나를 로드하면 logcat에서 경고를 생성하고 대상 기기에 토스트 메시지가 표시되어 알려줍니다. 이러한 경고가 표시되면 앱을 업데이트하여 해당 라이브러리의 자체 사본을 포함하거나 공개 NDK API만 사용해야 합니다. Android 플랫폼의 향후 출시 버전에서는 비공개 라이브러리의 사용을 완전히 제한하여 앱이 비정상 종료될 수 있습니다.

모든 앱은 공개되지 않거나 일시적으로 액세스할 수 없는 API를 호출할 때 런타임 오류를 생성합니다. 그 결과 System.loadLibrary와 dlopen(3)가 모두 NULL를 반환하고 앱이 비정상 종료될 수 있습니다. 앱 코드를 검토하여 비공개 플랫폼 API 사용을 삭제하고 Android 7.0(API 수준 24)을 실행하는 기기 또는 에뮬레이터를 사용하여 앱을 철저히 테스트해야 합니다. 앱에서 비공개 라이브러리를 사용하는지 확실하지 않은 경우 Logcat을 확인하여 런타임 오류를 식별할 수 있습니다.

다음 표에서는 비공개 네이티브 라이브러리 사용 및 대상 API 수준(android:targetSdkVersion)에 따라 앱에서 예상되는 동작을 설명합니다.

라이브러리 대상 API 수준 동적 링커를 사용한 런타임 액세스 Android 7.0(API 수준 24) 동작 향후 Android 플랫폼 동작
NDK 공개 모두 액세스 가능 예상대로 작동한다 예상대로 작동한다
비공개(일시적으로 액세스할 수 있는 비공개 라이브러리) 23 이하 일시적으로 액세스 가능 예상대로 작동하지만 logcat 경고가 표시됩니다. 런타임 오류
비공개(일시적으로 액세스 가능한 비공개 라이브러리) 24 이상 제한됨 런타임 오류 런타임 오류
비공개(기타) 모두 제한됨 런타임 오류 런타임 오류

앱에서 비공개 라이브러리를 사용하는지 확인하기

비공개 라이브러리를 로드하는 문제를 식별하는 데 도움이 되도록 logcat에서 경고 또는 런타임 오류를 생성할 수 있습니다. 예를 들어 앱이 API 수준 23 이하를 타겟팅하고 Android 7.0을 실행하는 기기에서 비공개 라이브러리에 액세스하려고 하면 다음과 유사한 경고가 표시될 수 있습니다.

03-21 17:07:51.502 31234 31234 W linker  : library "libandroid_runtime.so"
("/system/lib/libandroid_runtime.so") needed or dlopened by
"/data/app/com.popular-app.android-2/lib/arm/libapplib.so" is not accessible
for the namespace "classloader-namespace" - the access is temporarily granted
as a workaround for http://b/26394120

이러한 logcat 경고는 비공개 플랫폼 API에 액세스하려고 하는 라이브러리를 알려주지만 앱이 비정상 종료되지는 않습니다. 그러나 앱이 대상 API 수준 24 이상을 타겟팅하는 경우 Logcat은 다음 런타임 오류를 생성하고 앱이 비정상 종료될 수 있습니다.

java.lang.UnsatisfiedLinkError: dlopen failed: library "libcutils.so"
("/system/lib/libcutils.so") needed or dlopened by
"/system/lib/libnativeloader.so" is not accessible for the namespace
"classloader-namespace"
  at java.lang.Runtime.loadLibrary0(Runtime.java:977)
  at java.lang.System.loadLibrary(System.java:1602)

앱에서 비공개 플랫폼 API에 동적으로 연결되는 서드 파티 라이브러리를 사용하는 경우에도 이러한 logcat 출력이 표시될 수 있습니다. Android 7.0 NDK의 readelf 도구를 사용하면 다음 명령어를 실행하여 지정된 .so 파일의 모든 동적 연결 공유 라이브러리 목록을 생성할 수 있습니다.

aarch64-linux-android-readelf -dW libMyLibrary.so

앱 업데이트

다음은 이러한 유형의 오류를 수정하고 향후 플랫폼 업데이트에서 앱이 비정상 종료되지 않도록 하기 위해 취할 수 있는 몇 가지 단계입니다.

  • 앱에서 비공개 플랫폼 라이브러리를 사용하는 경우 이러한 라이브러리의 자체 사본을 포함하도록 업데이트하거나 공개 NDK API를 사용해야 합니다.
  • 앱에서 비공개 기호에 액세스하는 서드 파티 라이브러리를 사용하는 경우 라이브러리 작성자에게 연락하여 라이브러리를 업데이트하세요.
  • 모든 비 NDK 라이브러리를 APK와 함께 패키징해야 합니다.
  • libandroid_runtime.so의 getJavaVM 및 getJNIEnv 대신 표준 JNI 함수를 사용합니다.
    AndroidRuntime::getJavaVM -> GetJavaVM from <jni.h>
    AndroidRuntime::getJNIEnv -> JavaVM::GetEnv or
    JavaVM::AttachCurrentThread from <jni.h>.
    
  • libcutils.so의 비공개 property_get 기호 대신 __system_property_get를 사용합니다. 이렇게 하려면 다음 include와 함께 __system_property_get를 사용합니다.
    #include <sys/system_properties.h>

    참고: 시스템 속성의 사용 가능 여부와 콘텐츠는 CTS를 통해 테스트되지 않습니다. 더 나은 해결 방법은 이러한 속성을 아예 사용하지 않는 것입니다.

  • libcrypto.so의 SSL_ctrl 기호의 로컬 버전을 사용합니다. 예를 들어 .so 파일에서 libcyrpto.a를 정적으로 연결하거나 BoringSSL/OpenSSL에서 libcrypto.so의 동적으로 연결된 버전을 포함하고 APK에 패키징해야 합니다.

Android for Work

Android 7.0에는 인증서 설치, 비밀번호 재설정, 보조 사용자 관리, 기기 식별자 액세스에 대한 변경사항을 비롯하여 Android for Work를 타겟팅하는 앱에 대한 변경사항이 포함되어 있습니다. Android for Work 환경용 앱을 빌드하는 경우 이러한 변경사항을 검토하고 그에 따라 앱을 수정해야 합니다.

  • DPC에서 설정하기 전에 위임된 인증서 설치 프로그램을 설치해야 합니다. Android 7.0(API 수준 24)을 타겟팅하는 프로필 및 기기 소유자 앱의 경우 기기 정책 컨트롤러(DPC)가 DevicePolicyManager.setCertInstallerPackage()를 호출하기 전에 위임된 인증서 설치 프로그램을 설치해야 합니다. 설치 프로그램이 아직 설치되지 않은 경우 시스템에서 IllegalArgumentException을 발생시킵니다.
  • 기기 관리자의 비밀번호 재설정 제한사항이 이제 프로필 소유자에게 적용됩니다. 기기 관리자는 더 이상 DevicePolicyManager.resetPassword()를 사용하여 비밀번호를 삭제하거나 이미 설정된 비밀번호를 변경할 수 없습니다. 기기 관리자는 여전히 비밀번호를 설정할 수 있지만 기기에 비밀번호, PIN 또는 패턴이 없는 경우에만 가능합니다.
  • 기기 및 프로필 소유자는 제한이 설정되어 있어도 계정을 관리할 수 있습니다. 기기 소유자와 프로필 소유자는 DISALLOW_MODIFY_ACCOUNTS 사용자 제한이 적용되는 경우에도 계정 관리 API를 호출할 수 있습니다.
  • 기기 소유자는 보조 사용자를 관리할 수 있습니다. 기기가 기기 소유자 모드에서 실행 중인 경우 DISALLOW_ADD_USER 제한이 자동으로 설정됩니다. 이렇게 하면 사용자가 관리되지 않는 보조 사용자를 만들 수 없습니다. 또한 CreateUser() 및 createAndInitializeUser() 메서드는 지원 중단되었으며 새로운 DevicePolicyManager.createAndManageUser() 메서드로 대체되었습니다.
  • 기기 소유자는 기기 식별자에 액세스할 수 있습니다. 기기 소유자는 DevicePolicyManager.getWifiMacAddress()를 사용하여 기기의 Wi-Fi MAC 주소에 액세스할 수 있습니다. 기기에서 Wi-Fi가 사용 설정된 적이 없는 경우 이 메서드는 null 값을 반환합니다.
  • 직장 모드 설정은 업무용 앱에 대한 액세스를 제어합니다. 직장 모드가 사용 중지되면 시스템 런처는 업무용 앱을 비활성화하여 사용할 수 없음을 나타냅니다. 직장 모드를 다시 사용 설정하면 정상적인 동작이 복원됩니다.
  • 설정 UI에서 클라이언트 인증서 체인과 해당 비공개 키가 포함된 PKCS #12 파일을 설치할 때 체인의 CA 인증서가 더 이상 신뢰할 수 있는 사용자 인증 정보 저장소에 설치되지 않습니다. 이는 나중에 앱이 클라이언트 인증서 체인을 가져오려고 할 때 KeyChain.getCertificateChain()의 결과에 영향을 미치지 않습니다. 필요한 경우 CA 인증서는 .crt 또는 .cer 파일 확장자 아래에 DER로 인코딩된 형식으로 설정 UI를 사용하여 신뢰할 수 있는 사용자 인증 정보 저장소에 별도로 설치해야 합니다.
  • Android 7.0부터 지문 등록 및 저장소는 사용자별로 관리됩니다. 프로필 소유자의 기기 정책 클라이언트(DPC)가 Android 7.0(API 수준 24)을 실행하는 기기에서 API 수준 23 이하를 타겟팅하는 경우 사용자는 여전히 기기에 지문을 설정할 수 있지만 직장 애플리케이션은 기기 지문에 액세스할 수 없습니다. DPC가 대상 API 수준 24 이상을 타겟팅하는 경우 사용자는 설정 > 보안 > 직장 프로필 보안으로 이동하여 직장 프로필에 지문을 설정할 수 있습니다.
  • 새 암호화 상태 ENCRYPTION_STATUS_ACTIVE_PER_USER가 DevicePolicyManager.getStorageEncryptionStatus()에 의해 반환되어 암호화가 활성 상태이고 암호화 키가 사용자와 연결되어 있음을 나타냅니다. 새 상태는 DPC가 대상 API 수준 24 이상을 타겟팅하는 경우에만 반환됩니다. 이전 API 수준을 타겟팅하는 앱의 경우 암호화 키가 사용자 또는 프로필에만 적용되더라도 ENCRYPTION_STATUS_ACTIVE가 반환됩니다.
  • Android 7.0에서는 일반적으로 기기 전체에 영향을 미치는 여러 메서드가 기기에 별도의 직장 확인 질문이 있는 직장 프로필이 설치된 경우 다르게 작동합니다. 이러한 메서드는 전체 기기에 영향을 미치지 않고 직장 프로필에만 적용됩니다. 이러한 메서드의 전체 목록은 DevicePolicyManager.getParentProfileInstance() 문서에 있습니다. 예를 들어 DevicePolicyManager.lockNow()은 전체 기기를 잠그는 대신 직장 프로필만 잠급니다. 이러한 각 메서드의 경우 DevicePolicyManager의 상위 인스턴스에서 메서드를 호출하여 이전 동작을 가져올 수 있습니다. DevicePolicyManager.getParentProfileInstance()를 호출하여 이 상위 요소를 가져올 수 있습니다. 예를 들어 상위 인스턴스의 lockNow() 메서드를 호출하면 전체 기기가 잠깁니다.

주석 보관

Android 7.0에서는 주석의 공개 상태가 무시되는 버그가 수정되었습니다. 이 문제로 인해 런타임이 액세스할 수 없어야 하는 주석에 액세스할 수 있었습니다. 이러한 주석에는 다음이 포함되었습니다.

  • VISIBILITY_BUILD: 빌드 시에만 표시됩니다.
  • VISIBILITY_SYSTEM: 런타임에 표시되며 기본 시스템에만 적용됩니다.

앱이 이 동작을 사용한 경우 런타임에 사용할 수 있어야 하는 주석에 보관 정책을 추가하세요. @Retention(RetentionPolicy.RUNTIME)를 사용하여 이 작업을 수행합니다.

TLS/SSL 기본 구성 변경사항

Android 7.0에서는 앱이 HTTPS 및 기타 TLS/SSL 트래픽에 사용하는 기본 TLS/SSL 구성에 다음과 같은 변경사항이 적용됩니다.

  • RC4 암호화 스위트가 사용 중지되었습니다.
  • CHACHA20-POLY1305 암호화 스위트가 사용 설정되었습니다.

RC4가 기본적으로 사용 중지되면 서버가 최신 암호화 스위트를 협상하지 않을 때 HTTPS 또는 TLS/SSL 연결이 중단될 수 있습니다. 권장되는 해결 방법은 서버 구성을 개선하여 더 강력하고 최신인 암호화 스위트와 프로토콜을 사용 설정하는 것입니다. 이상적으로는 TLSv1.2 및 AES-GCM을 사용 설정하고 Forward Secrecy 암호화 스위트(ECDHE)를 사용 설정하고 선호해야 합니다.

또는 서버와 통신하는 데 맞춤 SSLSocketFactory를 사용하도록 앱을 수정할 수 있습니다. 팩토리는 기본 암호화 스위트 외에도 서버에 필요한 일부 암호화 스위트가 사용 설정된 SSLSocket 인스턴스를 만들도록 설계해야 합니다.

참고: 이 변경사항은 WebView에 적용되지 않습니다.

Android 7.0을 타겟팅하는 앱

이러한 동작 변경사항은 Android 7.0(API 수준 24) 이상을 타겟팅하는 앱에만 적용됩니다. Android 7.0을 기준으로 컴파일하거나 targetSdkVersion을 Android 7.0 이상으로 설정하는 앱은 이러한 동작을 올바르게 지원하도록 앱을 수정해야 합니다(앱에 적용되는 경우).

직렬화 변경사항

Android 7.0(API 수준 24)에서는 사양과 일치하지 않는 기본 serialVersionUID 계산의 버그가 수정되었습니다.

Serializable를 구현하고 명시적 serialVersionUID 필드를 지정하지 않는 클래스는 기본 serialVersionUID가 변경될 수 있으며, 이로 인해 이전 버전에서 직렬화되었거나 이전 버전을 타겟팅하는 앱에서 직렬화된 클래스의 인스턴스를 역직렬화하려고 할 때 예외가 발생할 수 있습니다. 오류 메시지는 다음과 같습니다.

local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 4567

이러한 문제를 해결하려면 영향을 받는 클래스에 오류 메시지의 stream classdesc serialVersionUID 값(이 경우 1234)이 있는 serialVersionUID 필드를 추가해야 합니다. 이 변경사항은 직렬화 코드 작성에 관한 모든 권장사항을 준수하며 모든 Android 버전에서 작동합니다.

수정된 특정 버그는 정적 이니셜라이저 메서드(예: <clinit>)의 존재와 관련이 있었습니다. 사양에 따르면 클래스에 정적 이니셜라이저 메서드가 있는지 여부가 해당 클래스에 대해 계산된 기본 serialVersionUID에 영향을 미칩니다. 버그 수정 전에는 클래스에 정적 이니셜라이저가 없는 경우 계산에서 정적 이니셜라이저의 슈퍼클래스도 확인했습니다.

명확히 하자면 이 변경사항은 대상 API 수준 23 이하를 타겟팅하는 앱, serialVersionUID 필드가 있는 클래스, 정적 이니셜라이저 메서드가 있는 클래스에는 영향을 미치지 않습니다.

기타 중요한 사항

  • 앱이 Android 7.0에서 실행되지만 더 낮은 API 수준을 타겟팅하고 사용자가 디스플레이 크기를 변경하면 앱 프로세스가 종료됩니다. 앱은 이 시나리오를 원활하게 처리할 수 있어야 합니다. 그렇지 않으면 사용자가 최근 항목에서 복원할 때 비정상 종료됩니다.

    이러한 동작이 발생하지 않도록 앱을 테스트해야 합니다. DDMS를 사용하여 앱을 수동으로 종료할 때 동일한 비정상 종료를 일으키면 됩니다.

    Android 7.0(API 수준 24) 이상을 타겟팅하는 앱은 밀도 변경 시 자동으로 종료되지 않지만 구성 변경에 제대로 응답하지 않을 수 있습니다.

  • Android 7.0의 앱은 구성 변경을 원활하게 처리할 수 있어야 하며 이후 시작 시 비정상 종료되지 않아야 합니다. 글꼴 크기(설정 > 디스플레이 > 글꼴 크기)를 변경한 다음 최근 항목에서 앱을 복원하여 앱 동작을 확인할 수 있습니다.
  • 이전 버전의 Android에 있는 버그로 인해 시스템은 기본 스레드의 TCP 소켓에 쓰는 것을 엄격 모드 위반으로 플래그 지정하지 않았습니다. Android 7.0에서는 이 버그가 수정되었습니다. 이 동작을 보이는 앱은 이제 android.os.NetworkOnMainThreadException을 발생시킵니다. 일반적으로 기본 스레드에서 네트워크 작업을 실행하는 것은 좋지 않습니다. 이러한 작업은 일반적으로 지연 시간이 길어 ANR과 버벅거림을 유발하기 때문입니다.
  • 이제 Debug.startMethodTracing() 메서드 계열은 기본적으로 SD 카드의 최상위 수준이 아닌 공유 저장소의 패키지별 디렉터리에 출력을 저장합니다. 즉, 앱은 더 이상 이러한 API를 사용하기 위해 WRITE_EXTERNAL_STORAGE 권한을 요청할 필요가 없습니다.
  • 이제 많은 플랫폼 API가 Binder 트랜잭션에서 전송되는 대규모 페이로드를 확인하기 시작했으며, 시스템은 이제 TransactionTooLargeExceptions를 자동으로 로깅하거나 억제하는 대신 RuntimeExceptions로 다시 발생시킵니다. 한 가지 일반적인 예는 Activity.onSaveInstanceState()에 너무 많은 데이터를 저장하는 것입니다. 이로 인해 앱이 Android 7.0을 타겟팅할 때 ActivityThread.StopInfo에서 RuntimeException이 발생합니다.
  • 앱이 Runnable 작업을 View에 게시하고 View가 창에 연결되지 않은 경우 시스템은 Runnable 작업을 View와 함께 큐에 추가합니다. Runnable 작업은 View가 창에 연결될 때까지 실행되지 않습니다. 이 동작은 다음 버그를 수정합니다.
    • 앱이 의도한 창의 UI 스레드가 아닌 스레드에서 View에 게시한 경우 결과적으로 Runnable가 잘못된 스레드에서 실행될 수 있습니다.
    • Runnable 태스크가 루퍼 스레드가 아닌 스레드에서 게시된 경우 앱은 Runnable 태스크를 노출할 수 있습니다.
  • DELETE_PACKAGES 권한이 있는 Android 7.0의 앱이 패키지를 삭제하려고 하지만 다른 앱이 해당 패키지를 설치한 경우 시스템은 사용자 확인을 요구합니다. 이 시나리오에서 앱은 PackageInstaller.uninstall()를 호출할 때 STATUS_PENDING_USER_ACTION를 반환 상태로 예상해야 합니다.
  • Crypto라는 JCA 제공자는 유일한 알고리즘인 SHA1PRNG가 암호화 측면에서 취약하므로 지원 중단되었습니다. 이 제공업체를 더 이상 사용할 수 없으므로 앱은 더 이상 SHA1PRNG를 사용하여 키를 (안전하지 않게) 파생할 수 없습니다. 자세한 내용은 블로그 게시물 Android N에서 보안 '암호화' 제공업체 지원 중단을 참고하세요.