Tối ưu hoá ở chế độ nền

Các quy trình trong nền có thể tốn nhiều bộ nhớ và pin. Ví dụ như một thông báo truyền phát ngầm có thể bắt đầu nhiều quy trình nền đã đăng ký để theo dõi thông báo đó, ngay cả khi các quy trình đó có thể không hoạt động hiệu quả. Việc này có thể có tác động đáng kể đến cả hiệu suất của thiết bị và trải nghiệm người dùng.

Để khắc phục vấn đề này, Android 7.0 (API cấp 24) áp dụng các hạn chế sau:

  • Các ứng dụng nhắm mục tiêu Android 7.0 (API cấp 24) trở lên sẽ không nhận được thông báo CONNECTIVITY_ACTION nếu những ứng dụng đó khai báo trình nhận truyền phát trong tệp kê khai. Các ứng dụng vẫn sẽ nhận được thông báo CONNECTIVITY_ACTION nếu đăng ký BroadcastReceiver thông qua Context.registerReceiver() và ngữ cảnh đó vẫn hợp lệ.
  • Ứng dụng không thể gửi hoặc nhận thông báo ACTION_NEW_PICTURE hoặc ACTION_NEW_VIDEO. Tính năng tối ưu hoá này ảnh hưởng đến tất cả ứng dụng chứ không chỉ những ứng dụng nhắm mục tiêu Android 7.0 (API cấp 24).

Nếu ứng dụng của bạn sử dụng các ý định này, thì bạn nên xoá các phần phụ thuộc này càng sớm càng tốt để có thể nhắm mục tiêu đúng cách trên những thiết bị chạy Android 7.0 trở lên. Khung Android cung cấp một số giải pháp để giảm thiểu nhu cầu đối với các thông báo truyền phát ngầm này. Ví dụ như JobSchedulerWorkManager mới cung cấp các cơ chế mạnh mẽ để lên lịch hoạt động mạng khi các điều kiện được chỉ định, chẳng hạn như việc kết nối với mạng không đo lượng dữ liệu được đáp ứng. Giờ đây, bạn cũng có thể dùng JobScheduler để phản hồi với các thay đổi đối với nhà cung cấp nội dung. Các đối tượng JobInfo đóng gói các tham số mà JobScheduler sử dụng để lên lịch công việc của bạn. Khi đáp ứng các điều kiện của công việc, hệ thống sẽ thực thi công việc này trên JobService của ứng dụng.

Trên trang này, chúng ta sẽ tìm hiểu cách dùng các phương thức thay thế, chẳng hạn như JobScheduler, để điều chỉnh ứng dụng theo những hạn chế mới này.

Các hạn chế do người dùng khởi tạo

Trên trang Mức sử dụng pin trong phần cài đặt hệ thống, người dùng có thể chọn trong số các tuỳ chọn sau:

  • Không hạn chế: Cho phép thực hiện mọi tác vụ trong nền, do đó có thể hao tốn nhiều pin hơn.
  • Tối ưu hoá (mặc định): Tối ưu hoá khả năng của ứng dụng để thực hiện tác vụ trong nền, dựa trên cách người dùng tương tác với ứng dụng.
  • Hạn chế: Ngăn hoàn toàn không cho ứng dụng chạy trong nền. Ứng dụng có thể không hoạt động như dự kiến.

Nếu một ứng dụng thể hiện một số hành vi xấu được mô tả trong Android vitals, thì hệ thống có thể nhắc người dùng hạn chế quyền truy cập của ứng dụng đó vào tài nguyên hệ thống.

Nếu nhận thấy một ứng dụng đang sử dụng quá nhiều tài nguyên, thì hệ thống sẽ thông báo cho người dùng và cho phép người dùng lựa chọn hạn chế các hành động của ứng dụng. Những hành vi có thể kích hoạt thông báo bao gồm:

  • Quá nhiều lần khoá chế độ thức: 1 phần khoá chế độ thức bị giữ trong một giờ khi màn hình tắt
  • Dịch vụ nền quá mức: Nếu ứng dụng nhắm đến các cấp độ API thấp hơn 26 và có quá nhiều dịch vụ nền

Các hạn chế chính xác do nhà sản xuất thiết bị quyết định. Ví dụ: trên các bản dựng AOSP chạy Android 9 (API cấp 28) trở lên, các ứng dụng chạy ở chế độ nền và có trạng thái "bị hạn chế" thì cũng sẽ bị giới hạn như sau:

  • Không thể khởi chạy dịch vụ trên nền trước
  • Các dịch vụ trên nền trước hiện có sẽ bị xoá khỏi nền trước
  • Chuông báo không được kích hoạt
  • Công việc không được thực thi

