การเพิ่มประสิทธิภาพในเบื้องหลัง

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

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

KotlinJava
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)
}
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 ที่ช่วยให้คุณกำหนดเวลา งานเบื้องหลังที่จำเป็น รับประกันการดำเนินการเสร็จสมบูรณ์ ไม่ว่ากระบวนการของแอปจะเป็นไปหรือไม่ก็ตาม เครื่องมือจัดการงาน เลือกวิธีที่เหมาะสมในการทำงาน (ไม่ว่าจะเป็นเทรดโดยตรงในกระบวนการของแอป รวมทั้งการใช้ JobScheduler, FirebaseJobDispatcher หรือ LoadManager) โดยอิงตามปัจจัยต่างๆ เช่น ระดับ 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 เนื้อหาเพียงรายการเดียวก็ตาม
เพิ่มแฟล็ก TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS ไปยัง ทริกเกอร์งานหากองค์ประกอบสืบทอดของ URI ที่ระบุมีการเปลี่ยนแปลง แฟล็กนี้ สอดคล้องกับพารามิเตอร์ notifyForDescendants ที่ส่งไปยัง registerContentObserver()

หมายเหตุ: TriggerContentUri() ไม่สามารถใช้ใน รวมกับ setPeriodic() หรือ setPersisted() ในการติดตามการเปลี่ยนแปลงของเนื้อหาอย่างต่อเนื่อง JobInfoก่อนที่ JobService ของแอปจะจัดการกับ Callback ล่าสุดให้เสร็จสิ้น

โค้ดตัวอย่างต่อไปนี้กำหนดเวลางานที่จะทริกเกอร์เมื่อระบบรายงาน การเปลี่ยนแปลง URI เนื้อหา MEDIA_URI:

KotlinJava
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)
}
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 ที่ทริกเกอร์งาน:

KotlinJava
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
}
@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