Tổng quan về dịch vụ

Service là một thành phần ứng dụng có thể thực hiện các thao tác diễn ra trong thời gian dài ở chế độ nền. Lớp này không cung cấp giao diện người dùng. Sau khi bắt đầu, dịch vụ có thể tiếp tục chạy trong một thời gian, ngay cả sau khi người dùng chuyển sang một ứng dụng khác. Ngoài ra, một thành phần có thể liên kết với một dịch vụ để tương tác với thành phần đó và thậm chí là thực hiện hoạt động giao tiếp liên quy trình (IPC). Ví dụ: một dịch vụ có thể xử lý các giao dịch mạng, phát nhạc, thực hiện I/O tệp hoặc tương tác với nhà cung cấp nội dung, tất cả ở chế độ nền.

Thận trọng: Dịch vụ chạy trong luồng chính của quy trình lưu trữ; dịch vụ đó không tạo luồng riêng và không chạy trong một quy trình riêng trừ phi bạn chỉ định khác. Bạn nên chạy mọi thao tác chặn trên một luồng riêng trong dịch vụ để tránh lỗi Ứng dụng không phản hồi (ANR).

Các loại dịch vụ

Sau đây là 3 loại dịch vụ:

Nền trước

Dịch vụ trên nền trước thực hiện một số thao tác mà người dùng có thể nhận thấy. Ví dụ: ứng dụng âm thanh sẽ dùng dịch vụ trên nền trước để phát bản âm thanh. Dịch vụ trên nền trước phải hiển thị một Thông báo. Các dịch vụ trên nền trước sẽ tiếp tục chạy ngay cả khi người dùng không tương tác với ứng dụng.

Khi sử dụng dịch vụ trên nền trước, bạn phải hiển thị thông báo để người dùng chủ động biết được dịch vụ đó đang chạy. Không thể đóng thông báo này trừ phi dịch vụ bị dừng hoặc bị xoá khỏi nền trước.

Tìm hiểu thêm về cách định cấu hình dịch vụ trên nền trước trong ứng dụng của bạn.

Lưu ý: API WorkManager mang đến một cách linh hoạt để lên lịch tác vụ và có thể chạy các công việc này dưới dạng dịch vụ trên nền trước nếu cần. Trong nhiều trường hợp, bạn nên sử dụng trực tiếp dịch vụ trên nền trước bằng WorkManager.

Thông tin khái quát
Dịch vụ nền thực hiện một thao tác mà người dùng không trực tiếp chú ý. Ví dụ: nếu một ứng dụng sử dụng một dịch vụ để thu gọn bộ nhớ, thì đó thường sẽ là dịch vụ nền.

Lưu ý: Nếu ứng dụng của bạn nhắm đến API cấp 26 trở lên, thì hệ thống sẽ áp dụng các hạn chế đối với việc chạy dịch vụ nền khi ứng dụng đó không ở nền trước. Ví dụ: trong hầu hết các trường hợp, bạn không nên truy cập vào thông tin vị trí ở chế độ nền. Thay vào đó, hãy lên lịch cho các tác vụ bằng WorkManager.

Đã ràng buộc
Dịch vụ được liên kết khi một thành phần ứng dụng liên kết với dịch vụ đó bằng cách gọi bindService(). Dịch vụ ràng buộc cung cấp giao diện máy khách-máy chủ cho phép các thành phần tương tác với dịch vụ, gửi yêu cầu, nhận kết quả và thậm chí là thực hiện việc này giữa các quy trình có giao tiếp liên quy trình (IPC). Dịch vụ ràng buộc chỉ chạy khi một thành phần khác của ứng dụng được liên kết với dịch vụ đó. Nhiều thành phần có thể liên kết với dịch vụ cùng một lúc, nhưng khi tất cả các thành phần đó huỷ liên kết, dịch vụ sẽ bị huỷ bỏ.

Mặc dù tài liệu này thường thảo luận riêng biệt về các dịch vụ bắt đầu và liên kết, nhưng dịch vụ của bạn có thể hoạt động theo cả hai cách – có thể bắt đầu (chạy vô thời hạn) và cũng cho phép liên kết. Chỉ đơn giản là bạn có triển khai một vài phương thức gọi lại: onStartCommand() để cho phép các thành phần bắt đầu phương thức này và onBind() để cho phép liên kết.

