การเปลี่ยนแปลงลักษณะการทำงาน: แอปที่กำหนดเป้าหมายเป็น Android 14 ขึ้นไป

Android 14 มีการเปลี่ยนแปลงลักษณะการทำงานที่อาจส่งผลต่อแอปของคุณเช่นเดียวกับรุ่นก่อนหน้า การเปลี่ยนแปลงลักษณะการทำงานต่อไปนี้มีผลเฉพาะกับแอปที่กำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไป หากแอปกำหนดเป้าหมายเป็น Android 14 ขึ้นไป คุณควรแก้ไขแอปให้รองรับลักษณะการทำงานเหล่านี้อย่างเหมาะสม ในกรณีที่เกี่ยวข้อง

อย่าลืมตรวจสอบรายการการเปลี่ยนแปลงลักษณะการทำงานที่มีผลกับแอปทั้งหมด ที่ทำงานบน Android 14 ไม่ว่าtargetSdkVersion ของแอปจะเป็นอย่างไร

ฟังก์ชันหลัก

ต้องระบุประเภทบริการที่ทำงานอยู่เบื้องหน้า

หากแอปกำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไป แอปต้องระบุประเภทบริการที่ทำงานอยู่เบื้องหน้าอย่างน้อย 1 ประเภทสำหรับบริการที่ทำงานอยู่เบื้องหน้าแต่ละรายการภายในแอป คุณควรเลือกประเภทบริการที่ทำงานอยู่เบื้องหน้าที่แสดงถึง Use Case ของแอป ระบบคาดหวังว่าบริการที่ทำงานอยู่เบื้องหน้าซึ่งมีประเภทหนึ่งๆ จะเป็นไปตาม Use Case ที่เฉพาะเจาะจง

หากกรณีการใช้งานในแอปของคุณไม่เกี่ยวข้องกับประเภทใดเลย เราขอแนะนำให้ย้ายข้อมูลตรรกะของคุณเพื่อใช้ WorkManager หรือการโอนข้อมูลที่เริ่มต้นโดยผู้ใช้

การบังคับใช้สิทธิ์ BLUETOOTH_CONNECT ใน 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" />

การอัปเดต OpenJDK 17

Android 14 continues the work of refreshing Android's core libraries to align with the features in the latest OpenJDK LTS releases, including both library updates and Java 17 language support for app and platform developers.

A few of these changes can affect app compatibility:

  • Changes to regular expressions: Invalid group references are now disallowed to more closely follow the semantics of OpenJDK. You might see new cases where an IllegalArgumentException is thrown by the java.util.regex.Matcher class, so make sure to test your app for areas that use regular expressions. To enable or disable this change while testing, toggle the DISALLOW_INVALID_GROUP_REFERENCE flag using the compatibility framework tools.
  • UUID handling: The java.util.UUID.fromString() method now does more strict checks when validating the input argument, so you might see an IllegalArgumentException during deserialization. To enable or disable this change while testing, toggle the ENABLE_STRICT_VALIDATION flag using the compatibility framework tools.
  • ProGuard issues: In some cases, the addition of the java.lang.ClassValue class causes an issue if you try to shrink, obfuscate, and optimize your app using ProGuard. The problem originates with a Kotlin library that changes runtime behaviour based on whether Class.forName("java.lang.ClassValue") returns a class or not. If your app was developed against an older version of the runtime without the java.lang.ClassValue class available, then these optimizations might remove the computeValue method from classes derived from java.lang.ClassValue.

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.

API การเปิดตัวไทล์

For apps targeting 14 and higher, TileService#startActivityAndCollapse(Intent) is deprecated and now throws an exception when called. If your app launches activities from tiles, use TileService#startActivityAndCollapse(PendingIntent) instead.

ความเป็นส่วนตัว

สิทธิ์เข้าถึงรูปภาพและวิดีโอบางส่วน

Android 14 เปิดตัวการเข้าถึงรูปภาพที่เลือก ซึ่งช่วยให้ผู้ใช้สามารถให้สิทธิ์แอปเข้าถึงรูปภาพและวิดีโอที่เฉพาะเจาะจงในคลังได้ แทนที่จะให้สิทธิ์เข้าถึงสื่อทั้งหมดในประเภทหนึ่งๆ

การเปลี่ยนแปลงนี้จะเปิดใช้ก็ต่อเมื่อแอปกำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไปเท่านั้น หากคุณยังไม่ได้ใช้เครื่องมือเลือกรูปภาพ เราขอแนะนำให้ติดตั้งใช้งานในแอปเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่สม่ำเสมอในการเลือกรูปภาพและวิดีโอ รวมถึงช่วยเพิ่มความเป็นส่วนตัวของผู้ใช้โดยไม่ต้องขอสิทธิ์เข้าถึงพื้นที่เก็บข้อมูล

หากคุณดูแลรักษาเครื่องมือเลือกแกลเลอรีของคุณเองโดยใช้สิทธิ์เข้าถึงพื้นที่เก็บข้อมูลและต้องการควบคุมการติดตั้งใช้งานอย่างเต็มรูปแบบ ให้ปรับการติดตั้งใช้งานเพื่อใช้สิทธิ์ READ_MEDIA_VISUAL_USER_SELECTED ใหม่ หากแอปของคุณไม่ได้ใช้สิทธิ์ใหม่ ระบบจะเรียกใช้แอปในโหมดความเข้ากันได้

ประสบการณ์ของผู้ใช้

รักษาความปลอดภัยให้กับการแจ้งเตือน Intent แบบเต็มหน้าจอ

ใน Android 11 (API ระดับ 30) แอปใดก็ได้ที่จะใช้ Notification.Builder.setFullScreenIntent เพื่อส่ง Intent แบบเต็มหน้าจอได้ขณะที่โทรศัพท์ล็อกอยู่ คุณสามารถให้สิทธิ์นี้โดยอัตโนมัติเมื่อติดตั้งแอปโดยประกาศสิทธิ์ USE_FULL_SCREEN_INTENT ใน AndroidManifest

การแจ้งเตือน Intent แบบเต็มหน้าจอออกแบบมาเพื่อแจ้งเตือนที่มีลำดับความสำคัญสูงมากซึ่งต้องการให้ผู้ใช้สนใจในทันที เช่น การโทรเข้าหรือการตั้งค่านาฬิกาปลุกที่ผู้ใช้กำหนดค่าไว้ สำหรับแอปที่กำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไป แอปที่ได้รับอนุญาตให้ใช้สิทธิ์นี้จะจำกัดไว้เฉพาะแอปที่มีการโทรและการปลุกเท่านั้น Google Play Store จะเพิกถอนสิทธิ์ USE_FULL_SCREEN_INTENT เริ่มต้นสำหรับแอปที่ไม่ตรงกับโปรไฟล์นี้ กำหนดเวลาสำหรับการเปลี่ยนแปลงนโยบายเหล่านี้คือ31 พฤษภาคม 2024

สิทธิ์นี้จะยังคงเปิดใช้อยู่สำหรับแอปที่ติดตั้งในโทรศัพท์ก่อนที่ผู้ใช้จะอัปเดตเป็น Android 14 ผู้ใช้จะเปิดหรือปิดสิทธิ์นี้ได้

คุณสามารถใช้ API ใหม่ NotificationManager.canUseFullScreenIntent เพื่อตรวจสอบว่าแอปของคุณมีสิทธิ์หรือไม่ หากไม่มี แอปจะใช้ Intent ใหม่ ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT เพื่อเปิดหน้าการตั้งค่าที่ผู้ใช้สามารถให้สิทธิ์ได้

ความปลอดภัย

ข้อจำกัดของ Intent โดยนัยและ Intent ที่รอดำเนินการ

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 ที่ลงทะเบียนรันไทม์ต้องระบุลักษณะการส่งออก

