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

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

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 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 ของเนื้อหาที่ระบุ แอปของคุณจะ ได้รับการเรียกกลับและส่งออบเจ็กต์ JobParameters ไปยัง เมธอด onStartJob() ใน MediaContentJob.class

ระบุว่าหน่วยงานด้านเนื้อหาใดที่ทริกเกอร์งาน

Android 7.0 (API ระดับ 24) ยังขยาย JobParameters เพื่อ อนุญาตให้แอปรับข้อมูลที่เป็นประโยชน์เกี่ยวกับสิ่งที่ Content Authority และ 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