Bất kể dịch vụ được bắt đầu, liên kết hay cả hai, mọi thành phần của ứng dụng đều có thể sử dụng dịch vụ (ngay cả từ một ứng dụng riêng biệt) theo cách mà mọi thành phần đều có thể sử dụng một hoạt động, bằng cách bắt đầu bằng một Intent. Tuy nhiên, bạn có thể khai báo dịch vụ là riêng tư trong tệp kê khai và chặn quyền truy cập của các ứng dụng khác. Vấn đề này được thảo luận thêm trong phần Khai báo dịch vụ trong tệp kê khai.

Chọn giữa một dịch vụ và một chuỗi

Dịch vụ chỉ đơn giản là một thành phần có thể chạy ở chế độ nền, ngay cả khi người dùng không tương tác với ứng dụng. Vì vậy, bạn chỉ nên tạo dịch vụ nếu đó là điều bạn cần.

Nếu phải thực hiện tác vụ bên ngoài luồng chính, nhưng chỉ trong khi người dùng tương tác với ứng dụng, thì bạn nên tạo một luồng mới trong bối cảnh của một thành phần khác của ứng dụng. Ví dụ: nếu muốn phát một số bản nhạc, nhưng chỉ trong khi hoạt động đang chạy, bạn có thể tạo một luồng trong onCreate(), bắt đầu chạy luồng đó trong onStart() và dừng luồng đó trong onStop(). Ngoài ra, hãy cân nhắc sử dụng nhóm luồng và executor từ gói java.util.concurrent hoặc Coroutine Kotlin thay vì lớp Thread truyền thống. Vui lòng xem tài liệu về Tạo luồng trên Android để biết thêm thông tin về cách di chuyển quá trình thực thi sang luồng trong nền.

Hãy nhớ rằng nếu bạn sử dụng một dịch vụ, thì theo mặc định, dịch vụ đó vẫn chạy trong luồng chính của ứng dụng. Vì vậy, bạn vẫn nên tạo một luồng mới trong dịch vụ nếu dịch vụ này thực hiện các thao tác chặn hoặc thao tác nâng cao.

Thông tin cơ bản

Để tạo một dịch vụ, bạn phải tạo một lớp con của Service hoặc sử dụng một trong các lớp con hiện có của dịch vụ đó. Trong quá trình triển khai, bạn phải ghi đè một số phương thức gọi lại xử lý các khía cạnh chính của vòng đời dịch vụ và cung cấp cơ chế cho phép các thành phần liên kết với dịch vụ nếu thích hợp. Dưới đây là các phương thức gọi lại quan trọng nhất mà bạn nên ghi đè:

onStartCommand()
Hệ thống gọi phương thức này bằng cách gọi startService() khi một thành phần khác (chẳng hạn như hoạt động) yêu cầu bắt đầu dịch vụ. Khi phương thức này thực thi, dịch vụ sẽ bắt đầu và có thể chạy vô thời hạn ở chế độ nền. Nếu triển khai việc này, bạn có trách nhiệm dừng dịch vụ khi hoàn tất công việc bằng cách gọi stopSelf() hoặc stopService(). Nếu chỉ muốn cung cấp liên kết, bạn không cần phải triển khai phương thức này.
onBind()
Hệ thống gọi phương thức này bằng cách gọi bindService() khi một thành phần khác muốn liên kết với dịch vụ (chẳng hạn như để thực hiện RPC). Trong quá trình triển khai phương thức này, bạn phải cung cấp một giao diện mà ứng dụng dùng để giao tiếp với dịch vụ bằng cách trả về một IBinder. Bạn phải luôn triển khai phương thức này; tuy nhiên, nếu không muốn cho phép liên kết, bạn nên trả về giá trị rỗng.
onCreate()
Hệ thống sẽ gọi phương thức này để thực hiện các quy trình thiết lập một lần khi dịch vụ được tạo ban đầu (trước khi gọi onStartCommand() hoặc onBind()). Nếu dịch vụ đang chạy, phương thức này sẽ không được gọi.
onDestroy()
Hệ thống sẽ gọi phương thức này khi dịch vụ không còn được sử dụng và đang bị huỷ bỏ. Dịch vụ của bạn nên triển khai việc này để dọn dẹp mọi tài nguyên như luồng, trình nghe đã đăng ký hoặc dịch vụ nhận. Đây là cuộc gọi cuối cùng mà dịch vụ nhận được.

Nếu một thành phần khởi động dịch vụ bằng cách gọi startService() (dẫn đến lệnh gọi đến onStartCommand()), thì dịch vụ đó sẽ tiếp tục chạy cho đến khi tự ngừng sử dụng stopSelf() hoặc một thành phần khác dừng dịch vụ bằng cách gọi stopService().

