กระบวนการเบื้องหลังอาจใช้หน่วยความจำและแบตเตอรี่มาก ตัวอย่างเช่น การกระจายข้อมูลแบบไม่เจาะจงปลายทางอาจเริ่มกระบวนการเบื้องหลังหลายรายการที่ลงทะเบียนเพื่อรอรับข้อมูล แม้ว่ากระบวนการเหล่านั้นอาจไม่ได้ทำงานมากนักก็ตาม ซึ่งอาจส่งผลกระทบอย่างมากต่อทั้งประสิทธิภาพของอุปกรณ์และประสบการณ์ของผู้ใช้
เพื่อบรรเทาปัญหานี้ Android 7.0 (ระดับ API 24) จึงใช้ข้อจำกัดต่อไปนี้
- แอปที่กำหนดเป้าหมายเป็น Android 7.0 (ระดับ API 24) ขึ้นไปจะไม่ได้รับการออกอากาศ
CONNECTIVITY_ACTIONหากประกาศ Broadcast Receiver ในไฟล์ Manifest แอปจะยังคงได้รับการออกอากาศCONNECTIVITY_ACTIONหากลงทะเบียนBroadcastReceiverด้วยContext.registerReceiver()และบริบทนั้นยังคงถูกต้อง - แอปไม่สามารถส่งหรือรับการออกอากาศ
ACTION_NEW_PICTUREหรือACTION_NEW_VIDEOการเพิ่มประสิทธิภาพนี้ส่งผลต่อแอปทั้งหมด ไม่ใช่เฉพาะแอปที่กำหนดเป้าหมายเป็น Android 7.0 (ระดับ API 24)
หากแอปของคุณใช้ Intent เหล่านี้ คุณควรนำทรัพยากร Dependency ของ Intent เหล่านั้นออกโดยเร็วที่สุดเพื่อให้กำหนดเป้าหมายอุปกรณ์ที่ใช้ Android 7.0 ขึ้นไปได้อย่างเหมาะสม เฟรมเวิร์ก Android มีโซลูชันหลายอย่างเพื่อลดความจำเป็นในการใช้การกระจายข้อมูลแบบไม่เจาะจงปลายทางเหล่านี้ ตัวอย่างเช่น JobScheduler และ WorkManager ใหม่มีกลไกที่มีประสิทธิภาพในการกำหนดเวลาการทำงานของเครือข่ายเมื่อเป็นไปตามเงื่อนไขที่ระบุ เช่น การเชื่อมต่อกับเครือข่ายที่ไม่มีการวัดปริมาณอินเทอร์เน็ต ตอนนี้คุณยังใช้ JobScheduler เพื่อตอบสนองต่อการเปลี่ยนแปลงใน Content Provider ได้ด้วย ออบเจ็กต์ JobInfo จะรวมพารามิเตอร์ที่ JobScheduler ใช้เพื่อกำหนดเวลางานของคุณ เมื่อเป็นไปตามเงื่อนไขของงาน ระบบจะเรียกใช้งานนี้ใน JobService ของแอป
ในหน้านี้ เราจะเรียนรู้วิธีใช้เมธอดทางเลือก เช่น JobScheduler เพื่อปรับแอปให้เข้ากับข้อจำกัดใหม่เหล่านี้
ข้อจำกัดที่ผู้ใช้เริ่มต้น
ในหน้าการใช้งานแบตเตอรี่ภายในการตั้งค่าระบบ ผู้ใช้สามารถเลือกจากตัวเลือกต่อไปนี้
- ไม่จำกัด: อนุญาตให้ทำงานในเบื้องหลังทั้งหมด ซึ่งอาจใช้แบตเตอรี่มากขึ้น
- เพิ่มประสิทธิภาพ (ค่าเริ่มต้น): เพิ่มประสิทธิภาพความสามารถของแอปในการทำงานในเบื้องหลัง โดยอิงตามวิธีที่ผู้ใช้โต้ตอบกับแอป
- จำกัด: ป้องกันไม่ให้แอปทำงานในเบื้องหลังโดยสมบูรณ์ แอปอาจไม่ทำงานตามที่คาดไว้
หากแอปแสดงลักษณะการทำงานที่ไม่ถูกต้องบางอย่างตามที่อธิบายไว้ใน Android Vitals ระบบอาจแจ้งให้ผู้ใช้จำกัดสิทธิ์เข้าถึงทรัพยากรระบบของแอปนั้น
หากระบบสังเกตเห็นว่าแอปใช้ทรัพยากรมากเกินไป ระบบจะแจ้งให้ผู้ใช้ทราบและให้ตัวเลือกแก่ผู้ใช้ในการจำกัดการทำงานของแอป ลักษณะการทำงานที่อาจทำให้เกิดการแจ้งเตือนมีดังนี้
- Wake Lock มากเกินไป: Wake Lock บางส่วน 1 รายการที่ทำงานเป็นเวลา 1 ชั่วโมงเมื่อหน้าจอปิดอยู่
- บริการที่ทำงานอยู่เบื้องหลังมากเกินไป: หากแอปกำหนดเป้าหมายเป็น API ระดับต่ำกว่า 26 และมีบริการที่ทำงานอยู่เบื้องหลังมากเกินไป
ข้อจำกัดที่แน่นอนที่กำหนดจะขึ้นอยู่กับผู้ผลิตอุปกรณ์ ตัวอย่างเช่น ในบิลด์ AOSP ที่ใช้ Android 9 (ระดับ API 28) ขึ้นไป แอปที่ทำงานอยู่เบื้องหลังซึ่งอยู่ในสถานะ "ถูกจำกัด" จะมีข้อจำกัดต่อไปนี้
- เปิดบริการที่ทำงานอยู่เบื้องหน้าไม่ได้
- ระบบจะนำบริการที่ทำงานอยู่เบื้องหน้าที่มีอยู่ออกจากเบื้องหน้า
- การปลุกไม่ทำงาน
- ระบบจะไม่ดำเนินการตามงาน
นอกจากนี้ หากแอปกำหนดเป้าหมายเป็น Android 13 (ระดับ API 33) ขึ้นไปและอยู่ในสถานะ "ถูกจำกัด" ระบบจะไม่ส่งประกาศ BOOT_COMPLETED หรือประกาศ LOCKED_BOOT_COMPLETED จนกว่าแอปจะเริ่มทำงานด้วยเหตุผลอื่นๆ
ข้อจำกัดที่เฉพาะเจาะจงจะแสดงอยู่ใน ข้อจำกัดการจัดการพลังงาน
ข้อจำกัดในการรับการออกอากาศกิจกรรมเครือข่าย
แอปที่กำหนดเป้าหมายเป็น Android 7.0 (ระดับ API 24) จะไม่ได้รับการออกอากาศ CONNECTIVITY_ACTION หากลงทะเบียนเพื่อรับการออกอากาศในไฟล์ Manifest และกระบวนการที่ขึ้นอยู่กับการออกอากาศนี้จะไม่เริ่มทำงาน ซึ่งอาจทำให้เกิดปัญหาสำหรับแอปที่ต้องการฟังการเปลี่ยนแปลงของเครือข่ายหรือทำกิจกรรมเครือข่ายจำนวนมากเมื่ออุปกรณ์เชื่อมต่อกับเครือข่ายที่ไม่มีการวัดปริมาณอินเทอร์เน็ต เฟรมเวิร์ก Android มีโซลูชันหลายอย่างเพื่อหลีกเลี่ยงข้อจำกัดนี้อยู่แล้ว แต่การเลือกโซลูชันที่เหมาะสมจะขึ้นอยู่กับสิ่งที่คุณต้องการให้แอปทำสำเร็จ
หมายเหตุ: BroadcastReceiver ที่ลงทะเบียนกับ Context.registerReceiver() จะยังคงได้รับการออกอากาศเหล่านี้ขณะที่แอปทำงานอยู่
ตั้งเวลางานเครือข่ายในการเชื่อมต่อแบบไม่มีการวัดปริมาณอินเทอร์เน็ต
เมื่อใช้คลาส JobInfo.Builder เพื่อสร้างออบเจ็กต์ JobInfo ให้ใช้เมธอด setRequiredNetworkType() และส่ง JobInfo.NETWORK_TYPE_UNMETERED เป็นพารามิเตอร์งาน ตัวอย่างโค้ดต่อไปนี้จะกำหนดเวลาให้บริการทำงานเมื่ออุปกรณ์เชื่อมต่อกับเครือข่ายที่ไม่มีการวัดปริมาณอินเทอร์เน็ตและกำลังชาร์จอยู่
Kotlin
const val MY_BACKGROUND_JOB = 0 ... fun scheduleJob(context: Context) { val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val job = JobInfo.Builder( MY_BACKGROUND_JOB, ComponentName(context, MyJobService::class.java) ) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build() jobScheduler.schedule(job) }
Java
public static final int MY_BACKGROUND_JOB = 0; ... public static void scheduleJob(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo job = new JobInfo.Builder( MY_BACKGROUND_JOB, new ComponentName(context, MyJobService.class)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build(); js.schedule(job); }
เมื่อตรงตามเงื่อนไขของงาน แอปจะได้รับการเรียกกลับเพื่อเรียกใช้เมธอด onStartJob() ใน JobService.class ที่ระบุ หากต้องการดูตัวอย่างเพิ่มเติมเกี่ยวกับการใช้งาน JobScheduler โปรดดูแอปตัวอย่าง JobScheduler
ทางเลือกใหม่สำหรับ JobScheduler คือ WorkManager ซึ่งเป็น API ที่ช่วยให้คุณกำหนดเวลางานเบื้องหลังที่ต้องรับประกันว่าจะเสร็จสมบูรณ์ได้ ไม่ว่ากระบวนการของแอปจะทำงานอยู่หรือไม่ก็ตาม WorkManager จะเลือกวิธีที่เหมาะสมในการเรียกใช้งาน (ไม่ว่าจะเรียกใช้โดยตรงในเทรดในกระบวนการของแอป รวมถึงใช้ JobScheduler, FirebaseJobDispatcher หรือ AlarmManager) โดยอิงตามปัจจัยต่างๆ เช่น ระดับ API ของอุปกรณ์ นอกจากนี้ WorkManager ไม่จำเป็นต้องใช้บริการ Google Play และมีฟีเจอร์ขั้นสูงหลายอย่าง เช่น การเชื่อมโยงงานเข้าด้วยกันหรือการตรวจสอบสถานะของงาน ดูข้อมูลเพิ่มเติมได้ที่ WorkManager
ตรวจสอบการเชื่อมต่อเครือข่ายขณะที่แอปทำงานอยู่
แอปที่ทำงานอยู่จะยังคงฟัง CONNECTIVITY_CHANGE ด้วย BroadcastReceiver ที่ลงทะเบียนไว้ได้ อย่างไรก็ตาม ConnectivityManager API มีวิธีที่มีประสิทธิภาพมากกว่าในการขอ Callback เฉพาะเมื่อเป็นไปตามเงื่อนไขเครือข่ายที่ระบุ
ออบเจ็กต์ NetworkRequest จะกำหนดพารามิเตอร์ของ Network Callback ในแง่ของ NetworkCapabilities คุณสร้างออบเจ็กต์ NetworkRequest ด้วยคลาส NetworkRequest.Builder registerNetworkCallback()
จากนั้นส่งออบเจ็กต์ NetworkRequest ไปยังระบบ เมื่อเป็นไปตามเงื่อนไขของเครือข่าย แอปจะได้รับการเรียกกลับเพื่อเรียกใช้เมธอด onAvailable() ที่กำหนดไว้ในคลาส ConnectivityManager.NetworkCallback
แอปจะยังคงได้รับ Callback ต่อไปจนกว่าแอปจะออกหรือเรียกใช้ unregisterNetworkCallback()
ข้อจำกัดในการรับการออกอากาศรูปภาพและวิดีโอ
ใน Android 7.0 (ระดับ API 24) แอปจะส่งหรือรับการออกอากาศ ACTION_NEW_PICTURE หรือ ACTION_NEW_VIDEO ไม่ได้ ข้อจำกัดนี้ช่วยลดผลกระทบต่อประสิทธิภาพและประสบการณ์ของผู้ใช้เมื่อแอปหลายแอปต้องเริ่มทำงานเพื่อประมวลผลรูปภาพหรือวิดีโอใหม่ Android 7.0 (API ระดับ 24) ขยาย JobInfo และ JobParameters เพื่อมอบโซลูชันทางเลือก
ทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI เนื้อหา
หากต้องการทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI เนื้อหา Android 7.0 (ระดับ API 24) จะขยาย API JobInfo ด้วยเมธอดต่อไปนี้
-
JobInfo.TriggerContentUri() - สรุปพารามิเตอร์ที่จำเป็นในการทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI เนื้อหา
-
JobInfo.Builder.addTriggerContentUri() -
ส่งออบเจ็กต์
TriggerContentUriไปยังJobInfoContentObserverจะตรวจสอบ URI เนื้อหาที่แคปซูลไว้ หากมีออบเจ็กต์TriggerContentUriหลายรายการที่เชื่อมโยงกับงาน ระบบจะเรียกกลับแม้ว่าจะรายงานการเปลี่ยนแปลงใน URI เนื้อหาเพียงรายการเดียวก็ตาม -
เพิ่มแฟล็ก
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTSเพื่อทริกเกอร์งานหากองค์ประกอบสืบทอดของ URI ที่ระบุมีการเปลี่ยนแปลง แฟล็กนี้สอดคล้องกับพารามิเตอร์notifyForDescendantsที่ส่งไปยังregisterContentObserver()
หมายเหตุ: ไม่สามารถใช้ TriggerContentUri() ร่วมกับ setPeriodic() หรือ setPersisted() ได้ หากต้องการตรวจสอบการเปลี่ยนแปลงเนื้อหาอย่างต่อเนื่อง ให้กำหนดเวลา JobInfo ใหม่ก่อนที่ JobService ของแอปจะจัดการ Callback ล่าสุดเสร็จสิ้น
โค้ดตัวอย่างต่อไปนี้จะกำหนดเวลางานให้ทริกเกอร์เมื่อระบบรายงานการเปลี่ยนแปลง URI เนื้อหา MEDIA_URI
Kotlin
const val MY_BACKGROUND_JOB = 0 ... fun scheduleJob(context: Context) { val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val job = JobInfo.Builder( MY_BACKGROUND_JOB, ComponentName(context, MediaContentJob::class.java) ) .addTriggerContentUri( JobInfo.TriggerContentUri( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS ) ) .build() jobScheduler.schedule(job) }
Java
public static final int MY_BACKGROUND_JOB = 0; ... public static void scheduleJob(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo.Builder builder = new JobInfo.Builder( MY_BACKGROUND_JOB, new ComponentName(context, MediaContentJob.class)); builder.addTriggerContentUri( new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); js.schedule(builder.build()); }
เมื่อระบบรายงานการเปลี่ยนแปลงใน URI เนื้อหาที่ระบุ แอปของคุณจะได้รับ Callback และออบเจ็กต์ JobParameters จะส่งไปยังเมธอด onStartJob() ใน MediaContentJob.class
ระบุว่าหน่วยงานด้านเนื้อหาใดเรียกให้งานทำงาน
Android 7.0 (ระดับ API 24) ยังขยาย JobParameters เพื่อให้แอปของคุณได้รับข้อมูลที่เป็นประโยชน์เกี่ยวกับหน่วยงานด้านเนื้อหาและ URI ที่ทริกเกอร์งานด้วย
-
Uri[] getTriggeredContentUris() -
แสดงผลอาร์เรย์ของ URI ที่ทริกเกอร์งาน ค่านี้จะเป็น
nullหากไม่มี URI ที่ทริกเกอร์งาน (เช่น งานถูกทริกเกอร์เนื่องจากถึงกำหนดเวลาหรือเหตุผลอื่นๆ) หรือจำนวน URI ที่เปลี่ยนแปลงมีมากกว่า 50 รายการ -
String[] getTriggeredContentAuthorities() -
แสดงผลอาร์เรย์สตริงของหน่วยงานด้านเนื้อหาที่ทริกเกอร์งาน
หากอาร์เรย์ที่แสดงผลไม่ใช่
nullให้ใช้getTriggeredContentUris()เพื่อดึงรายละเอียดว่า URI ใดมีการเปลี่ยนแปลง
โค้ดตัวอย่างต่อไปนี้จะลบล้างเมธอด JobService.onStartJob() และบันทึกหน่วยงานด้านเนื้อหาและ URI ที่ทริกเกอร์งาน
Kotlin
override fun onStartJob(params: JobParameters): Boolean { StringBuilder().apply { append("Media content has changed:\n") params.triggeredContentAuthorities?.also { authorities -> append("Authorities: ${authorities.joinToString(", ")}\n") append(params.triggeredContentUris?.joinToString("\n")) } ?: append("(No content)") Log.i(TAG, toString()) } return true }
Java
@Override public boolean onStartJob(JobParameters params) { StringBuilder sb = new StringBuilder(); sb.append("Media content has changed:\n"); if (params.getTriggeredContentAuthorities() != null) { sb.append("Authorities: "); boolean first = true; for (String auth : params.getTriggeredContentAuthorities()) { if (first) { first = false; } else { sb.append(", "); } sb.append(auth); } if (params.getTriggeredContentUris() != null) { for (Uri uri : params.getTriggeredContentUris()) { sb.append("\n"); sb.append(uri); } } } else { sb.append("(No content)"); } Log.i(TAG, sb.toString()); return true; }
เพิ่มประสิทธิภาพแอปให้ดียิ่งขึ้น
การเพิ่มประสิทธิภาพแอปให้ทำงานบนอุปกรณ์ที่มีหน่วยความจำต่ำหรือในสภาวะที่มีหน่วยความจำต่ำจะช่วยปรับปรุงประสิทธิภาพและประสบการณ์ของผู้ใช้ได้ การนำทรัพยากร Dependency ในบริการที่ทำงานอยู่เบื้องหลังและ Broadcast Receiver แบบไม่เจาะจงปลายทางที่ลงทะเบียนในไฟล์ Manifest ออกจะช่วยให้แอปทำงานได้ดีขึ้นในอุปกรณ์ดังกล่าว แม้ว่า Android 7.0 (ระดับ API 24) จะมีขั้นตอนในการลดปัญหาเหล่านี้บางส่วน แต่เราขอแนะนำให้คุณเพิ่มประสิทธิภาพแอปให้ทำงานโดยไม่ต้องใช้กระบวนการเบื้องหลังเหล่านี้เลย
คำสั่ง Android Debug Bridge (ADB) ต่อไปนี้จะช่วยคุณทดสอบลักษณะการทำงานของแอปเมื่อปิดใช้กระบวนการเบื้องหลัง
- หากต้องการจำลองสภาวะที่การกระจายข้อมูลแบบไม่เจาะจงปลายทางและบริการที่ทำงานอยู่เบื้องหลังไม่พร้อมใช้งาน ให้ป้อนคำสั่งต่อไปนี้
-
$ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore - หากต้องการเปิดใช้การกระจายข้อมูลแบบไม่เจาะจงปลายทางและบริการที่ทำงานอยู่เบื้องหลังอีกครั้ง ให้ป้อนคำสั่งต่อไปนี้
-
$ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow - คุณสามารถจำลองสถานการณ์ที่ผู้ใช้นำแอปของคุณไปไว้ในสถานะ "ถูกจำกัด" สำหรับการใช้งานแบตเตอรี่อยู่เบื้องหลังได้ การตั้งค่านี้จะป้องกันไม่ให้แอปทำงานในเบื้องหลังได้ โดยเรียกใช้คำสั่งต่อไปนี้ในหน้าต่างเทอร์มินัล
-
$ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny