동작 변경사항: Android 14 이상을 타겟팅하는 앱

이전 버전과 마찬가지로 Android 14에는 앱에 영향을 미칠 수 있는 동작 변경사항이 포함되어 있습니다. 다음 동작 변경사항은 Android 14 (API 수준 34) 이상을 타겟팅하는 앱에만 적용됩니다. 앱이 Android 14 이상을 타겟팅한다면 이러한 동작을 올바르게 지원하도록 앱을 수정해야 합니다(적용되는 경우).

앱의 targetSdkVersion과 관계없이 Android 14에서 실행되는 모든 앱에 영향을 미치는 동작 변경사항 목록도 검토해야 합니다.

핵심 기능

포그라운드 서비스 유형은 필수 항목임

If your app targets Android 14 (API level 34) or higher, it must specify at least one foreground service type for each foreground service within your app. You should choose a foreground service type that represents your app's use case. The system expects foreground services that have a particular type to satisfy a particular use case.

If a use case in your app isn't associated with any of these types, it's strongly recommended that you migrate your logic to use WorkManager or user-initiated data transfer jobs.

BluetoothAdapter에서 BLUETOOTH_CONNECT 권한 시행

Android 14 enforces the BLUETOOTH_CONNECT permission when calling the BluetoothAdapter getProfileConnectionState() method for apps targeting Android 14 (API level 34) or higher.

This method already required the BLUETOOTH_CONNECT permission, but it was not enforced. Make sure your app declares BLUETOOTH_CONNECT in your app's AndroidManifest.xml file as shown in the following snippet and check that a user has granted the permission before calling getProfileConnectionState.

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

OpenJDK 17 업데이트

Android 14에서는 앱 및 플랫폼 개발자를 위한 라이브러리 업데이트와 Java 17 언어 지원을 비롯하여 최신 OpenJDK LTS 출시의 기능과 일치하도록 Android의 핵심 라이브러리를 새로고침하는 작업을 계속하고 있습니다.

다음과 같은 변경사항은 앱 호환성에 영향을 미칠 수 있습니다.

  • 정규 표현식 변경사항: OpenJDK의 시맨틱을 더 엄격하게 준수하도록 이제 잘못된 그룹 참조가 허용되지 않습니다. java.util.regex.Matcher 클래스에 의해 IllegalArgumentException이 발생하는 새로운 사례가 있을 수 있으므로 정규 표현식을 사용하는 영역에서 앱을 테스트해야 합니다. 테스트 중에 이 변경사항을 사용 설정하거나 사용 중지하려면 호환성 프레임워크 도구를 사용하여 DISALLOW_INVALID_GROUP_REFERENCE 플래그를 전환합니다.
  • UUID 처리: 이제 java.util.UUID.fromString() 메서드가 입력 인수를 확인할 때 더 엄격한 검사를 실행하므로 역직렬화 중에 IllegalArgumentException이 발생할 수 있습니다. 테스트 중에 이 변경사항을 사용 설정하거나 사용 중지하려면 호환성 프레임워크 도구를 사용하여 ENABLE_STRICT_VALIDATION 플래그를 전환합니다.
  • ProGuard 문제: 경우에 따라 java.lang.ClassValue 클래스를 추가하면 ProGuard를 사용하여 앱을 축소, 난독화, 최적화하려고 할 때 문제가 발생합니다. 이 문제는 Class.forName("java.lang.ClassValue")가 클래스를 반환하는지 여부에 따라 런타임 동작을 변경하는 Kotlin 라이브러리에서 발생합니다. 앱이 사용 가능한 java.lang.ClassValue 클래스가 없는 이전 버전의 런타임을 대상으로 개발된 경우 이러한 최적화로 인해 java.lang.ClassValue에서 파생된 클래스에서 computeValue 메서드가 삭제될 수 있습니다.

JobScheduler가 콜백 및 네트워크 동작을 강화합니다.