Nếu một thành phần gọi bindService() để tạo dịch vụ và onStartCommand() không được gọi, thì dịch vụ chỉ chạy khi thành phần đó được liên kết với dịch vụ đó. Sau khi dịch vụ bị huỷ liên kết khỏi tất cả các ứng dụng khách, hệ thống sẽ huỷ liên kết đó.

Hệ thống Android chỉ dừng một dịch vụ khi bộ nhớ sắp hết và phải khôi phục tài nguyên hệ thống cho hoạt động tập trung vào người dùng. Nếu dịch vụ được liên kết với một hoạt động tập trung vào người dùng, thì dịch vụ đó ít có khả năng bị tắt. Nếu dịch vụ được khai báo là chạy ở nền trước, thì dịch vụ đó hiếm khi bị tắt. Nếu dịch vụ được khởi động và chạy trong thời gian dài, hệ thống sẽ giảm vị trí của nó trong danh sách tác vụ ở chế độ nền theo thời gian, và dịch vụ đó rất dễ bị tắt. Nếu dịch vụ được khởi động, bạn phải thiết kế dịch vụ để xử lý dễ dàng các tác vụ khởi động lại do hệ thống khởi động. Nếu hệ thống tắt dịch vụ, thì sẽ khởi động lại ngay khi tài nguyên có sẵn, nhưng điều này cũng phụ thuộc vào giá trị bạn trả về từ onStartCommand(). Để biết thêm thông tin về thời điểm hệ thống có thể huỷ bỏ một dịch vụ, hãy xem tài liệu về Quy trình và luồng.

Trong những phần sau, bạn sẽ tìm hiểu cách tạo các phương thức dịch vụ startService()bindService(), cũng như cách sử dụng các phương thức đó trong các thành phần khác của ứng dụng.

Khai báo một dịch vụ trong tệp kê khai

Bạn phải khai báo tất cả các dịch vụ trong tệp kê khai của ứng dụng, giống như cách bạn thực hiện với các hoạt động và các thành phần khác.

Để khai báo dịch vụ, hãy thêm phần tử <service> làm phần tử con của phần tử <application>. Dưới đây là ví dụ:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

Hãy xem tài liệu tham khảo phần tử <service> để biết thêm thông tin về cách khai báo dịch vụ trong tệp kê khai.

Có nhiều thuộc tính khác mà bạn có thể đưa vào phần tử <service> để xác định các thuộc tính, chẳng hạn như các quyền cần thiết để bắt đầu dịch vụ và quy trình mà dịch vụ sẽ chạy. Thuộc tính android:name là thuộc tính bắt buộc duy nhất. Thuộc tính này chỉ định tên lớp của dịch vụ. Sau khi bạn phát hành ứng dụng, hãy giữ nguyên tên này để tránh nguy cơ bị lỗi mã do phụ thuộc vào ý định tường minh để bắt đầu hoặc liên kết dịch vụ (đọc bài đăng trên blog Những điều không thể thay đổi).

Thận trọng: Để đảm bảo ứng dụng an toàn, hãy luôn sử dụng ý định tường minh khi khởi động Service và không khai báo bộ lọc ý định cho dịch vụ. Việc sử dụng ý định ngầm ẩn để bắt đầu một dịch vụ sẽ gây ra mối nguy hiểm về bảo mật vì bạn không thể chắc chắn dịch vụ đó sẽ phản hồi ý định và người dùng không thể biết dịch vụ nào sẽ bắt đầu. Kể từ Android 5.0 (API cấp 21), hệ thống sẽ đưa ra ngoại lệ nếu bạn gọi bindService() với ý định ngầm ẩn.

Bạn có thể đảm bảo rằng dịch vụ của mình chỉ được cung cấp cho ứng dụng của bạn bằng cách thêm thuộc tính android:exported và đặt thuộc tính này thành false. Điều này sẽ ngăn các ứng dụng khác bắt đầu dịch vụ của bạn một cách hiệu quả, ngay cả khi sử dụng một ý định tường minh.

Lưu ý: Người dùng có thể xem dịch vụ nào đang chạy trên thiết bị của mình. Nếu thấy một dịch vụ mà họ không nhận ra hoặc không tin tưởng, họ có thể ngừng dịch vụ đó. Để tránh việc người dùng vô tình dừng dịch vụ của bạn, bạn cần thêm thuộc tính android:description vào phần tử <service> trong tệp kê khai ứng dụng. Trong phần mô tả, hãy cung cấp một câu ngắn giải thích chức năng và lợi ích của dịch vụ.

