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

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

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

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

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

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.

การบังคับใช้สิทธิ์ BLUETOOTH_CONNECT ใน BluetoothAdapter

Android 14 จะบังคับใช้สิทธิ์ BLUETOOTH_CONNECT เมื่อเรียกใช้เมธอด getProfileConnectionState() ของ BluetoothAdapter สำหรับแอปที่กำหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไป

วิธีนี้ต้องใช้สิทธิ์ BLUETOOTH_CONNECT อยู่แล้ว แต่ไม่มีการบังคับใช้ ตรวจสอบว่าแอปประกาศ BLUETOOTH_CONNECT ในไฟล์ AndroidManifest.xml ของแอปตามที่แสดงในข้อมูลโค้ดต่อไปนี้ และตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์แล้วก่อนที่จะเรียกใช้ getProfileConnectionState

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

การอัปเดต OpenJDK 17

Android 14 ยังคงปรับปรุงไลบรารีหลักของ Android ให้สอดคล้องกับฟีเจอร์ใน OpenJDK LTS เวอร์ชันล่าสุด ซึ่งรวมถึงทั้งการอัปเดตไลบรารีและการรองรับภาษา Java 17 สําหรับนักพัฒนาแอปและแพลตฟอร์ม

การเปลี่ยนแปลงบางอย่างอาจส่งผลต่อความเข้ากันได้ของแอป

  • การเปลี่ยนแปลงนิพจน์ทั่วไป: ตอนนี้ระบบไม่อนุญาตให้ใช้การอ้างอิงกลุ่มที่ไม่ถูกต้องเพื่อให้สอดคล้องกับความหมายของ OpenJDK มากขึ้น คุณอาจเห็นกรณีใหม่ซึ่งคลาส java.util.regex.Matcher แสดงข้อยกเว้น IllegalArgumentException ดังนั้นโปรดทดสอบแอปในส่วนที่ใช้นิพจน์ทั่วไป หากต้องการเปิดหรือปิดใช้การเปลี่ยนแปลงนี้ขณะทดสอบ ให้สลับ Flag DISALLOW_INVALID_GROUP_REFERENCE โดยใช้เครื่องมือเฟรมเวิร์กความเข้ากันได้
  • การจัดการ UUID: ตอนนี้เมธอด java.util.UUID.fromString() จะตรวจสอบอย่างเข้มงวดมากขึ้นเมื่อตรวจสอบอาร์กิวเมนต์อินพุต คุณจึงอาจเห็น IllegalArgumentException ระหว่างการแปลงข้อมูลย้อนกลับ หากต้องการเปิดหรือปิดใช้การเปลี่ยนแปลงนี้ขณะทดสอบ ให้สลับ Flag ENABLE_STRICT_VALIDATION โดยใช้เครื่องมือเฟรมเวิร์กความเข้ากันได้
  • ปัญหาเกี่ยวกับ ProGuard: ในบางกรณี การเพิ่มคลาส java.lang.ClassValue จะทำให้เกิดปัญหาหากคุณพยายามบีบอัด สร้างความสับสน และเพิ่มประสิทธิภาพแอปโดยใช้ ProGuard ปัญหานี้เกิดจากไลบรารี Kotlin ที่เปลี่ยนลักษณะการทํางานรันไทม์โดยขึ้นอยู่กับว่า Class.forName("java.lang.ClassValue") แสดงผลคลาสหรือไม่ หากแอปของคุณพัฒนาขึ้นโดยใช้รันไทม์เวอร์ชันเก่าที่ไม่มีคลาส java.lang.ClassValue อยู่ การเพิ่มประสิทธิภาพเหล่านี้อาจนําเมธอด computeValue ออกจากคลาสที่มาจาก java.lang.ClassValue

JobScheduler เสริมการทำงานแบบเรียกกลับและเครือข่าย

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

ANR นี้อาจเกิดจาก 2 สถานการณ์ ดังนี้ 1. มีงานบล็อกเทรดหลัก ซึ่งทําให้ Callback onStartJob หรือ onStopJob ไม่สามารถดําเนินการและทํางานให้เสร็จภายในเวลาจํากัดที่คาดไว้ 2. นักพัฒนาแอปกำลังทำงานที่บล็อกภายในการเรียกกลับ onStartJob หรือ onStopJob ของ JobScheduler ซึ่งทำให้การเรียกกลับดำเนินการไม่เสร็จภายในเวลาจำกัดที่คาดไว้

