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

如果您的应用以 Android 14(API 级别 34)或更高版本为目标平台,则必须为应用中的每个前台服务至少指定一项前台服务类型。您应选择一个能代表应用用例的前台服务类型。系统需要特定类型的前台服务满足特定用例。

如果应用中的用例与这些类型均不相关,强烈建议您迁移逻辑以使用 WorkManager用户发起的数据传输作业

Thực thi quyền BLUETOOTH_CONNECT trong BluetoothAdapter

Android 14 thực thi quyền BLUETOOTH_CONNECT khi gọi phương thức BluetoothAdapter getProfileConnectionState() cho các ứng dụng nhắm đến Android 14 (API cấp 34) trở lên.

Phương thức này đã yêu cầu quyền BLUETOOTH_CONNECT, nhưng quyền này không được thực thi. Đảm bảo ứng dụng của bạn khai báo BLUETOOTH_CONNECT trong tệp AndroidManifest.xml của ứng dụng như trong đoạn mã sau và kiểm tra để đảm bảo rằng người dùng đã cấp quyền trước khi gọi 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à 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.

API khởi chạy Thẻ thông tin

Đố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

在 Android 11(API 级别 30)中,任何应用都可以在手机处于锁定状态时使用 Notification.Builder.setFullScreenIntent 发送全屏 intent。您可以通过在 AndroidManifest 中声明 USE_FULL_SCREEN_INTENT 权限,在应用安装时自动授予此权限。

全屏 intent 通知适用于需要用户立即注意的极高优先级通知,例如用户来电或用户配置的闹钟设置。对于以 Android 14(API 级别 34)或更高版本为目标平台的应用,获准使用此权限的应用仅限于提供通话和闹钟的应用。对于不适合此情况的任何应用,Google Play 商店会撤消其默认的 USE_FULL_SCREEN_INTENT 权限。这些政策变更的截止日期为 2024 年 5 月 31 日

在用户更新到 Android 14 之前,在手机上安装的应用仍拥有此权限。用户可以开启和关闭此权限。

您可以使用新 API NotificationManager.canUseFullScreenIntent 检查应用是否具有该权限;如果没有,应用可以使用新 intent ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT 启动设置页面,在该页面中,用户可以授予权限。

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ý

Đối với ứng dụng nhắm đến Android 14 (API cấp 34) trở lên, Android hạn chế việc ứng dụng gửi ý định ngầm ẩn đến các thành phần ứng dụng nội bộ theo những cách sau:

  • Ý định ngầm ẩn chỉ được gửi đến các thành phần đã xuất. Ứng dụng phải sử dụng một ý định tường minh để phân phối tới các thành phần chưa xuất hoặc đánh dấu thành phần đó là đã xuất.
  • Nếu một ứng dụng tạo ý định đang chờ xử lý có thể thay đổi với một ý định không chỉ định thành phần hoặc gói, thì hệ thống sẽ gửi ra một ngoại lệ.

Những thay đổi này ngăn các ứng dụng độc hại can thiệp vào ý định ngầm ẩn mà thành phần nội bộ của ứng dụng sử dụng.

Ví dụ: bạn có thể khai báo bộ lọc ý định trong tệp kê khai của ứng dụng:

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

Nếu ứng dụng của bạn cố gắng chạy hoạt động này bằng cách sử dụng một ý định ngầm ẩn, thì hệ thống sẽ gửi ra một ngoại lệ ActivityNotFoundException:

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

Để khởi chạy hoạt động không xuất, ứng dụng của bạn nên sử dụng ý định tường minh:

KotlinJava
// This makes the intent explicit.
val explicitIntent =
        Intent("com.example.action.APP_ACTION")
explicitIntent.apply {
    package = context.packageName
}
context.startActivity(explicitIntent)
// 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

Các ứng dụng và dịch vụ nhắm đến Android 14 (API cấp 34) trở lên và sử dụng trình thu thập dữ liệu đã đăng ký theo bối cảnh phải chỉ định cờ để cho biết liệu có nên xuất bộ thu sang tất cả ứng dụng khác trên thiết bị hay không: RECEIVER_EXPORTED hoặc RECEIVER_NOT_EXPORTED. Yêu cầu này giúp bảo vệ ứng dụng khỏi các lỗ hổng bảo mật bằng cách tận dụng các tính năng được giới thiệu trong Android 13 dành cho những bộ thu này.

Ngoại lệ đối với các bộ thu chỉ nhận tin do hệ thống truyền ra

Nếu ứng dụng của bạn chỉ đăng ký bộ nhận cho tin do hệ thống truyền ra qua các phương thức Context#registerReceiver (ví dụ: Context#registerReceiver()), thì bạn không nên chỉ định cờ khi đăng ký bộ nhận.

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:

KotlinJava
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)
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().

对于以 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

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.