Ngoài ra, nếu một ứng dụng nhắm đến Android 13 (API cấp 33) trở lên và ở trạng thái "bị hạn chế", thì hệ thống sẽ không phân phối thông báo BOOT_COMPLETED hoặc thông báo LOCKED_BOOT_COMPLETED cho đến khi ứng dụng đó được khởi động vì lý do khác.

Các hạn chế cụ thể được liệt kê trong phần Các hạn chế khi quản lý nguồn pin.

Quy định hạn chế đối với việc nhận thông báo về hoạt động mạng

Các ứng dụng nhắm mục tiêu Android 7.0 (API cấp 24) không nhận được thông báo CONNECTIVITY_ACTION (nếu có đăng ký nhận các thông báo đó trong tệp kê khai), và những quy trình phụ thuộc vào thông báo này sẽ không bắt đầu. Điều này có thể gây ra vấn đề cho các ứng dụng muốn theo dõi những thay đổi mạng hoặc thực hiện hoạt động hàng loạt trên mạng khi thiết bị kết nối với một mạng không đo lượng dữ liệu. Đã có sẵn một số giải pháp để khắc phục vấn đề hạn chế này trong khung Android, nhưng việc chọn giải pháp phù hợp sẽ tuỳ thuộc vào việc bạn muốn ứng dụng của mình đạt được điều gì.

Lưu ý: BroadcastReceiver đã đăng ký với Context.registerReceiver() sẽ tiếp tục nhận được những thông báo này khi ứng dụng đang chạy.

Lên lịch cho lệnh sử dụng mạng trên các kết nối không đo lượng dữ liệu

Khi sử dụng lớp JobInfo.Builder để tạo đối tượng JobInfo, hãy áp dụng phương thức setRequiredNetworkType() và truyền JobInfo.NETWORK_TYPE_UNMETERED dưới dạng tham số công việc. Đoạn mã mẫu sau đây lên lịch chạy một dịch vụ khi thiết bị kết nối với một mạng không đo lượng dữ liệu và đang sạc:

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);
}

Khi các điều kiện cho công việc của bạn được đáp ứng, ứng dụng của bạn sẽ nhận được lệnh gọi lại để chạy phương thức onStartJob() trong JobService.class được chỉ định. Để xem thêm ví dụ về cách triển khai JobScheduler, vui lòng xem ứng dụng mẫu JobScheduler.

Lựa chọn thay thế mới cho Job Scheduler là WorkManager, một API cho phép bạn lên lịch các tác vụ nền cần hoàn thành, bất kể quy trình ứng dụng có xoay quanh hay không. WorkManager chọn cách phù hợp để chạy tác vụ (có thể trực tiếp trên một luồng trong quy trình ứng dụng, cũng như sử dụng JobScheduler, FirebaseJobDispatcher hoặc AlarmManager) dựa trên các yếu tố như cấp độ API của thiết bị. Ngoài ra, WorkManager không yêu cầu Dịch vụ Play và cung cấp một số tính năng nâng cao, chẳng hạn như nối chuỗi các tác vụ với nhau hoặc kiểm tra trạng thái của một tác vụ. Để tìm hiểu thêm, hãy xem WorkManager.

Giám sát khả năng kết nối mạng trong khi ứng dụng đang chạy

Các ứng dụng đang chạy vẫn có thể theo dõi CONNECTIVITY_CHANGE bằng một BroadcastReceiver đã đăng ký. Tuy nhiên, API ConnectivityManager cung cấp một phương thức mạnh mẽ hơn để chỉ yêu cầu gọi lại khi các điều kiện mạng đã chỉ định được đáp ứng.

Đối tượng NetworkRequest xác định các tham số của lệnh gọi lại mạng về phương diện NetworkCapabilities. Bạn sẽ tạo các đối tượng NetworkRequest bằng lớp NetworkRequest.Builder. registerNetworkCallback() sau đó chuyển đối tượng NetworkRequest đến hệ thống. Khi đã đáp ứng được các điều kiện mạng, ứng dụng sẽ nhận được lệnh gọi lại để thực thi phương thức onAvailable() được xác định trong lớp ConnectivityManager.NetworkCallback.

Ứng dụng sẽ tiếp tục nhận được lệnh gọi lại cho đến khi ứng dụng thoát hoặc gọi unregisterNetworkCallback().

Quy định hạn chế đối với việc nhận thông báo về hình ảnh và video

Trong Android 7.0 (API cấp 24), các ứng dụng không thể gửi hoặc nhận các thông báo ACTION_NEW_PICTURE hoặc ACTION_NEW_VIDEO. Quy định hạn chế này giúp giảm bớt hiệu quả hoạt động và trải nghiệm người dùng khi một số ứng dụng phải đánh thức để xử lý hình ảnh hoặc video mới. Android 7.0 (API cấp 24) mở rộng JobInfoJobParameters để cung cấp một giải pháp thay thế.

