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

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

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