Tạo một dịch vụ đã bắt đầu

Dịch vụ đã bắt đầu là dịch vụ mà một thành phần khác bắt đầu bằng cách gọi startService(), dẫn đến lệnh gọi đến phương thức onStartCommand() của dịch vụ đó.

Khi một dịch vụ được bắt đầu, dịch vụ đó có vòng đời độc lập với thành phần đã bắt đầu dịch vụ đó. Dịch vụ có thể chạy ở chế độ nền vô thời hạn, ngay cả khi thành phần bắt đầu dịch vụ bị huỷ. Do đó, dịch vụ sẽ tự dừng khi tác vụ hoàn tất bằng cách gọi stopSelf(), hoặc một thành phần khác có thể dừng bằng cách gọi stopService().

Một thành phần ứng dụng như hoạt động có thể bắt đầu dịch vụ bằng cách gọi startService() và truyền Intent chỉ định dịch vụ và bao gồm mọi dữ liệu để dịch vụ sử dụng. Dịch vụ sẽ nhận được Intent này trong phương thức onStartCommand().

Ví dụ: giả sử một hoạt động cần lưu một số dữ liệu vào cơ sở dữ liệu trực tuyến. Hoạt động có thể bắt đầu một dịch vụ đồng hành và phân phối dữ liệu đó để lưu bằng cách truyền ý định đến startService(). Dịch vụ nhận ý định trong onStartCommand(), kết nối với Internet và thực hiện giao dịch cơ sở dữ liệu. Khi giao dịch hoàn tất, dịch vụ sẽ tự dừng và bị huỷ.

Thận trọng: Dịch vụ chạy theo cùng quy trình với ứng dụng mà dịch vụ này được khai báo và chạy trong luồng chính của ứng dụng đó theo mặc định. Nếu dịch vụ của bạn thực hiện các thao tác chặn hoặc thao tác chuyên sâu trong khi người dùng tương tác với một hoạt động từ cùng một ứng dụng, thì dịch vụ đó sẽ làm chậm hiệu suất hoạt động. Để tránh ảnh hưởng đến hiệu suất của ứng dụng, hãy bắt đầu một luồng mới trong dịch vụ.

Lớp Service là lớp cơ sở cho tất cả dịch vụ. Khi mở rộng lớp này, bạn cần phải tạo một luồng mới để dịch vụ có thể hoàn tất mọi công việc; theo mặc định, dịch vụ này sẽ sử dụng luồng chính của ứng dụng và điều này có thể làm chậm hiệu suất của mọi hoạt động mà ứng dụng đang chạy.

Khung Android cũng cung cấp lớp con IntentService của Service, sử dụng một luồng worker để xử lý từng yêu cầu bắt đầu một. Bạn không nên sử dụng lớp này cho các ứng dụng mới vì lớp này sẽ không hoạt động tốt kể từ Android 8 Oreo, do có Giới hạn thực thi trong nền. Hơn nữa, kể từ Android 11, API này sẽ không còn được dùng nữa. Bạn có thể sử dụng JobIntentService để thay thế cho IntentService tương thích với các phiên bản Android mới.

Các phần sau đây mô tả cách bạn có thể triển khai dịch vụ tuỳ chỉnh của riêng mình. Tuy nhiên, bạn nên cân nhắc việc sử dụng WorkManager cho hầu hết các trường hợp sử dụng. Hãy tham khảo hướng dẫn về tính năng xử lý nền trên Android để xem có giải pháp nào phù hợp với nhu cầu của bạn hay không.

Mở rộng lớp Dịch vụ

Bạn có thể mở rộng lớp Service để xử lý từng ý định đến. Dưới đây là ví dụ về cách triển khai cơ bản:

Kotlin

class HelloService : Service() {

    private var serviceLooper: Looper? = null
    private var serviceHandler: ServiceHandler? = null

    // Handler that receives messages from the thread
    private inner class ServiceHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            // Normally we would do some work here, like download a file.
            // For our sample, we just sleep for 5 seconds.
            try {
                Thread.sleep(5000)
            } catch (e: InterruptedException) {
                // Restore interrupt status.
                Thread.currentThread().interrupt()
            }