Since its introduction, JobScheduler expects your app to return from onStartJob or onStopJob within a few seconds. Prior to Android 14, if a job runs too long, the job is stopped and fails silently. If your app targets Android 14 (API level 34) or higher and exceeds the granted time on the main thread, the app triggers an ANR with the error message "No response to onStartJob" or "No response to onStopJob".

This ANR may be a result of 2 scenarios: 1. There is work blocking the main thread, preventing the callbacks onStartJob or onStopJob from executing and completing within the expected time limit. 2. The developer is running blocking work within the JobScheduler callback onStartJob or onStopJob, preventing the callback from completing within the expected time limit.

To address #1, you will need to further debug what is blocking the main thread when the ANR occurs, you can do this using ApplicationExitInfo#getTraceInputStream() to get the tombstone trace when the ANR occurs. If you're able to manually reproduce the ANR, you can record a system trace and inspect the trace using either Android Studio or Perfetto to better understand what is running on the main thread when the ANR occurs. Note that this can happen when using JobScheduler API directly or using the androidx library WorkManager.

To address #2, consider migrating to WorkManager, which provides support for wrapping any processing in onStartJob or onStopJob in an asynchronous thread.

JobScheduler also introduces a requirement to declare the ACCESS_NETWORK_STATE permission if using setRequiredNetworkType or setRequiredNetwork constraint. If your app does not declare the ACCESS_NETWORK_STATE permission when scheduling the job and is targeting Android 14 or higher, it will result in a SecurityException.

Tiles launch API

14 이상을 타겟팅하는 앱의 경우 TileService#startActivityAndCollapse(Intent)가 지원 중단되었으며 다음 오류가 발생합니다. 예외가 발생할 수 있습니다. 앱이 카드에서 활동을 실행하는 경우 다음을 사용합니다. 대신 TileService#startActivityAndCollapse(PendingIntent)를 사용하세요.

개인정보 보호

사진 및 동영상에 대한 일부 액세스

Android 14 introduces Selected Photos Access, which allows users to grant apps access to specific images and videos in their library, rather than granting access to all media of a given type.

This change is only enabled if your app targets Android 14 (API level 34) or higher. If you don't use the photo picker yet, we recommend implementing it in your app to provide a consistent experience for selecting images and videos that also enhances user privacy without having to request any storage permissions.

If you maintain your own gallery picker using storage permissions and need to maintain full control over your implementation, adapt your implementation to use the new READ_MEDIA_VISUAL_USER_SELECTED permission. If your app doesn't use the new permission, the system runs your app in a compatibility mode.

사용자 환경

전체 화면 인텐트 보안 알림

With Android 11 (API level 30), it was possible for any app to use Notification.Builder.setFullScreenIntent to send full-screen intents while the phone is locked. You could auto-grant this on app install by declaring USE_FULL_SCREEN_INTENT permission in the AndroidManifest.

Full-screen intent notifications are designed for extremely high-priority notifications demanding the user's immediate attention, such as an incoming phone call or alarm clock settings configured by the user. For apps targeting Android 14 (API level 34) or higher, apps that are allowed to use this permission are limited to those that provide calling and alarms only. The Google Play Store revokes default USE_FULL_SCREEN_INTENT permissions for any apps that don't fit this profile. The deadline for these policy changes is May 31, 2024.

This permission remains enabled for apps installed on the phone before the user updates to Android 14. Users can turn this permission on and off.

You can use the new API NotificationManager.canUseFullScreenIntent to check if your app has the permission; if not, your app can use the new intent ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT to launch the settings page where users can grant the permission.

보안

암시적 인텐트와 대기 중인 인텐트 제한사항

For apps targeting Android 14 (API level 34) or higher, Android restricts apps from sending implicit intents to internal app components in the following ways:

  • Implicit intents are only delivered to exported components. Apps must either use an explicit intent to deliver to unexported components, or mark the component as exported.
  • If an app creates a mutable pending intent with an intent that doesn't specify a component or package, the system throws an exception.

