กระบวนการที่ทำงานอยู่เบื้องหลังอาจใช้หน่วยความจำและแบตเตอรี่มาก ตัวอย่างเช่น การออกอากาศโดยนัยอาจเริ่มกระบวนการเบื้องหลังหลายรายการที่ลงทะเบียนไว้เพื่อฟังการออกอากาศนั้น แม้ว่ากระบวนการเหล่านั้นอาจไม่ได้ทํางานมากนัก ซึ่งอาจส่งผลกระทบอย่างมากต่อทั้งประสิทธิภาพของอุปกรณ์และประสบการณ์ของผู้ใช้
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 มีวิธีที่มีประสิทธิภาพมากกว่าในการส่งคำขอ Callback เมื่อเป็นไปตามเงื่อนไขของเครือข่ายที่ระบุเท่านั้น
ออบเจ็กต์ 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
หลายรายการที่เชื่อมโยงกับงาน ระบบจะเรียกใช้ Callback แม้ว่าจะรายงานการเปลี่ยนแปลงใน URI เนื้อหาเพียง 1 รายการก็ตาม -
เพิ่ม 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