            // Stop the service using the startId, so that we don't stop
            // the service in the middle of handling another job
            stopSelf(msg.arg1)
        }
    }

    override fun onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
            start()

            // Get the HandlerThread's Looper and use it for our Handler
            serviceLooper = looper
            serviceHandler = ServiceHandler(looper)
        }
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()

        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        serviceHandler?.obtainMessage()?.also { msg ->
            msg.arg1 = startId
            serviceHandler?.sendMessage(msg)
        }

        // If we get killed, after returning from here, restart
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        // We don't provide binding, so return null
        return null
    }

    override fun onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
    }
}

Java

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work doesn't disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

Mã ví dụ xử lý tất cả lệnh gọi đến trong onStartCommand() và đăng tác vụ lên Handler chạy trên luồng trong nền. Lớp này hoạt động giống như IntentService và xử lý lần lượt tất cả yêu cầu theo tuần tự. Chẳng hạn, bạn có thể thay đổi mã để chạy công việc trên nhóm luồng, chẳng hạn như nếu bạn muốn chạy nhiều yêu cầu cùng lúc.

Lưu ý rằng phương thức onStartCommand() phải trả về một số nguyên. Số nguyên là một giá trị mô tả cách hệ thống sẽ tiếp tục dịch vụ trong trường hợp hệ thống tắt dịch vụ đó. Giá trị trả về từ onStartCommand() phải là một trong các hằng số sau:

START_NOT_STICKY
Nếu hệ thống dừng dịch vụ sau khi onStartCommand() trả về, không tạo lại dịch vụ trừ phi có ý định phân phối đang chờ xử lý. Đây là phương án an toàn nhất để tránh chạy dịch vụ khi không cần thiết và khi ứng dụng có thể đơn giản là khởi động lại mọi công việc chưa hoàn tất.
START_STICKY
Nếu hệ thống ngừng dịch vụ sau khi onStartCommand() trả về, hãy tạo lại dịch vụ và gọi onStartCommand(), nhưng không phân phối lại ý định cuối cùng. Thay vào đó, hệ thống gọi onStartCommand() với ý định rỗng trừ phi có các ý định đang chờ xử lý để bắt đầu dịch vụ. Trong trường hợp đó, các ý định đó sẽ được phân phối. Chế độ này phù hợp với những trình phát nội dung đa phương tiện (hoặc các dịch vụ tương tự) không thực thi lệnh nhưng chạy vô thời hạn và đang chờ một công việc.
START_REDELIVER_INTENT
Nếu hệ thống loại bỏ dịch vụ sau khi onStartCommand() trả về, hãy tạo lại dịch vụ và gọi onStartCommand() với ý định cuối cùng được phân phối đến dịch vụ. Mọi ý định đang chờ xử lý sẽ được phân phối lần lượt. Điều này phù hợp với các dịch vụ đang tích cực thực hiện công việc cần được tiếp tục ngay lập tức, chẳng hạn như tải tệp xuống.

Để biết thêm thông tin chi tiết về các giá trị trả về này, hãy xem tài liệu tham khảo được liên kết cho từng hằng số.

Bắt đầu dịch vụ

Bạn có thể bắt đầu dịch vụ từ một hoạt động hoặc thành phần khác của ứng dụng bằng cách truyền Intent đến startService() hoặc startForegroundService(). Hệ thống Android gọi phương thức onStartCommand() của dịch vụ rồi truyền phương thức đó vào Intent. Phương thức này chỉ định dịch vụ nào cần bắt đầu.

Lưu ý: Nếu ứng dụng của bạn nhắm đến API cấp 26 trở lên, thì hệ thống sẽ áp dụng các hạn chế đối với việc sử dụng hoặc tạo dịch vụ nền, trừ phi ứng dụng đó chạy ở nền trước. Nếu cần tạo một dịch vụ trên nền trước, thì ứng dụng đó phải gọi startForegroundService(). Phương thức đó tạo một dịch vụ nền, nhưng sẽ báo hiệu cho hệ thống rằng dịch vụ đó sẽ tự quảng bá lên nền trước. Sau khi tạo, dịch vụ phải gọi phương thức startForeground() trong vòng 5 giây.

Ví dụ: một hoạt động có thể bắt đầu dịch vụ mẫu trong phần trước (HelloService) bằng cách sử dụng ý định tường minh với startService(), như minh hoạ dưới đây:

Kotlin

startService(Intent(this, HelloService::class.java))

Java

startService(new Intent(this, HelloService.class));

