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

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

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