These changes prevent malicious apps from intercepting implicit intents that are intended for use by an app's internal components.

For example, here is an intent filter that could be declared in your app's manifest file:

<activity
    android:name=".AppActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.action.APP_ACTION" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

If your app tried to launch this activity using an implicit intent, an ActivityNotFoundException exception would be thrown:

Kotlin

// Throws an ActivityNotFoundException exception when targeting Android 14.
context.startActivity(Intent("com.example.action.APP_ACTION"))

Java

// Throws an ActivityNotFoundException exception when targeting Android 14.
context.startActivity(new Intent("com.example.action.APP_ACTION"));

To launch the non-exported activity, your app should use an explicit intent instead:

Kotlin

// This makes the intent explicit.
val explicitIntent =
        Intent("com.example.action.APP_ACTION")
explicitIntent.apply {
    package = context.packageName
}
context.startActivity(explicitIntent)

Java

// This makes the intent explicit.
Intent explicitIntent =
        new Intent("com.example.action.APP_ACTION")
explicitIntent.setPackage(context.getPackageName());
context.startActivity(explicitIntent);

런타임 등록 broadcast receiver는 내보내기 동작을 지정해야 함

Apps and services that target Android 14 (API level 34) or higher and use context-registered receivers are required to specify a flag to indicate whether or not the receiver should be exported to all other apps on the device: either RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED, respectively. This requirement helps protect apps from security vulnerabilities by leveraging the features for these receivers introduced in Android 13.

Exception for receivers that receive only system broadcasts

If your app is registering a receiver only for system broadcasts through Context#registerReceiver methods, such as Context#registerReceiver(), then it shouldn't specify a flag when registering the receiver.

더 안전한 동적 코드 로드

If your app targets Android 14 (API level 34) or higher and uses Dynamic Code Loading (DCL), all dynamically-loaded files must be marked as read-only. Otherwise, the system throws an exception. We recommend that apps avoid dynamically loading code whenever possible, as doing so greatly increases the risk that an app can be compromised by code injection or code tampering.

If you must dynamically load code, use the following approach to set the dynamically-loaded file (such as a DEX, JAR, or APK file) as read-only as soon as the file is opened and before any content is written:

Kotlin

val jar = File("DYNAMICALLY_LOADED_FILE.jar")
val os = FileOutputStream(jar)
os.use {
    // Set the file to read-only first to prevent race conditions
    jar.setReadOnly()
    // Then write the actual file content
}
val cl = PathClassLoader(jar, parentClassLoader)

Java

File jar = new File("DYNAMICALLY_LOADED_FILE.jar");
try (FileOutputStream os = new FileOutputStream(jar)) {
    // Set the file to read-only first to prevent race conditions
    jar.setReadOnly();
    // Then write the actual file content
} catch (IOException e) { ... }
PathClassLoader cl = new PathClassLoader(jar, parentClassLoader);

Handle dynamically-loaded files that already exist

To prevent exceptions from being thrown for existing dynamically-loaded files, we recommend deleting and recreating the files before you try to dynamically load them again in your app. As you recreate the files, follow the preceding guidance for marking the files read-only at write time. Alternatively, you can re-label the existing files as read-only, but in this case, we strongly recommend that you verify the integrity of the files first (for example, by checking the file's signature against a trusted value), to help protect your app from malicious actions.

백그라운드에서 활동 시작에 관한 추가 제한사항

Android 14(API 수준 34) 이상을 타겟팅하는 앱의 경우 시스템은 앱이 백그라운드에서 활동을 시작하도록 허용되는 시점을 추가로 제한합니다.

  • 앱이 PendingIntent#send() 또는 유사한 메서드를 사용하여 PendingIntent를 전송할 때 대기 중인 인텐트를 시작하기 위해 자체 백그라운드 활동 실행 권한을 부여할지 이제 앱이 선택해야 합니다. 선택하려면 앱에서 setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)가 포함된 ActivityOptions 번들을 전달해야 합니다.
  • 표시되는 앱이 bindService() 메서드를 사용하여 백그라운드에 있는 다른 앱의 서비스를 바인딩하는 경우 이제 표시되는 앱은 바운드된 서비스에 자체 백그라운드 활동 실행 권한을 부여할지 선택해야 합니다. 선택하려면 앱에 다음 항목이 포함되어야 합니다. BIND_ALLOW_ACTIVITY_STARTS 플래그 bindService() 메서드를 사용하여 지도 가장자리에 패딩을 추가할 수 있습니다.

이러한 변경사항으로 인해 기존 제한사항이 확대되어 악성 앱이 불편을 초래하는 API를 악용하지 못하도록 방지하여 사용자를 보호합니다. 할 수 있습니다.

압축 파일 경로 순회

Android 14 (API 수준 34) 이상을 타겟팅하는 앱의 경우 Android는 다음과 같은 방법으로 ZIP 경로 순회 취약점을 방지합니다. ZIP 파일 항목 이름에 '..'가 포함되거나 '/'로 시작하면 ZipFile(String)ZipInputStream.getNextEntry()에서 ZipException이 발생합니다.

앱은 dalvik.system.ZipPathValidator.clearCallback()를 호출하여 이 유효성 검사를 선택 해제할 수 있습니다.

Android 14 (API 수준 34) 이상을 타겟팅하는 앱의 경우 다음 시나리오 중 하나에서 MediaProjection#createVirtualDisplay에 의해 SecurityException이 발생합니다.

앱은 각 캡처 세션 전에 사용자에게 동의를 요청해야 합니다. 단일 캡처 세션은 MediaProjection#createVirtualDisplay의 단일 호출이며 각 MediaProjection 인스턴스는 한 번만 사용되어야 합니다.

구성 변경 처리

앱에서 MediaProjection#createVirtualDisplay를 호출하여 구성 변경 (예: 화면 방향 또는 화면 크기 변경)을 처리해야 한다면 다음 단계에 따라 기존 MediaProjection 인스턴스의 VirtualDisplay를 업데이트할 수 있습니다.

  1. 새 너비와 높이로 VirtualDisplay#resize를 호출합니다.
  2. VirtualDisplay#setSurface에 새 너비와 높이가 포함된 새 Surface를 제공합니다.

콜백 등록

사용자가 캡처 세션을 계속하는 데 동의하지 않는 경우를 처리하기 위해 앱에서 콜백을 등록해야 합니다. 이렇게 하려면 Callback#onStop를 구현하고 앱에서 관련 리소스 (예: VirtualDisplaySurface)를 해제하도록 합니다.

앱에서 이 콜백을 등록하지 않은 경우 앱에서 호출할 때 MediaProjection#createVirtualDisplay에서 IllegalStateException이 발생합니다.

업데이트된 비 SDK 제한사항

Android 14 includes updated lists of restricted non-SDK interfaces based on collaboration with Android developers and the latest internal testing. Whenever possible, we make sure that public alternatives are available before we restrict non-SDK interfaces.

If your app does not target Android 14, some of these changes might not immediately affect you. However, while you can currently use some non-SDK interfaces (depending on your app's target API level), using any non-SDK method or field always carries a high risk of breaking your app.

If you are unsure if your app uses non-SDK interfaces, you can test your app to find out. If your app relies on non-SDK interfaces, you should begin planning a migration to SDK alternatives. Nevertheless, we understand that some apps have valid use cases for using non-SDK interfaces. If you cannot find an alternative to using a non-SDK interface for a feature in your app, you should request a new public API.

To learn more about the changes in this release of Android, see Updates to non-SDK interface restrictions in Android 14. To learn more about non-SDK interfaces generally, see Restrictions on non-SDK interfaces.