ในการแก้ไขข้อ 1 คุณจะต้องแก้ไขข้อบกพร่องของสิ่งที่บล็อกเทรดหลักเพิ่มเติม เมื่อเกิด ANR ขึ้น คุณสามารถทำเช่นนี้ได้โดยใช้ ApplicationExitInfo#getTraceInputStream() เพื่อรับ Tombstone ติดตามเมื่อเกิด ANR หากคุณสร้าง ANR ซ้ำด้วยตนเองได้ คุณสามารถบันทึกการติดตามของระบบและตรวจสอบการติดตามได้โดยใช้ Android Studio หรือ Perfetto เพื่อให้เข้าใจได้ดีขึ้นถึงสิ่งที่กำลังทำงานอยู่ เทรดหลักเมื่อเกิด ANR โปรดทราบว่าปัญหานี้อาจเกิดขึ้นเมื่อใช้ JobScheduler API โดยตรง หรือใช้ WorkManager ซึ่งเป็นไลบรารี androidx

หากต้องการแก้ไขปัญหาที่ 2 ให้ลองเปลี่ยนไปใช้ WorkManager ซึ่งรองรับการรวมการประมวลผลใน onStartJob หรือ onStopJob ในเธรดแบบแอซิงโครนัส

JobScheduler ยังกำหนดให้ต้องประกาศสิทธิ์ ACCESS_NETWORK_STATE ด้วยหากใช้ข้อจำกัด setRequiredNetworkType หรือ setRequiredNetwork หากแอปของคุณไม่ได้ประกาศฟิลด์ สิทธิ์ ACCESS_NETWORK_STATE เมื่อกำหนดเวลางานและกำหนดเป้าหมาย Android 14 ขึ้นไปจะส่งผลให้เกิด SecurityException

Tiles Launch API

สำหรับแอปที่กำหนดเป้าหมายเป็น Android 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.

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

การแจ้งเตือน 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 ที่ไม่ชัดแจ้งและที่รอดําเนินการ

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

  • ระบบจะส่ง Intent ที่ไม่ชัดแจ้งไปยังคอมโพเนนต์ที่ส่งออกเท่านั้น แอปต้องใช้ Intent ที่ชัดเจนเพื่อนำส่งไปยังคอมโพเนนต์ที่ไม่ได้ส่งออก หรือทำเครื่องหมายคอมโพเนนต์ว่าส่งออกแล้ว
  • หากแอปสร้าง PendingIntent ที่เปลี่ยนแปลงได้โดยมี Intent ที่ไม่ได้ระบุคอมโพเนนต์หรือแพ็กเกจ ระบบจะแสดงข้อยกเว้น

การเปลี่ยนแปลงเหล่านี้จะช่วยป้องกันไม่ให้แอปที่เป็นอันตรายขัดขวาง Intent แบบไม่เจาะจงปลายทางที่มีไว้สำหรับให้คอมโพเนนต์ภายในของแอปใช้งาน

ต่อไปนี้คือตัวอย่างตัวกรอง Intent ที่ประกาศได้ในไฟล์ Manifest ของแอป

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

หากแอปพยายามเปิดใช้งานกิจกรรมนี้โดยใช้ Intent ที่ไม่ชัด ระบบจะแสดงข้อยกเว้น ActivityNotFoundException ดังนี้

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

หากต้องการเปิดกิจกรรมที่ไม่ได้ส่งออก แอปของคุณควรใช้ Intent แบบเจาะจงแทน ดังนี้

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

สําหรับแอปที่กําหนดเป้าหมายเป็น Android 14 (API ระดับ 34) ขึ้นไป Android จะป้องกันช่องโหว่ Path Traversal ของไฟล์ ZIP ดังนี้ ZipFile(String) และ ZipInputStream.getNextEntry() จะแสดงข้อผิดพลาด ZipException หากชื่อรายการไฟล์ ZIP มี ".." หรือขึ้นต้นด้วย "/"

แอปสามารถเลือกไม่ใช้การตรวจสอบนี้ได้โดยเรียกใช้ 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 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.

ดูข้อมูลเพิ่มเติมเกี่ยวกับการเปลี่ยนแปลงใน Android เวอร์ชันนี้ได้ที่การอัปเดตข้อจํากัดอินเทอร์เฟซที่ไม่ใช่ SDK ใน Android 14 ดูข้อมูลเพิ่มเติมเกี่ยวกับอินเทอร์เฟซที่ไม่ใช่ SDK โดยทั่วไปได้ที่ข้อจำกัดเกี่ยวกับอินเทอร์เฟซที่ไม่ใช่ SDK