Thay đổi về hành vi: Ứng dụng nhắm đến Android 14 trở lên

Giống như các bản phát hành trước, Android 14 có các thay đổi về hành vi có thể ảnh hưởng đến ứng dụng của bạn. Những thay đổi về hành vi sau đây chỉ áp dụng cho ứng dụng nhắm đến Android 14 (API cấp 34) trở lên. Nếu ứng dụng của bạn nhắm đến Android 14 trở lên, bạn nên điều chỉnh ứng dụng để hỗ trợ những hành vi này cho phù hợp (nếu cần).

Ngoài ra, hãy nhớ tham khảo danh sách các thay đổi về hành vi ảnh hưởng đến tất cả ứng dụng chạy trên Android 14 bất kể targetSdkVersion của ứng dụng.

Chức năng cốt lõi

Bắt buộc phải có loại dịch vụ trên nền trước

Nếu nhắm đến Android 14 (API cấp 34) trở lên, ứng dụng của bạn phải chỉ định ít nhất một loại dịch vụ trên nền trước đối với từng dịch vụ trên nền trước trong ứng dụng. Bạn nên chọn loại dịch vụ trên nền trước tiêu biểu cho trường hợp sử dụng của ứng dụng. Bạn nên cung cấp những dịch vụ trên nền trước có một kiểu cụ thể để đáp ứng một trường hợp sử dụng nhất định.

Nếu một trường hợp sử dụng trong ứng dụng không liên quan đến bất cứ loại nào trong số này, bạn nên di chuyển logic để sử dụng WorkManager hoặc công việc chuyển dữ liệu do người dùng khởi tạo.

Thực thi quyền BLUETOOTH_CONNECT trong BluetoothAdapter

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" />

Nội dung cập nhật OpenJDK 17

Android 14 tiếp tục công cuộc làm mới các thư viện cốt lõi của Android để phù hợp với các tính năng trong bản phát hành LTS OpenJDK mới nhất, bao gồm cả bản cập nhật thư viện và tính năng hỗ trợ ngôn ngữ Java 17 cho các nhà phát triển ứng dụng và nền tảng.

Một vài thay đổi sau đây có thể ảnh hưởng đến khả năng tương thích của ứng dụng:

  • Thay đổi đối với biểu thức chính quy: Giờ đây, các lượt tham chiếu nhóm không hợp lệ không được phép bám sát ngữ nghĩa của OpenJDK. Bạn có thể nhận thấy các trường hợp mới như khi IllegalArgumentException được lớp java.util.regex.Matcher gửi ra, vì vậy, hãy nhớ kiểm thử ứng dụng của mình đối với những phần có sử dụng biểu thức chính quy. Để bật hoặc tắt thay đổi này trong quá trình kiểm thử, hãy bật/tắt cờ DISALLOW_INVALID_GROUP_REFERENCE bằng các công cụ khung tương thích.
  • Xử lý UUID: Phương thức java.util.UUID.fromString() nay thực hiện các bước kiểm tra nghiêm ngặt hơn khi xác thực đối số đầu vào. Vì vậy, bạn có thể thấy ngoại lệ IllegalArgumentException trong quá trình huỷ chuyển đổi tuần tự. Để bật hoặc tắt thay đổi này trong quá trình kiểm thử, hãy bật/tắt cờ ENABLE_STRICT_VALIDATION bằng công cụ khung tương thích.
  • Vấn đề về ProGuard: Trong một số trường hợp, việc thêm lớp java.lang.ClassValue sẽ gây ra sự cố nếu bạn cố gắng rút gọn, làm rối mã nguồn và tối ưu hoá ứng dụng sử dụng ProGuard. Vấn đề bắt nguồn từ việc thư viện Kotlin thay đổi hành vi trong thời gian chạy dựa trên việc liệu Class.forName("java.lang.ClassValue") có trả về một lớp hay không. Nếu bạn phát triển ứng dụng dựa trên một phiên bản thời gian chạy cũ hơn mà không có lớp java.lang.ClassValue, thì các phương thức tối ưu hoá này có thể xoá phương thức computeValue khỏi các lớp bắt nguồn từ java.lang.ClassValue.

JobScheduler củng cố hành vi gọi lại và hành vi mạng

Kể từ khi ra mắt, JobScheduler dự kiến ứng dụng của bạn sẽ trả về từ onStartJob hoặc onStopJob trong vòng vài giây. Trước Android 14, nếu một công việc chạy quá lâu, thì công việc đó sẽ bị dừng và không tự động ngừng hoạt động. Nếu ứng dụng của bạn nhắm đến Android 14 (API cấp 34) trở lên và vượt quá thời gian đã cấp trên luồng chính, ứng dụng sẽ kích hoạt lỗi ANR kèm thông báo lỗi "Không phản hồi onStartJob" hoặc "Không phản hồi onStopJob".