Phương thức startService() sẽ trả về ngay lập tức và hệ thống Android gọi phương thức onStartCommand() của dịch vụ. Nếu dịch vụ chưa chạy, trước tiên, hệ thống sẽ gọi onCreate(), sau đó sẽ gọi onStartCommand().

Nếu dịch vụ cũng không cung cấp tính năng liên kết, thì ý định được phân phối bằng startService() là chế độ giao tiếp duy nhất giữa thành phần ứng dụng và dịch vụ. Tuy nhiên, nếu bạn muốn dịch vụ gửi lại kết quả, thì ứng dụng bắt đầu dịch vụ có thể tạo PendingIntent cho một thông báo truyền tin (bằng getBroadcast()) và gửi thông báo đó đến dịch vụ trong Intent khởi động dịch vụ. Sau đó, dịch vụ có thể sử dụng thông báo truyền tin để phân phối kết quả.

Nhiều yêu cầu để bắt đầu dịch vụ sẽ dẫn đến nhiều lệnh gọi tương ứng đến onStartCommand() của dịch vụ. Tuy nhiên, bạn chỉ cần gửi một yêu cầu dừng dịch vụ (bằng stopSelf() hoặc stopService()) để dừng dịch vụ đó.

Dừng một dịch vụ

Dịch vụ đã bắt đầu phải tự quản lý vòng đời của chính nó. Nghĩa là, hệ thống không dừng hoặc huỷ dịch vụ trừ phi phải khôi phục bộ nhớ hệ thống và dịch vụ đó sẽ tiếp tục chạy sau khi onStartCommand() trả về. Dịch vụ phải tự dừng bằng cách gọi stopSelf(), hoặc một thành phần khác có thể dừng dịch vụ này bằng cách gọi stopService().

Sau khi được yêu cầu dừng bằng stopSelf() hoặc stopService(), hệ thống sẽ huỷ dịch vụ ngay khi có thể.

Nếu dịch vụ của bạn xử lý đồng thời nhiều yêu cầu đến onStartCommand(), thì bạn không nên dừng dịch vụ khi xử lý xong yêu cầu bắt đầu, vì có thể bạn đã nhận được một yêu cầu bắt đầu mới (việc dừng ở cuối yêu cầu đầu tiên sẽ chấm dứt yêu cầu thứ hai). Để tránh sự cố này, bạn có thể sử dụng stopSelf(int) để đảm bảo rằng yêu cầu dừng dịch vụ luôn dựa trên yêu cầu bắt đầu gần đây nhất. Tức là khi gọi stopSelf(int), bạn sẽ truyền mã của yêu cầu bắt đầu (startId được phân phối đến onStartCommand()) tương ứng với yêu cầu dừng của bạn. Sau đó, nếu dịch vụ nhận được một yêu cầu bắt đầu mới trước khi bạn có thể gọi stopSelf(int), thì mã nhận dạng sẽ không khớp và dịch vụ sẽ không dừng lại.

Thận trọng: Để tránh lãng phí tài nguyên hệ thống và tiêu thụ năng lượng pin, hãy đảm bảo ứng dụng của bạn dừng dịch vụ khi ứng dụng hoạt động xong. Nếu cần, các thành phần khác có thể dừng dịch vụ bằng cách gọi stopService(). Ngay cả khi bật tính năng liên kết cho dịch vụ, bạn vẫn phải luôn tự dừng dịch vụ nếu nhận được lệnh gọi đến onStartCommand().

Để biết thêm thông tin về vòng đời của một dịch vụ, hãy xem phần Quản lý vòng đời của một dịch vụ.

Tạo dịch vụ ràng buộc

Dịch vụ ràng buộc là dịch vụ cho phép các thành phần của ứng dụng liên kết với dịch vụ đó bằng cách gọi bindService() để tạo một kết nối lâu dài. API này thường không cho phép các thành phần khởi động bằng cách gọi startService().

Tạo dịch vụ ràng buộc khi bạn muốn tương tác với dịch vụ từ các hoạt động và thành phần khác trong ứng dụng hoặc để hiển thị một số chức năng của ứng dụng cho các ứng dụng khác thông qua giao tiếp liên quy trình (IPC).