แอปและบริการที่กำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไปและใช้ตัวรับที่ลงทะเบียนตามบริบทต้องระบุ Flag เพื่อระบุว่าควรส่งออกตัวรับไปยังแอปอื่นๆ ทั้งหมดในอุปกรณ์หรือไม่ โดยจะใช้ RECEIVER_EXPORTED หรือ RECEIVER_NOT_EXPORTED ตามลำดับ ข้อกำหนดนี้ช่วยปกป้องแอปจากช่องโหว่ด้านความปลอดภัยด้วยการใช้ฟีเจอร์สำหรับรีซีฟเวอร์เหล่านี้ซึ่งเปิดตัวใน Android 13

ข้อยกเว้นสำหรับผู้รับที่รับเฉพาะการออกอากาศของระบบ

หากแอปลงทะเบียนตัวรับสำหรับการออกอากาศของระบบผ่านContext#registerReceiverวิธีต่างๆ เท่านั้น เช่น Context#registerReceiver() ก็ไม่ควรระบุ Flag เมื่อลงทะเบียนตัวรับ

การโหลดโค้ดแบบไดนามิกที่ปลอดภัยยิ่งขึ้น

หากแอปกำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไปและใช้การโหลดโค้ดแบบไดนามิก (DCL) ไฟล์ที่โหลดแบบไดนามิกทั้งหมดต้องทําเครื่องหมายเป็นแบบอ่านอย่างเดียว มิฉะนั้น ระบบจะยกเว้นข้อยกเว้น เราขอแนะนำให้แอปหลีกเลี่ยงการโหลดโค้ดแบบไดนามิกทุกครั้งที่ทำได้ เนื่องจากการดำเนินการดังกล่าวจะเพิ่มความเสี่ยงอย่างมากที่แอปจะเกิดการประนีประนอมจากการแทรกโค้ดหรือการดัดแปลงโค้ด

หากคุณต้องโหลดโค้ดแบบไดนามิก ให้ใช้วิธีการต่อไปนี้ในการตั้งค่า ไฟล์ที่โหลดแบบไดนามิก (เช่น ไฟล์ DEX, JAR หรือ APK) เป็นแบบอ่านอย่างเดียวในทันที เมื่อไฟล์เปิดขึ้นและก่อนที่จะเขียนเนื้อหา

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

จัดการไฟล์ที่โหลดแบบไดนามิกที่มีอยู่แล้ว

หากต้องการป้องกันไม่ให้มีการส่งข้อยกเว้นสำหรับไฟล์ที่โหลดแบบไดนามิกที่มีอยู่ เราขอแนะนำให้ลบและสร้างไฟล์ใหม่ก่อนที่จะลองใช้แบบไดนามิก โหลดส่วนขยายเหล่านั้นอีกครั้งในแอปของคุณ เมื่อคุณสร้างไฟล์ใหม่ ให้ทำตาม คำแนะนำสำหรับการทำเครื่องหมายไฟล์เป็นแบบอ่านอย่างเดียวในขณะที่เขียน อีกวิธีหนึ่งคือ ติดป้ายกำกับไฟล์ที่มีอยู่ใหม่เป็นแบบอ่านอย่างเดียว แต่ในกรณีนี้เราขอแนะนำ ขอแนะนำให้คุณตรวจสอบความสมบูรณ์ของไฟล์ก่อน (ตัวอย่างเช่น ตรวจสอบลายเซ็นของไฟล์กับค่าที่เชื่อถือได้) เพื่อช่วยปกป้องแอปของคุณ จากการกระทำที่เป็นอันตราย

ข้อจำกัดเพิ่มเติมในการเริ่มต้นกิจกรรมจากเบื้องหลัง

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.

Zip Path Traversal

For apps targeting Android 14 (API level 34) or higher, Android prevents the Zip Path Traversal Vulnerability in the following way: ZipFile(String) and ZipInputStream.getNextEntry() throws a ZipException if zip file entry names contain ".." or start with "/".

Apps can opt-out from this validation by calling dalvik.system.ZipPathValidator.clearCallback().