Lỗi ANR này có thể xảy ra do 2 tình huống: 1. Có công việc chặn luồng chính, ngăn chặn các lệnh gọi lại onStartJob hoặc onStopJob thực thi và hoàn thành trong giới hạn thời gian dự kiến. 2. Nhà phát triển đang chạy công việc chặn trong lệnh gọi lại JobScheduler onStartJob hoặc onStopJob, ngăn lệnh gọi lại hoàn tất trong giới hạn thời gian dự kiến.

Để giải quyết vấn đề #1, bạn cần gỡ lỗi thêm về điều gì đang chặn luồng chính khi lỗi ANR xảy ra. Bạn có thể thực hiện việc này bằng cách sử dụng ApplicationExitInfo#getTraceInputStream() để lấy dấu vết bia mộ khi lỗi ANR xảy ra. Nếu bạn có thể tái hiện lỗi ANR theo cách thủ công, bạn có thể ghi lại dấu vết hệ thống và kiểm tra dấu vết đó bằng Android Studio hoặc Perfetto để hiểu rõ hơn về ứng dụng đang chạy trên luồng chính khi ANR xảy ra. Xin lưu ý rằng điều này có thể xảy ra khi bạn sử dụng trực tiếp API JobScheduler hoặc sử dụng thư viện androidx WorkManager.

Để giải quyết vấn đề 2, hãy cân nhắc chuyển sang WorkManager, công cụ này cung cấp hỗ trợ gói mọi quá trình xử lý trong onStartJob hoặc onStopJob trong luồng không đồng bộ.

JobScheduler cũng đưa ra yêu cầu khai báo Quyền ACCESS_NETWORK_STATE nếu sử dụng setRequiredNetworkType hoặc Quy tắc ràng buộc setRequiredNetwork. Nếu ứng dụng của bạn không khai báo Quyền ACCESS_NETWORK_STATE khi lên lịch công việc và đang nhắm mục tiêu Android 14 trở lên sẽ dẫn đến SecurityException.

Tiles launch API

Đối với ứng dụng nhắm đến độ tuổi 14 trở lên, TileService#startActivityAndCollapse(Intent) không được dùng nữa và hiện đang gửi khi được gọi là một ngoại lệ. Nếu ứng dụng của bạn khởi chạy hoạt động từ thẻ thông tin, hãy sử dụng TileService#startActivityAndCollapse(PendingIntent).

Quyền riêng tư

Quyền truy cập một phần vào ảnh và video

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.

Trải nghiệm người dùng

Thông báo bảo mật về ý định toàn màn hình

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.

Bảo mật

Các quy tắc hạn chế đối với ý định ngầm ẩn và ý định đang chờ xử lý

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 đã đăng ký trong thời gian chạy phải chỉ định hành vi xuất

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.

Tải mã động an toàn hơn

Nếu ứng dụng của bạn nhắm đến Android 14 (API cấp 34) trở lên và sử dụng tính năng Tải mã động (DCL), thì tất cả tệp được tải động đều phải được đánh dấu là chỉ có quyền đọc. Nếu không, hệ thống sẽ gửi ra một ngoại lệ. Bất cứ khi nào có thể thì bạn nên tránh tải mã động, vì làm như vậy sẽ làm tăng đáng kể nguy cơ ứng dụng có thể bị xâm phạm do bị chèn mã hoặc can thiệp vào mã.

Nếu phải tải mã động, bạn hãy sử dụng phương pháp sau để thiết lập tệp được tải động (chẳng hạn như tệp DEX, JAR hoặc APK) ở chế độ chỉ có thể đọc ngay khi tệp được mở và trước khi bất cứ nội dung được ghi:

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);

Xử lý các tệp tải động đã tồn tại

Để ngăn ngoại lệ được gửi cho các tệp được tải động hiện có, bạn nên xoá và tạo lại các tệp đó trước khi cố gắng tải lại theo phương thức động trong ứng dụng. Khi bạn tạo lại các tệp, hãy làm theo hướng dẫn ở phần trước để đánh dấu các tệp là chỉ có quyền đọc tại thời điểm ghi. Ngoài ra, bạn có thể gắn nhãn lại các tệp hiện có thành "chỉ có quyền đọc", nhưng trong trường hợp này, bạn nên xác minh tính toàn vẹn của các tệp trước (chẳng hạn như bằng cách kiểm tra chữ ký của tệp dựa trên một giá trị đáng tin cậy), để giúp bảo vệ ứng dụng của bạn khỏi việc bị mã độc can thiệp.

Hạn chế bổ sung khi bắt đầu hoạt động ở chế độ nền

For apps targeting Android 14 (API level 34) or higher, the system further restricts when apps are allowed to start activities from the background:

These changes expand the existing set of restrictions to protect users by preventing malicious apps from abusing APIs to start disruptive activities from the background.

Truyền tải qua đường dẫn Zip

Đối với các ứng dụng nhắm đến Android 14 (API cấp 34) trở lên, Android sẽ ngăn chặn lỗ hổng Truyền tải qua đường dẫn zip theo cách sau: ZipFile(String)ZipInputStream.getNextEntry() gửi ra một ZipException nếu tên mục nhập tệp zip có chứa ".." hoặc bắt đầu bằng "/".

Ứng dụng có thể chọn không sử dụng tính năng xác thực này bằng cách gọi dalvik.system.ZipPathValidator.clearCallback().

Đối với ứng dụng nhắm đến Android 14 (API cấp 34) trở lên, MediaProjection#createVirtualDisplay sẽ gửi một SecurityException trong một trong hai trường hợp sau:

Ứng dụng của bạn phải yêu cầu người dùng đồng ý trước mỗi phiên chụp. Một phiên chụp là một lệnh gọi duy nhất trên MediaProjection#createVirtualDisplay và mỗi thực thể MediaProjection chỉ được sử dụng một lần.

Xử lý các thay đổi về cấu hình

Nếu ứng dụng của bạn cần gọi MediaProjection#createVirtualDisplay để xử lý các thay đổi về cấu hình (chẳng hạn như thay đổi hướng màn hình hoặc kích thước màn hình), bạn có thể làm theo các bước sau để cập nhật VirtualDisplay cho thực thể MediaProjection hiện có:

  1. Gọi VirtualDisplay#resize với chiều rộng và chiều cao mới.
  2. Cung cấp Surface mới với chiều rộng và chiều cao mới cho VirtualDisplay#setSurface.

Đăng ký lệnh gọi lại

Ứng dụng của bạn nên đăng ký lệnh gọi lại để xử lý các trường hợp người dùng không đồng ý tiếp tục phiên chụp. Để làm việc này, hãy triển khai Callback#onStop và yêu cầu ứng dụng phát hành mọi tài nguyên liên quan (chẳng hạn như VirtualDisplaySurface).

Nếu ứng dụng của bạn không đăng ký lệnh gọi lại này, MediaProjection#createVirtualDisplay sẽ gửi một IllegalStateException khi ứng dụng gọi lệnh gọi lại đó.

Các quy tắc hạn chế mới cập nhật đối với yếu tố ngoài SDK

Android 14 cung cấp danh sách mới cập nhật về các giao diện không phải SDK bị hạn chế dựa trên khả năng cộng tác với nhà phát triển Android và kiểm thử nội bộ mới nhất. Bất cứ khi nào có thể, chúng tôi phải đảm bảo việc cung cấp các phương án thay thế công khai trước khi hạn chế giao diện không phải SDK.

Nếu ứng dụng của bạn không nhắm đến Android 14, thì một số thay đổi này có thể sẽ không ảnh hưởng ngay. Tuy nhiên, mặc dù hiện tại bạn có thể sử dụng một số giao diện không phải SDK (tuỳ thuộc vào cấp độ API mục tiêu của ứng dụng), nhưng việc sử dụng phương thức hoặc trường không phải SDK luôn có nguy cơ cao làm hỏng ứng dụng.

Nếu không chắc ứng dụng của mình có sử dụng giao diện không phải SDK hay không, bạn có thể kiểm tra ứng dụng để tìm hiểu. Nếu ứng dụng của bạn dựa vào giao diện không phải SDK, thì bạn nên bắt đầu lập kế hoạch di chuyển sang SDK làm giải pháp thay thế. Tuy nhiên, chúng tôi hiểu rằng vẫn có một số trường hợp sử dụng hợp lệ cho việc ứng dụng sử dụng giao diện không phải SDK. Nếu không tìm được giải pháp thay thế cho việc sử dụng giao diện không phải SDK cho một tính năng trong ứng dụng, thì bạn nên yêu cầu một API công khai mới.

Để tìm hiểu thêm về những thay đổi trong bản phát hành Android này, hãy xem bài viết Thông tin cập nhật đối với những hạn chế về giao diện không phải SDK trong Android 14. Để tìm hiểu tổng quan thêm về giao diện không phải SDK, hãy xem Hạn chế đối với giao diện không phải SDK.