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, thì bạn nên sửa đổi ứng dụng để hỗ trợ những hành vi này cho phù hợp, nếu có.

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 pháp BluetoothAdapter getProfileConnectionState() để nhắm mục tiêu ứng dụng 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 chưa được thực thi. Hãy đảm bảo ứng dụng của bạn khai báo BLUETOOTH_CONNECT trong AndroidManifest.xml như minh hoạ trong đoạn mã sau và kiểm tra để đảm bảo 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ố lệnh gọi lại và hành vi mạng

Kể từ khi được giới thiệu, 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, công việc đó sẽ tự dừng và không thành cô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, thì ứng dụng sẽ kích hoạt lỗi ANR với thông báo lỗi "No response to onStartJob" (Không phản hồi onStartJob) hoặc "No response to onStopJob" (Không phản hồi cho onStopJob). Hãy cân nhắc di chuyển sang WorkManager, một công cụ hỗ trợ xử lý không đồng bộ hoặc di chuyển mọi công việc nặng nhọc vào luồng trong nền.

JobScheduler cũng đưa ra yêu cầu khai báo quyền ACCESS_NETWORK_STATE nếu sử dụng quy tắc ràng buộc setRequiredNetworkType hoặ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 đến Android 14 trở lên, thì điều này sẽ dẫn đến việc SecurityException.

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

Đối với các ứng dụng nhắm đến 14 trở lên, TileService#startActivityAndCollapse(Intent) không còn được dùng nữa và hiện sẽ gửi ra một ngoại lệ khi được gọi. Nếu ứng dụng của bạn chạy các 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 引入了所选照片访问权限,可让用户授权应用访问其媒体库中的特定图片和视频,而不是授予对指定类型的所有媒体的访问权限。

仅当您的应用以 Android 14(API 级别 34)或更高版本为目标平台时,才会启用此变更。如果您还没有使用照片选择器,建议您在应用中实现该选择器,以便在选择图片和视频时提供一致的体验,同时还可以加强用户隐私保护,而无需请求任何存储权限。

如果您使用存储权限维护自己的图库选择器,并且需要完全控制您的实现,请调整您的实现,以使用新的 READ_MEDIA_VISUAL_USER_SELECTED 权限。如果您的应用不使用新权限,系统会在兼容模式下运行应用。

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ế các ứng dụng từ việc gửi ý định ngầm ẩn đến các thành phần nội bộ của ứng dụng 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 một ý định đang chờ xử lý có thể thay đổi với ý định không chỉ định một thành phần hoặc gói, 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ệ:

Kotlin

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

Java

// Throws an 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:

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

以 Android 14(API 级别 34)或更高版本为目标平台并使用上下文注册的接收器的应用和服务必须指定一个标志,以指明接收器是否应导出到设备上的所有其他应用:分别为 RECEIVER_EXPORTEDRECEIVER_NOT_EXPORTED。此要求有助于利用 Android 13 中引入的这些接收器的功能,保护应用免受安全漏洞的影响。

仅接收系统广播的接收器的例外情况

如果您的应用仅通过 Context#registerReceiver 方法(例如 Context#registerReceiver())为系统广播注册接收器,那么在注册接收器时不应指定标志。

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 Mã động Khi tải (DCL), tất cả các tệp được tải động phải được đánh dấu là chỉ đọc. Nếu không, hệ thống sẽ gửi ra một ngoại lệ. Vì vậy, ứng dụng nên tránh mã tải động bất cứ khi nào có thể, 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

Đối với ứng dụng nhắm đến Android 14 (API cấp 34) trở lên, hệ thống sẽ hạn chế hơn nữa thời điểm các ứng dụng được phép bắt đầu hoạt động ở chế độ nền:

  • Khi gửi một PendingIntent bằng PendingIntent#send() hoặc các phương thức tương tự, ứng dụng phải chọn sử dụng nếu muốn cấp đặc quyền khởi chạy hoạt động của riêng mình ở chế độ nền để bắt đầu ý định đang chờ xử lý. Để chọn sử dụng, ứng dụng phải truyền một gói ActivityOptionssetPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED).
  • Khi một ứng dụng đang hiển thị ràng buộc dịch vụ của một ứng dụng khác ở chế độ nền bằng phương thức bindService(), thì ứng dụng đang hiển thị đó phải chọn sử dụng nếu muốn cấp đặc quyền khởi chạy hoạt động của riêng mình ở chế độ nền với dịch vụ ràng buộc. Để chọn sử dụng, ứng dụng phải gắn cờ BIND_ALLOW_ACTIVITY_STARTS khi gọi phương thức bindService().

Những thay đổi này mở rộng nhóm quy tắc hạn chế hiện có để bảo vệ người dùng bằng cách ngăn các ứng dụng độc hại lợi dụng API để bắt đầu các hoạt động gây phiền toái ở chế độ nền.

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 ZipException nếu tên tệp zip 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, SecurityException sẽ được MediaProjection#createVirtualDisplay gửi theo một trong các 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ỗi 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 một Surface mới có chiều rộng và chiều cao mới vào VirtualDisplay#setSurface.

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

Ứng dụng nên đăng ký lệnh gọi lại để xử lý trường hợp người dùng không đồng ý để tiếp tục phiên chụp. Để thực hiện việc này, hãy triển khai Callback#onStop và yêu cầu ứng dụng của bạn 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 nó.

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.