สำหรับแอปที่กำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไป SecurityException จะแสดงขึ้นโดย MediaProjection#createVirtualDisplay ในกรณีต่อไปนี้

แอปของคุณต้องขอให้ผู้ใช้ให้ความยินยอมก่อนเซสชันการจับภาพแต่ละครั้ง เซสชันการบันทึกเดียวคือการเรียกใช้ MediaProjection#createVirtualDisplay ครั้งเดียว และต้องใช้อินสแตนซ์ MediaProjection แต่ละรายการเพียงครั้งเดียว

จัดการการเปลี่ยนแปลงการกำหนดค่า

หากแอปของคุณต้องการเรียกใช้ MediaProjection#createVirtualDisplay เพื่อจัดการการเปลี่ยนแปลงการกำหนดค่า (เช่น การวางแนวหน้าจอหรือการเปลี่ยนแปลงขนาดหน้าจอ) ให้ทำตามขั้นตอนต่อไปนี้เพื่ออัปเดต VirtualDisplay สำหรับอินสแตนซ์ MediaProjection ที่มีอยู่

  1. เรียกใช้ VirtualDisplay#resize โดยใช้ความกว้างและความสูงใหม่
  2. ระบุ Surface ใหม่ที่มีความกว้างและความสูงใหม่ให้กับ VirtualDisplay#setSurface

ลงทะเบียนการโทรกลับ

แอปของคุณควรลงทะเบียนการเรียกกลับเพื่อจัดการกรณีที่ผู้ใช้ไม่ให้ความยินยอมในการบันทึกเซสชันต่อไป โดยให้ใช้ Callback#onStop และแอปของคุณเผยแพร่ทรัพยากรที่เกี่ยวข้อง (เช่น VirtualDisplay และ Surface)

หากแอปไม่ได้ลงทะเบียนการเรียกกลับนี้ MediaProjection#createVirtualDisplay จะแสดง IllegalStateException เมื่อแอปเรียกใช้

ข้อจำกัดที่ไม่ใช่ SDK ที่อัปเดตแล้ว

Android 14 มีรายการอินเทอร์เฟซที่ไม่ใช่ SDK ที่ถูกจำกัดซึ่งอัปเดตแล้ว โดยอิงตามการทำงานร่วมกับนักพัฒนาแอป Android และการทดสอบภายในล่าสุด เราจะตรวจสอบว่ามีทางเลือกสาธารณะ พร้อมใช้งานก่อนที่จะจำกัดอินเทอร์เฟซที่ไม่ใช่ SDK ทุกครั้งที่ทำได้

หากแอปไม่ได้กำหนดเป้าหมายเป็น Android 14 การเปลี่ยนแปลงบางอย่างเหล่านี้ อาจไม่มีผลกับคุณในทันที อย่างไรก็ตาม แม้ว่าปัจจุบันคุณจะใช้ อินเทอร์เฟซที่ไม่ใช่ SDK บางรายการได้ (ขึ้นอยู่กับระดับ API เป้าหมายของแอป) แต่การใช้วิธีการหรือฟิลด์ที่ไม่ใช่ SDK ใดๆ ก็ยังคงมีความเสี่ยงสูงที่จะทำให้แอป ขัดข้อง

หากไม่แน่ใจว่าแอปใช้อินเทอร์เฟซที่ไม่ใช่ SDK หรือไม่ คุณสามารถทดสอบแอป เพื่อดูได้ หากแอปของคุณใช้อินเทอร์เฟซที่ไม่ใช่ SDK คุณควรเริ่มวางแผน การย้ายข้อมูลไปยังทางเลือกอื่นของ SDK อย่างไรก็ตาม เราเข้าใจว่าแอปบางแอปมี Use Case ที่ถูกต้องสำหรับการใช้อินเทอร์เฟซที่ไม่ใช่ SDK หากไม่พบวิธีอื่นแทนการใช้อินเทอร์เฟซที่ไม่ใช่ SDK สำหรับฟีเจอร์ในแอป คุณควรขอ 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.