Để tạo một dịch vụ ràng buộc, hãy triển khai phương thức gọi lại onBind() để trả về một IBinder xác định giao diện để giao tiếp với dịch vụ. Sau đó, các thành phần khác của ứng dụng có thể gọi bindService() để truy xuất giao diện và bắt đầu gọi các phương thức trên dịch vụ. Dịch vụ chỉ hoạt động để phân phát thành phần ứng dụng liên kết với dịch vụ đó, vì vậy, khi không có thành phần nào liên kết với dịch vụ, hệ thống sẽ huỷ bỏ thành phần đó. Bạn không cần dừng dịch vụ ràng buộc theo cách tương tự như cách bạn phải dừng khi dịch vụ bắt đầu thông qua onStartCommand().

Để tạo dịch vụ ràng buộc, bạn phải xác định giao diện chỉ định cách ứng dụng có thể giao tiếp với dịch vụ. Giao diện này giữa dịch vụ và ứng dụng phải là một phương thức triển khai của IBinder và là nội dung mà dịch vụ của bạn phải trả về từ phương thức gọi lại onBind(). Sau khi nhận được IBinder, ứng dụng khách có thể bắt đầu tương tác với dịch vụ thông qua giao diện đó.

Nhiều ứng dụng có thể liên kết với dịch vụ cùng lúc. Sau khi tương tác xong với dịch vụ, ứng dụng sẽ gọi unbindService() để huỷ liên kết. Khi không có ứng dụng nào được liên kết với dịch vụ, hệ thống sẽ huỷ bỏ dịch vụ.

Có nhiều cách để triển khai dịch vụ ràng buộc và việc triển khai sẽ phức tạp hơn so với dịch vụ bắt đầu. Vì những lý do này, nội dung thảo luận về dịch vụ ràng buộc sẽ xuất hiện trong một tài liệu riêng về Dịch vụ ràng buộc.

Gửi thông báo cho người dùng

Khi đang chạy, dịch vụ có thể thông báo cho người dùng về các sự kiện bằng cách sử dụng thông báo trên thanh thông báo nhanh hoặc thông báo trên thanh trạng thái.

Thông báo trên thanh thông báo nhanh là một thông báo chỉ xuất hiện trên bề mặt của cửa sổ hiện tại trong một khoảnh khắc trước khi biến mất. Thông báo trên thanh trạng thái cung cấp một biểu tượng trên thanh trạng thái kèm theo thông báo mà người dùng có thể chọn để thực hiện một hành động (chẳng hạn như bắt đầu một hoạt động).

Thông thường, thông báo trên thanh trạng thái là kỹ thuật tốt nhất nên sử dụng khi tác vụ trong nền, chẳng hạn như tải tệp xuống đã hoàn tất và người dùng giờ có thể thao tác với thao tác đó. Khi người dùng chọn thông báo từ khung hiển thị mở rộng, thông báo có thể bắt đầu một hoạt động (chẳng hạn như để hiển thị tệp đã tải xuống).

Quản lý vòng đời của một dịch vụ

Vòng đời của một dịch vụ đơn giản hơn nhiều so với vòng đời của một hoạt động. Tuy nhiên, bạn nên chú ý kỹ đến cách tạo và huỷ dịch vụ vì một dịch vụ có thể chạy ở chế độ nền mà người dùng không biết.

Vòng đời dịch vụ (từ khi được tạo đến khi bị huỷ) có thể tuân theo một trong hai đường dẫn sau:

  • Dịch vụ đã bắt đầu

    Dịch vụ được tạo khi một thành phần khác gọi startService(). Sau đó, dịch vụ sẽ chạy vô thời hạn và phải tự dừng bằng cách gọi stopSelf(). Một thành phần khác cũng có thể dừng dịch vụ bằng cách gọi stopService(). Khi dịch vụ bị dừng, hệ thống sẽ huỷ dịch vụ đó.

  • Dịch vụ ràng buộc

    Dịch vụ được tạo khi một thành phần khác (ứng dụng) gọi bindService(). Sau đó, ứng dụng sẽ giao tiếp với dịch vụ thông qua giao diện IBinder. Ứng dụng có thể đóng kết nối bằng cách gọi unbindService(). Nhiều ứng dụng có thể liên kết với cùng một dịch vụ và khi tất cả các ứng dụng đó huỷ liên kết, hệ thống sẽ huỷ bỏ dịch vụ đó. Dịch vụ không cần phải tự dừng.