Kích hoạt công việc khi có thay đổi đối với URI nội dung

Để kích hoạt việc làm thay đổi trên URI nội dung, Android 7.0 (API cấp 24) mở rộng API JobInfo bằng các phương thức sau:

JobInfo.TriggerContentUri()
Bao gồm các thông số bắt buộc để kích hoạt việc làm thay đổi URI nội dung.
JobInfo.Builder.addTriggerContentUri()
Truyền một đối tượng TriggerContentUri đến JobInfo. ContentObserver giám sát URI nội dung được đóng gói. Nếu có nhiều đối tượng TriggerContentUri được liên kết với một công việc, hệ thống sẽ cung cấp một lệnh gọi lại ngay cả khi hệ thống chỉ báo cáo một thay đổi ở một trong các URI nội dung.
Thêm cờ TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS để kích hoạt công việc nếu có bất kỳ thành phần con nào của thay đổi URI đã cung cấp. Cờ này tương ứng với thông số notifyForDescendants được chuyển đến registerContentObserver().

Lưu ý: Bạn không thể sử dụng kết hợp TriggerContentUri() với setPeriodic() hoặc setPersisted(). Để liên tục theo dõi những thay đổi đối với nội dung, hãy lên lịch JobInfo mới trước khi JobService của ứng dụng xử lý xong lệnh gọi lại gần đây nhất.

Mã mẫu sau đây lên lịch tải một công việc khi hệ thống báo cáo một thay đổi đối với URI nội dung, 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());
}

Khi hệ thống báo cáo thay đổi về (các) URI nội dung được chỉ định, ứng dụng của bạn sẽ nhận được lệnh gọi lại và đối tượng JobParameters sẽ được chuyển đến phương thức onStartJob() trong MediaContentJob.class.

Xác định tổ chức phát hành nội dung nào đã kích hoạt một công việc

Android 7.0 (API cấp 24) cũng mở rộng JobParameters để cho phép ứng dụng của bạn nhận thông tin hữu ích về những tổ chức phát hành nội dung và URI nào đã kích hoạt công việc này:

Uri[] getTriggeredContentUris()
Trả về một mảng URI đã kích hoạt công việc. Đây sẽ là null nếu không có URI nào kích hoạt lệnh (chẳng hạn như lệnh đã được kích hoạt do có thời hạn hoặc vì một số lý do khác), hoặc số URI thay đổi lớn hơn 50.
String[] getTriggeredContentAuthorities()
Trả về một mảng chuỗi của tổ chức phát hành nội dung đã kích hoạt việc làm. Nếu mảng được trả về không phải là null, hãy dùng getTriggeredContentUris() để truy xuất thông tin chi tiết về các URI đã thay đổi.

Mã mẫu sau đây ghi đè phương thức JobService.onStartJob() và ghi lại các tổ chức phát hành nội dung và URI đã kích hoạt lệnh này:

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;
}

Tối ưu hoá hơn nữa ứng dụng của bạn

Việc tối ưu hoá ứng dụng để chạy trên các thiết bị có bộ nhớ thấp hoặc trong điều kiện bộ nhớ thấp có thể giúp cải thiện hiệu suất và trải nghiệm người dùng. Việc xoá các phần phụ thuộc trên dịch vụ nền và thông báo truyền phát ngầm đã đăng ký tệp kê khai có thể giúp ứng dụng của bạn chạy tốt hơn trên các thiết bị như vậy. Mặc dù Android 7.0 (API cấp 24) có các bước để giảm một vài vấn đề trong số này, nhưng bạn nên tối ưu hoá ứng dụng để chạy mà không cần sử dụng toàn bộ các quy trình trong nền này.

Các lệnh Cầu gỡ lỗi Android (ADB) sau đây có thể giúp bạn kiểm thử hành vi của ứng dụng khi quy trình trong nền bị tắt:

  • Để mô phỏng các điều kiện mà không có thông báo truyền phát ngầm cũng như dịch vụ nền, hãy nhập lệnh sau:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • Để bật lại các thông báo truyền phát ngầm và dịch vụ nền, hãy nhập lệnh sau:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • Bạn có thể mô phỏng việc người dùng đặt ứng dụng của bạn ở trạng thái "bị hạn chế" về mức sử dụng pin trong chế độ nền. Chế độ cài đặt này sẽ không cho ứng dụng của bạn chạy trong nền. Để thực hiện điều này, hãy chạy lệnh sau trong cửa sổ dòng lệnh:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny