กระบวนการที่ทำงานอยู่เบื้องหลังอาจใช้หน่วยความจำและแบตเตอรี่มาก ตัวอย่างเช่น การออกอากาศโดยนัยอาจเริ่มกระบวนการเบื้องหลังหลายรายการที่ลงทะเบียนไว้เพื่อฟังการออกอากาศนั้น แม้ว่ากระบวนการเหล่านั้นอาจไม่ได้ทํางานมากนัก ซึ่งอาจส่งผลอย่างมากต่อทั้งประสิทธิภาพของอุปกรณ์และประสบการณ์ของผู้ใช้
Android 7.0 (API ระดับ 24) ใช้ข้อจํากัดต่อไปนี้เพื่อบรรเทาปัญหานี้
- แอปที่กำหนดเป้าหมายเป็น Android 7.0 (API ระดับ 24) ขึ้นไปจะไม่ได้รับ
CONNECTIVITY_ACTION
การออกอากาศหากประกาศตัวรับการออกอากาศในไฟล์ Manifest แอปจะยังคงรับCONNECTIVITY_ACTION
การออกอากาศหากลงทะเบียนBroadcastReceiver
กับContext.registerReceiver()
และบริบทนั้นยังคงใช้งานได้ - แอปจะส่งหรือรับการออกอากาศ
ACTION_NEW_PICTURE
หรือACTION_NEW_VIDEO
ไม่ได้ การเพิ่มประสิทธิภาพนี้ส่งผลต่อแอปทั้งหมด ไม่ใช่เฉพาะแอปที่กำหนดเป้าหมายเป็น Android 7.0 (API ระดับ 24)
หากแอปใช้ Intent เหล่านี้ คุณควรนําการพึ่งพา Intent ดังกล่าวออกโดยเร็วที่สุดเพื่อให้กำหนดเป้าหมายอุปกรณ์ที่ใช้ Android 7.0 ขึ้นไปได้อย่างถูกต้อง เฟรมเวิร์ก Android มีโซลูชันหลายอย่างเพื่อลดความจำเป็นในการออกอากาศโดยนัยเหล่านี้ เช่น JobScheduler
และ WorkManager เวอร์ชันใหม่มีกลไกที่มีประสิทธิภาพในการกำหนดเวลาการดำเนินการของเครือข่ายเมื่อเป็นไปตามเงื่อนไขที่ระบุ เช่น การเชื่อมต่อกับเครือข่ายที่ไม่จำกัดปริมาณการใช้งาน นอกจากนี้ คุณยังใช้ JobScheduler
เพื่อตอบสนองต่อการเปลี่ยนแปลงของผู้ให้บริการเนื้อหาได้ด้วย JobInfo
ออบเจ็กต์จะรวมพารามิเตอร์ที่ JobScheduler
ใช้เพื่อตั้งเวลางาน เมื่อเงื่อนไขของงานตรงตามที่กำหนด ระบบจะเรียกใช้งานนี้ใน JobService
ของแอป
ในหน้านี้ เราจะดูวิธีใช้วิธีการอื่นๆ เช่น JobScheduler
เพื่อปรับแอปให้เข้ากับข้อจำกัดใหม่เหล่านี้
ข้อจำกัดที่ผู้ใช้เป็นผู้เริ่ม
ในหน้าการใช้งานแบตเตอรี่ภายในการตั้งค่าระบบ ผู้ใช้สามารถเลือกจากตัวเลือกต่อไปนี้
- ไม่จำกัด: อนุญาตให้ทำงานทั้งหมดในเบื้องหลัง ซึ่งอาจทำให้แบตเตอรี่หมดเร็วขึ้น
- เพิ่มประสิทธิภาพ (ค่าเริ่มต้น): เพิ่มประสิทธิภาพความสามารถของแอปในการทำงานเบื้องหลัง โดยอิงตามวิธีที่ผู้ใช้โต้ตอบกับแอป
- ถูกจำกัด: ป้องกันไม่ให้แอปทำงานอยู่เบื้องหลังโดยสมบูรณ์ แอปอาจไม่ทำงานตามที่คาดไว้
หากแอปแสดงลักษณะการทำงานที่ไม่ถูกต้องตามที่อธิบายไว้ใน Android Dev Tools ระบบอาจแจ้งให้ผู้ใช้จำกัดการเข้าถึงทรัพยากรของระบบของแอปนั้น
หากระบบพบว่าแอปใช้ทรัพยากรมากเกินไป ระบบจะแจ้งให้ผู้ใช้ทราบและให้ตัวเลือกแก่ผู้ใช้ในการจำกัดการดำเนินการของแอป ลักษณะการทำงานที่อาจทริกเกอร์การแจ้งเตือน ได้แก่
- 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 ยังไม่ต้องใช้บริการ Play และมีฟีเจอร์ขั้นสูงหลายอย่าง เช่น การเชื่อมโยงงานเข้าด้วยกันหรือการตรวจสอบสถานะของงาน ดูข้อมูลเพิ่มเติมได้ที่ WorkManager
ตรวจสอบการเชื่อมต่อเครือข่ายขณะที่แอปทํางานอยู่
แอปที่ทำงานอยู่จะยังคงฟัง CONNECTIVITY_CHANGE
ได้โดยใช้ BroadcastReceiver
ที่ลงทะเบียนไว้ อย่างไรก็ตาม ConnectivityManager
API มีวิธีการที่มีประสิทธิภาพมากขึ้นในการส่งคำขอการติดต่อกลับเมื่อเป็นไปตามเงื่อนไขเครือข่ายที่ระบุเท่านั้น
ออบเจ็กต์ NetworkRequest
จะกำหนดพารามิเตอร์ของ Callback ของเครือข่ายในแง่ของ NetworkCapabilities
คุณสร้างออบเจ็กต์ NetworkRequest
ด้วยคลาส NetworkRequest.Builder
registerNetworkCallback()
จากนั้นส่งออบเจ็กต์ NetworkRequest
ไปยังระบบ เมื่อเงื่อนไขเครือข่ายตรงตามข้อกำหนด แอปจะได้รับการเรียกกลับเพื่อเรียกใช้เมธอด onAvailable()
ที่กําหนดไว้ในคลาส ConnectivityManager.NetworkCallback
แอปจะยังคงได้รับการเรียกกลับจนกว่าแอปจะออกหรือเรียก 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) จะขยายJobInfo
API ด้วยเมธอดต่อไปนี้
-
JobInfo.TriggerContentUri()
- รวมพารามิเตอร์ที่จําเป็นเพื่อทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI ของเนื้อหา
-
JobInfo.Builder.addTriggerContentUri()
-
ส่งออบเจ็กต์
TriggerContentUri
ไปยังJobInfo
ContentObserver
จะตรวจสอบ URI ของเนื้อหาที่รวมอยู่ หากออบเจ็กต์TriggerContentUri
หลายรายการเชื่อมโยงกับงาน ระบบจะแสดงการเรียกกลับแม้ว่าจะรายงานการเปลี่ยนแปลงใน URI เนื้อหาเพียงรายการเดียวก็ตาม -
เพิ่ม Flag
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
เพื่อเรียกใช้งานหาก URI ใดๆ ที่เป็นรายการสืบทอดของ URI ที่ระบุมีการเปลี่ยนแปลง Flag นี้ตรงกับพารามิเตอร์notifyForDescendants
ที่ส่งไปยังregisterContentObserver()
หมายเหตุ: คุณใช้ TriggerContentUri()
ร่วมกับ setPeriodic()
หรือ setPersisted()
ไม่ได้ หากต้องการตรวจสอบการเปลี่ยนแปลงเนื้อหาอย่างต่อเนื่อง ให้กำหนดเวลาJobInfo
ใหม่ก่อนที่ JobService
ของแอปจะจัดการการเรียกกลับครั้งล่าสุดเสร็จสิ้น
โค้ดตัวอย่างต่อไปนี้จะกำหนดเวลางานให้ทริกเกอร์เมื่อระบบรายงานการเปลี่ยนแปลง 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 เนื้อหาที่ระบุ แอปของคุณจะได้รับการเรียกกลับและระบบจะส่งออบเจ็กต์ 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; }
เพิ่มประสิทธิภาพแอปเพิ่มเติม
การเพิ่มประสิทธิภาพแอปให้ทำงานในอุปกรณ์ที่มีหน่วยความจำต่ำหรือในสภาพแวดล้อมที่มีหน่วยความจำต่ำจะช่วยปรับปรุงประสิทธิภาพและประสบการณ์ของผู้ใช้ การนําการพึ่งพาบริการที่ทำงานอยู่เบื้องหลังและ 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