Hai đường dẫn này không hoàn toàn tách biệt. Bạn có thể liên kết với một dịch vụ đã bắt đầu bằng startService(). Ví dụ: bạn có thể bắt đầu một dịch vụ phát nhạc nền bằng cách gọi startService() với Intent giúp xác định bản nhạc sẽ phát. Sau này, có thể là khi người dùng muốn thực hiện một số quyền kiểm soát đối với trình phát hoặc muốn nhận thông tin về bài hát hiện tại, một hoạt động có thể liên kết với dịch vụ bằng cách gọi bindService(). Trong những trường hợp như vậy, stopService() hoặc stopSelf() không thực sự dừng dịch vụ cho đến khi tất cả ứng dụng khách huỷ liên kết.

Triển khai các phương thức gọi lại trong vòng đời

Giống như một hoạt động, dịch vụ có các phương thức gọi lại trong vòng đời mà bạn có thể triển khai để theo dõi các thay đổi về trạng thái của dịch vụ và thực hiện công việc vào những thời điểm thích hợp. Dịch vụ khung sau đây minh hoạ từng phương thức vòng đời:

Kotlin

class ExampleService : Service() {
    private var startMode: Int = 0             // indicates how to behave if the service is killed
    private var binder: IBinder? = null        // interface for clients that bind
    private var allowRebind: Boolean = false   // indicates whether onRebind should be used

    override fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return startMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return allowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Java

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return startMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return binder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return allowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Lưu ý: Không giống như các phương thức gọi lại trong vòng đời hoạt động, bạn không bắt buộc phải gọi phương thức triển khai lớp cấp cao của các phương thức gọi lại này.

Hình 2. Vòng đời của dịch vụ. Sơ đồ bên trái cho thấy vòng đời khi dịch vụ được tạo bằng startService() và sơ đồ bên phải cho thấy vòng đời khi dịch vụ được tạo bằng bindService().

Hình 2 minh hoạ các phương thức gọi lại thông thường cho một dịch vụ. Mặc dù hình này tách biệt các dịch vụ do startService() tạo với những dịch vụ do bindService() tạo, nhưng hãy lưu ý rằng bất kỳ dịch vụ nào, bất kể khởi động như thế nào, đều có thể cho phép ứng dụng liên kết với dịch vụ đó. Dịch vụ ban đầu được bắt đầu bằng onStartCommand() (do ứng dụng gọi startService()) vẫn có thể nhận lệnh gọi đến onBind() (khi ứng dụng khách gọi bindService()).

Bằng cách triển khai các phương thức này, bạn có thể theo dõi 2 vòng lặp lồng nhau này trong vòng đời của dịch vụ:

  • Toàn bộ thời gian hoạt động của một dịch vụ diễn ra từ thời điểm onCreate() được gọi đến thời điểm onDestroy() trả về. Giống như một hoạt động, dịch vụ thiết lập ban đầu trong onCreate() và giải phóng tất cả tài nguyên còn lại trong onDestroy(). Ví dụ: dịch vụ phát nhạc có thể tạo chuỗi nơi nhạc được phát trong onCreate(), sau đó có thể dừng chuỗi đó trong onDestroy().

    Lưu ý: Các phương thức onCreate()onDestroy() được gọi cho tất cả các dịch vụ, cho dù những phương thức đó do startService() hay bindService() tạo.

  • Thời gian hoạt động của một dịch vụ bắt đầu bằng lệnh gọi đến onStartCommand() hoặc onBind(). Mỗi phương thức được chuyển giao Intent đã được truyền cho startService() hoặc bindService().

    Nếu dịch vụ được bắt đầu, thời gian hoạt động sẽ kết thúc cùng lúc với toàn bộ thời gian hoạt động kết thúc (dịch vụ vẫn hoạt động ngay cả sau khi onStartCommand() trả về). Nếu dịch vụ bị ràng buộc, thời gian hoạt động sẽ kết thúc khi onUnbind() trả về.

Lưu ý: Mặc dù dịch vụ đã bắt đầu bị dừng bằng lệnh gọi đến stopSelf() hoặc stopService(), nhưng không có lệnh gọi lại tương ứng nào cho dịch vụ đó (không có lệnh gọi lại onStop()). Trừ phi dịch vụ được liên kết với một ứng dụng, hệ thống sẽ huỷ bỏ dịch vụ đó khi ngừng dịch vụ – onDestroy() là lệnh gọi lại duy nhất nhận được.

Để biết thêm thông tin về cách tạo dịch vụ cung cấp tính năng liên kết, hãy xem tài liệu Dịch vụ ràng buộc để biết thêm thông tin về phương thức gọi lại onRebind() trong phần Quản lý vòng đời của dịch vụ ràng buộc.