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

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

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 Vitals ระบบอาจแจ้งให้ผู้ใช้จำกัดสิทธิ์เข้าถึงทรัพยากรของระบบสำหรับแอปนั้น

หากระบบสังเกตเห็นว่าแอปใช้ทรัพยากรมากเกินไป ระบบจะแจ้งให้ผู้ใช้ทราบและให้ตัวเลือกแก่ผู้ใช้ในการจำกัดการดำเนินการของแอป พฤติกรรมที่อาจทำให้ระบบแสดงประกาศมีดังนี้

  • 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

WorkManager เป็น API ที่ช่วยให้คุณกำหนดเวลา งานในเบื้องหลังที่ต้อง รับประกันว่างานจะเสร็จสมบูรณ์ ไม่ว่ากระบวนการของแอปจะทำงานอยู่หรือไม่ก็ตาม WorkManager จะเลือกวิธีที่เหมาะสมในการเรียกใช้งาน (ไม่ว่าจะในเธรดในกระบวนการของแอปโดยตรง รวมถึงการใช้ JobScheduler, FirebaseJobDispatcher หรือ AlarmManager) โดยอิงตามปัจจัยต่างๆ เช่น ระดับ API ของอุปกรณ์ นอกจากนี้ WorkManager ยังไม่จำเป็นต้องใช้บริการ Google Play และมีฟีเจอร์ขั้นสูงหลายอย่าง เช่น การเชื่อมโยงงานเข้าด้วยกันหรือการตรวจสอบสถานะของงาน ดูข้อมูลเพิ่มเติมได้ที่ WorkManager

ตรวจสอบการเชื่อมต่อเครือข่ายขณะที่แอปทำงาน

แอปที่กำลังทำงานจะยังคงฟัง CONNECTIVITY_CHANGE ด้วย BroadcastReceiver ที่ลงทะเบียนไว้ได้ อย่างไรก็ตาม ConnectivityManager API มีวิธีที่แข็งแกร่งกว่าในการขอ การเรียกกลับเมื่อตรงตามเงื่อนไขเครือข่ายที่ระบุเท่านั้น

ออบเจ็กต์ NetworkRequest จะกำหนดพารามิเตอร์ของการเรียกกลับของเครือข่ายในแง่ของ NetworkCapabilities คุณ สร้างออบเจ็กต์ NetworkRequest ด้วยคลาส NetworkRequest.Builder registerNetworkCallback() จากนั้นส่งออบเจ็กต์ NetworkRequest ไปยังระบบ เมื่อ ตรงตามเงื่อนไขของเครือข่าย แอปจะได้รับการเรียกกลับเพื่อเรียกใช้เมธอด onAvailable() ที่กำหนดไว้ในคลาส ConnectivityManager.NetworkCallback

แอปจะยังคงได้รับ Callback จนกว่าแอปจะออกหรือเรียกใช้ unregisterNetworkCallback()

ข้อจำกัดในการรับการออกอากาศรูปภาพและวิดีโอ

ใน Android 7.0 (ระดับ API 24) แอปจะส่งหรือรับการออกอากาศ ACTION_NEW_PICTURE หรือ ACTION_NEW_VIDEO ไม่ได้ ข้อจำกัดนี้ช่วย ลดผลกระทบต่อประสิทธิภาพและประสบการณ์ของผู้ใช้เมื่อแอปหลายแอปต้อง เปิดขึ้นเพื่อประมวลผลรูปภาพหรือวิดีโอใหม่ Android 7.0 (ระดับ API 24) ขยาย JobInfo และ JobParameters เพื่อมอบโซลูชันทางเลือก

ทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI เนื้อหา

Android 7.0 (ระดับ API 24) ขยาย JobInfo API ด้วยเมธอดต่อไปนี้เพื่อทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI เนื้อหา

JobInfo.TriggerContentUri()
แคปซูลพารามิเตอร์ที่จำเป็นในการทริกเกอร์งานเมื่อมีการเปลี่ยนแปลง URI เนื้อหา
JobInfo.Builder.addTriggerContentUri()
ส่งออบเจ็กต์ TriggerContentUri ไปยัง JobInfo ContentObserver จะตรวจสอบ URI เนื้อหา หากมีออบเจ็กต์ TriggerContentUri หลายรายการที่เชื่อมโยงกับงาน ระบบจะเรียกใช้ การเรียกกลับแม้ว่าจะรายงานการเปลี่ยนแปลงใน URI ของเนื้อหาเพียงรายการเดียวก็ตาม
เพิ่มแฟล็ก TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS เพื่อ ทริกเกอร์งานหากมีการเปลี่ยนแปลงลูกหลานของ URI ที่ระบุ โดยแฟล็กนี้ จะสอดคล้องกับพารามิเตอร์ 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 เนื้อหาที่ระบุ แอปของคุณจะ ได้รับ 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 ที่ทริกเกอร์งาน

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