lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Dịch vụ

Service là một thành phần ứng dụng có khả năng thực hiện các thao tác chạy kéo dài trong nền và không cung cấp giao diện người dùng. Một thành phần ứng dụng khác có thể bắt đầu một dịch vụ và nó sẽ tiếp tục chạy ngầm ngay cả 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ể gắn kết với một dịch vụ để tương tác với nó và thậm chí thực hiện truyền thông liên tiến 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 một trình cung cấp nội dung, tất cả đều xuất phát từ nền.

Về cơ bản, một dịch vụ có thể có hai dạng:

Được bắt đầu
Dịch vụ có dạng "được bắt đầu" khi một thành phần ứng dụng (chẳng hạn như một hoạt động) bắt đầu nó bằng cách gọi startService(). Sau khi được bắt đầu, dịch vụ có thể chạy ngầm vô thời hạn, ngay cả khi thành phần bắt đầu nó bị hủy. Thông thường, dịch vụ được bắt đầu sẽ thực hiện một thao tác đơn lẻ và không trả về kết quả cho hàm gọi. Ví dụ, nó có thể tải xuống hoặc tải lên một tệp thông qua mạng. Khi thao tác được hoàn thành, dịch vụ tự nó sẽ dừng lại.
Gắn kết
Dịch vụ có dạng "gắn kết" khi một thành phần ứng dụng gắn kết với nó bằng cách gọi bindService(). Dịch vụ gắn kết sẽ đưa ra một 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àm vậy thông qua truyền thông liên tiến trình (IPC). Dịch vụ gắn kết chỉ chạy trong khi một thành phần ứng dụng khác được gắn kết với nó. Nhiều thành phần có thể gắn kết cùng lúc với dịch vụ, nhưng khi tất cả bị bỏ gắn kết thì dịch vụ sẽ bị hủy.

Mặc dù tài liệu này thường đề cập tới hai loại dịch vụ riêng rẽ, dịch vụ của bạn có thể hoạt động theo cả hai cách—nó có thể được bắt đầu (để chạy vô thời hạn) và cũng cho phép gắn kết. Đó đơn giản là vấn đề bạn có triển khai một cặp phương pháp gọi lại hay không: onStartCommand() để cho phép thành phần bắt đầu nó và onBind() để cho phép nó gắn kết.

Không phụ thuộc vào việc ứng dụng của bạn được bắt đầu, gắn kết, hay cả hai, bất kỳ thành phần ứng dụng nào cũng có thể sử dụng dịch vụ (thậm chí từ một ứng dụng riêng biệt), giống như cách mà bất kỳ thành phần nào cũng có thể sử dụng một hoạt động—bằng cách bắt đầu nó 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 bản kê khai, và chặn truy cập từ các ứng dụng khác. Điều này được trình bày kỹ hơn trong phần về Khai báo dịch vụ trong bản kê khai.

Chú ý: Một dịch vụ chạy trong luồng chính của tiến trình lưu trữ của nó—dịch vụ không tạo luồng của chính nó và không chạy trong một tiến trình riêng biệt (trừ khi bạn quy định khác). Điều này có nghĩa là, nếu dịch vụ của bạn định thực hiện bất kỳ công việc nặng nào đối với CPU hay chặn các thao tác (chẳng hạn như phát lại MP3 hay kết nối mạng), bạn nên tạo một luồng mới bên trong dịch vụ để thực hiện công việc đó. Bằng cách sử dụng một luồng riêng biệt, bạn sẽ giảm rủi ro gặp lỗi Ứng dụng Không Hồi đáp (ANR) và luồng chính của ứng dụng có thể vẫn dành riêng cho tương tác giữa người dùng với các hoạt động của bạn.

Nội dung 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 một trong các lớp con hiện tại của nó). Trong triển khai của mình, bạn cần khống chế một số phương pháp gọi lại có chức năng xử lý những khía cạnh chính trong vòng đời của dịch vụ và cung cấp một cơ chế để các thành phần gắn kết với dịch vụ đó, nếu phù hợp. Những phương pháp gọi lại quan trọng nhất mà bạn nên khống chế là:

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

Nếu một thành phần bắt đầu dịch vụ bằng cách gọi startService() (kết quả là một lệnh gọi tới onStartCommand()), khi đó dịch vụ sẽ vẫn chạy tới khi tự nó dừng bằng stopSelf() hoặc một thành phần khác dừng nó 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), khi đó dịch vụ sẽ chỉ chạy khi nào mà thành phần đó còn gắn kết với nó. Sau khi dịch vụ được bỏ gắn kết khỏi tất cả máy khách, hệ thống sẽ hủy nó.

Hệ thống Android sẽ buộc dừng một dịch vụ chỉ khi bộ nhớ thấp và nó phải khôi phục tài nguyên của hệ thống cho hoạt động có tiêu điểm của người dùng. Nếu dịch vụ gắn kết với một hoạt động mà có tiêu điểm của người dùng, khi đó sẽ có ít khả năng nó sẽ bị tắt bỏ hơn, và nếu dịch vụ được khai báo là chạy trong tiền cảnh (đề cập sau), khi đó nó sẽ hầu như không bao giờ bị tắt bỏ. Mặt khác, nếu dịch vụ được bắt đầu và chạy trong thời gian dài, hệ thống sẽ hạ thấp vị trí của nó trong danh sách tác vụ chạy ngầm qua thời gian và dịch vụ sẽ rất có thể bị tắt bỏ—nếu dịch vụ của bạn được bắt đầu, khi đó bạn phải thiết kế nó để xử lý việc khởi động lại do hệ thống một cách uyển chuyển. Nếu hệ thống tắt bỏ dịch vụ của bạn, nó sẽ khởi động lại dịch vụ ngay khi tài nguyên có sẵn trở lại (mặc dù điều này cũng phụ thuộc vào giá trị mà bạn trả về từ onStartCommand(), vấn đề này sẽ được bàn sau). Để biết thêm thông tin về thời điểm mà hệ thống có thể hủy một dịch vụ, hãy xem tài liệu Tiến trình và Luồng .

Trong những phần sau, bạn sẽ thấy cách bạn có thể tạo từng loại dịch vụ và cách sử dụng nó từ các thành phần ứng dụng khác.

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

Giống như hoạt động (và các thành phần khác), bạn phải khai báo tất cả dịch vụ trong tệp bản kê khai của ứng dụng của mình.

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

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

Xem tham chiếu phần tử &lt;service&gt; để biết thêm thông tin về việc khai báo dịch vụ của bạn trong bản kê khai.

Có các thuộc tính khác mà bạn có thể bao gồm trong phần tử &lt;service&gt; để định nghĩa các tính chất chẳng hạn như những quyền cần để bắt đầu dịch vụ và tiến trình mà dịch vụ sẽ chạy trong đó. Thuộc tính android:name là thuộc tính bắt buộc duy nhất—nó quy định tên lớp của dịch vụ. Một khi bạn phát hành ứng dụng của mình, bạn không nên thay đổi tên này, vì nếu bạn làm vậy, bạn sẽ gặp rủi ro làm gãy mã do sự phụ thuộc vào các ý định biểu thị để bắt đầu hoặc gắn kết dịch vụ (đọc bài đăng blog, Những Điều Không Thay Đổi Được).

Để đảm bảo ứng dụng của bạn được bảo mật, luôn sử dụng một ý định biểu thị khi bắt đầu hoặc gắn kết Service của bạn và không được khai báo bộ lọc ý định cho dịch vụ. Nếu điều trọng yếu là bạn phải cho phép một chút không rõ ràng về dịch vụ nào sẽ bắt đầu, bạn có thể cung cấp bộ lọc ý định cho dịch vụ của mình và loại bỏ tên thành phần khỏi Intent, nhưng sau đó bạn có thể đặt gói cho ý định bằng setPackage(), điều này cung cấp sự không rõ ràng vừa đủ cho dịch vụ mục tiêu đó.

Ngoài ra, bạn có thể đảm bảo rằng dịch vụ của mình chỉ sẵn có cho ứng dụng của bạn bằng cách đưa vào thuộc tính android:exported và đặt nó thành "false". Điều này sẽ dừng việc các ứng dụng khác bắt đầu dịch vụ của bạn, ngay cả khi sử dụng một ý định biểu thị.

Tạo một Dịch vụ được Bắt đầu

Dịch vụ được 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(), kết quả là một lệnh gọi tới phương pháp onStartCommand() của dịch vụ.

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

Một thành phần ứng dụng chẳng hạn như một hoạt động có thể bắt đầu dịch vụ bằng cách gọi startService() và chuyển một Intent trong đó quy định dịch vụ và bao gồm bất kỳ dữ liệu nào để cho dịch vụ sử dụng. Dịch vụ sẽ nhận Intent này trong phương pháp 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à truyền cho nó dữ liệu để lưu bằng cách chuyển một ý định tới startService(). Dịch vụ sẽ nhận ý định trong onStartCommand(), kết nối với Internet và thực hiện giao tác cơ sở dữ liệu. Khi giao tác được thực hiện, dịch vụ sẽ tự dừng lại và nó bị hủy.

Chú ý: Một dịch vụ sẽ chạy trong cùng tiến trình như ứng dụng mà nó được khai báo trong đó và trong luồng chính của ứng dụng đó theo mặc định. Vì vậy, nếu dịch vụ của bạn thực hiện các thao tác tăng cường hoặc chặn trong khi người dùng tương tác với một hoạt động từ cùng ứng dụng, dịch vụ sẽ làm chậm hiệu năng của hoạt động. Để tránh tác động tới hiệu năng của ứng dụng, bạn nên bắt đầu một luồng mới bên trong dịch vụ.

Thông thường, có hai lớp mà bạn có thể mở rộng để tạo một dịch vụ được bắt đầu:

Service
Đây là lớp cơ bản cho tất cả dịch vụ. Khi bạn mở rộng lớp này, điều quan trọng là bạn tạo một luồng mới để thực hiện tất cả công việc của dịch vụ trong đó, do dịch vụ sử dụng luồng chính của ứng dụng của bạn, theo mặc định, điều này có thể làm chậm hiệu năng của bất kỳ hoạt động nào mà ứng dụng của bạn đang chạy.
IntentService
Đây là một lớp con của Service có chức năng sử dụng một luồng trình thực hiện để xử lý tất cả yêu cầu bắt đầu một cách lần lượt. Đây là lựa chọn tốt nhất nếu bạn không yêu cầu dịch vụ của mình xử lý đồng thời nhiều yêu cầu. Tất cả những gì bạn cần làm đó là triển khai onHandleIntent(), nó sẽ nhận ý định cho mỗi yêu cầu bắt đầu để bạn có thể thực hiện công việc chạy ngầm.

Các phần sau mô tả cách bạn có thể triển khai dịch vụ của mình bằng cách sử dụng một trong các cách cho những lớp này.

Mở rộng lớp IntentService

Vì phần lớn các dịch vụ được bắt đầu không cần xử lý nhiều yêu cầu một cách đồng thời (điều này thực sự có thể là một kịch bản tạo đa luồng nguy hiểm), có lẽ tốt nhất là nếu bạn triển khai dịch vụ của mình bằng cách sử dụng lớp IntentService.

IntentService làm điều sau đây:

  • Tạo một luồng trình thực hiện mặc định để thực thi tất cả ý định được chuyển tới onStartCommand() tách riêng với luồng chính của ứng dụng của bạn.
  • Tạo một hàng đợi công việc để chuyển lần lượt từng ý định tới triển khai onHandleIntent() của bạn, vì thế bạn không bao giờ phải lo lắng về vấn đề tạo đa luồng.
  • Dừng dịch vụ sau khi tất cả yêu cầu bắt đầu đều đã được xử lý, vì thế bạn không bao giờ phải gọi stopSelf().
  • Cung cấp triển khai mặc định của onBind() mà trả về rỗng.
  • Cung cấp triển khai mặc định của onStartCommand() mà gửi ý định tới hàng đợi công việc rồi tới triển khai onHandleIntent() của bạn.

Tất cả đều nói lên một thực tế rằng tất cả những việc bạn cần làm đó là triển khai onHandleIntent() để thực hiện công việc mà máy khách cung cấp. (Mặc dù bạn cũng cần cung cấp một hàm dựng nhỏ cho dịch vụ.)

Sau đây là ví dụ về triển khai IntentService:

public class HelloIntentService extends IntentService {

  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

Đó là tất cả những gì bạn cần: một hàm dựng và triển khai onHandleIntent().

Nếu bạn quyết định cũng khống chế các phương pháp gọi lại khác, chẳng hạn như onCreate(), onStartCommand(), hoặc onDestroy(), hãy nhớ gọi ra siêu triển khai, sao cho IntentService có thể xử lý hợp lý vòng đời của luồng trình thực hiện.

Ví dụ, onStartCommand() phải trả về triển khai mặc định (đó là cách mà ý định được chuyển tới onHandleIntent()):

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

Bên cạnh onHandleIntent(), phương pháp duy nhất mà từ đó bạn không cần gọi siêu lớp là onBind() (nhưng bạn chỉ cần triển khai điều đó nếu dịch vụ của bạn cho phép gắn kết).

Trong phần tiếp theo, bạn sẽ thấy cách mà cùng loại dịch vụ được triển khai khi mở rộng lớp Service cơ sở, nó có nhiều mã hơn nhưng có thể phù hợp nếu bạn cần xử lý các yêu cầu bắt đầu đồng thời.

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

Như bạn thấy trong phần trước, sử dụng IntentService giúp việc triển khai một dịch vụ được bắt đầu của bạn trở nên rất đơn giản. Tuy nhiên, nếu bạn cần dịch vụ của mình thực hiện tạo đa luồng (thay vì xử lý các yêu cầu bắt đầu thông qua một hàng đợi công việc), khi đó bạn có thể mở rộng lớp Service để xử lý từng ý định.

Để so sánh, đoạn mã mẫu sau là triển khai lớp Service mà thực hiện chính xác cùng công việc như ví dụ bên trên bằng cách sử dụng IntentService. Cụ thể, đối với mỗi yêu cầu bắt đầu, nó sẽ sử dụng một luồng trình thực hiện để thực hiện công việc và chỉ xử lý lần lượt từng yêu cầu một.

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // 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.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // 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 will not 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
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @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 = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.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();
  }
}

Như bạn có thể thấy, có nhiều việc hơn nhiều so với việc sử dụng IntentService.

Tuy nhiên, do bạn tự mình xử lý từng lệnh gọi đến onStartCommand(), bạn có thể thực hiện nhiều yêu cầu một cách đồng thời. Đó không phải là việc mà ví dụ này làm, nhưng nếu đó là việc bạn muốn, vậy bạn có thể tạo một luồng mới cho từng yêu cầu và ngay lập tức trả chúng về (thay vì đợi tới khi yêu cầu trước hoàn thành).

Để ý rằng phương pháp 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 nên tiếp tục dịch vụ trong trường hợp hệ thống tắt bỏ nó (như được đề cập ở trên, triển khai mặc định cho IntentService sẽ xử lý điều này cho bạn dù bạn có thể sửa đổi nó). 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 tắt bỏ dịch vụ sau khi onStartCommand() trả về, không được tạo lại dịch vụ đó, trừ khi có các ý định đang chờ để được chuyển. Đây là lựa chọn an toàn nhất để tránh chạy dịch vụ của bạn khi không cần thiết và khi ứng dụng của bạn có thể đơn thuần khởi động lại bất kỳ công việc chưa hoàn thành nào.
START_STICKY
Nếu hệ thống tắt bỏ 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 chuyển lại ý định cuối cùng. Thay vào đó, hệ thống sẽ gọi onStartCommand() bằng một ý định rỗng, trừ khi có các ý định đang chờ để bắt đầu dịch vụ, trong trường hợp đó, những ý định này sẽ được chuyển. Điều này phù hợp với các trình phát phương tiện (hoặc dịch vụ tương tự) mà không đang thực thi lệnh, nhưng đang chạy vô thời hạn và chờ một tác vụ.
START_REDELIVER_INTENT
Nếu hệ thống tắt bỏ dịch vụ sau khi onStartCommand() trả về, hãy tạo lại dịch vụ và gọi onStartCommand() bằng ý định cuối cùng được chuyển tới dịch vụ. Mọi ý định chờ đều được chuyển lần lượt. Điều này phù hợp với các dịch vụ đang chủ động thực hiện một công việc mà nên được tiếp tục ngay lập tức, chẳng hạn như tải xuống một tệp.

Để biết thêm chi tiết về những 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 một Dịch vụ

Bạn có thể bắt đầu một dịch vụ từ một hoạt động hoặc thành phần ứng dụng khác bằng cách chuyển một Intent (quy định dịch vụ sẽ bắt đầu) đến startService(). Hệ thống Android sẽ gọi phương pháp onStartCommand() của dịch vụ và chuyển cho nó Intent. (Bạn tuyệt đối không nên trực tiếp gọi onStartCommand().)

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

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

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

Nếu dịch vụ cũng không cung cấp khả năng gắn kết, ý định được chuyển bằng startService() sẽ là phương thức 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 một kết quả trở lại, khi đó máy khách mà bắt đầu dịch vụ có thể tạo một PendingIntent cho một quảng bá (bằng getBroadcast()) và chuyển nó tới dịch vụ trong Intent mà bắt đầu dịch vụ. Khi đó, dịch vụ có thể sử dụng quảng bá để chuyển 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 tới onStartCommand() của dịch vụ. Tuy nhiên, chỉ có một yêu cầu dừng dịch vụ (bằng stopSelf() hoặc stopService()) là bắt buộc để dừng nó.

Dừng một dịch vụ

Dịch vụ được bắt đầu phải quản lý vòng đời của chính nó. Cụ thể, hệ thống không dừng hay hủy dịch vụ trừ khi nó phải khôi phục bộ nhớ của hệ thống và dịch vụ sẽ tiếp tục chạy sau khi onStartCommand() trả về. Vì vậy, 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 nó 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ẽ hủy dịch vụ ngay khi có thể.

Tuy nhiên, nếu dịch vụ của bạn xử lý nhiều yêu cầu onStartCommand() đồng thời, khi đó bạn không nên dừng dịch vụ khi bạn đã hoàn thành xử lý yêu cầu bắt đầu, vì bạn có thể đã nhận được một yêu cầu bắt đầu mới kể từ thời điểm đó (dừng khi kết thúc yêu cầu thứ nhất sẽ chấm dứt yêu cầu thứ hai). Để tránh vấn đề 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ụ của bạn luôn được dựa trên yêu cầu bắt đầu gần đây nhất. Cụ thể, khi bạn gọi stopSelf(int), bạn sẽ chuyển ID của yêu cầu bắt đầu (startId được chuyển tới onStartCommand()) mà yêu cầu dừng của bạn tương ứng với. Khi đó, 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), vậy ID sẽ không khớp và dịch vụ sẽ không dừng.

Chú ý: Điều quan trọng là ứng dụng của bạn dừng dịch vụ của nó khi nó hoàn thành xong công việc để tránh lãng phí tài nguyên của hệ thống và tốn pin. 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ạn kích hoạt gắn kết cho dịch vụ, bạn phải luôn tự mình dừng dịch vụ nếu dịch vụ đã nhận được lệnh gọi tới onStartCommand().

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

Tạo một Dịch vụ Gắn kết

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

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

Để tạo một dịch vụ gắn kết, bạn phải triển khai phương pháp gọi lại onBind() để trả về một IBinder mà định nghĩa giao diện cho giao tiếp với dịch vụ đó. Khi đó, các thành phần ứng dụng khác có thể gọi bindService() để truy xuất giao diện và bắt đầu các phương pháp gọi trên dịch vụ. Dịch vụ tồn tại chỉ nhằm phục vụ thành phần ứng dụng mà được gắn kết với nó, vì thế khi không có thành phần được gắn kết với dịch vụ, hệ thống sẽ hủy nó (bạn không cần dừng một dịch vụ gắn kết theo cách phải làm khi dịch vụ được bắt đầu thông qua onStartCommand()).

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

Nhiều máy khách có thể gắn kết với dịch vụ đồng thời. Khi một máy khách hoàn thành tương tác với dịch vụ, nó sẽ gọi unbindService() để bỏ gắn kết. Sau khi không còn máy khách nào được gắn kết với dịch vụ, hệ thống sẽ hủy dịch vụ.

Có nhiều cách để triển khai một dịch vụ gắn kết và triển khai sẽ phức tạp hơn so với dịch vụ được bắt đầu, vì thế nội dung bàn về dịch vụ gắn kết được trình bày trong một tài liệu riêng về Dịch vụ Gắn kết.

Gửi Thông báo tới Người dùng

Sau khi chạy, một dịch vụ có thể thông báo cho người dùng về sự kiện bằng cách sử dụng Thông báo Cửa sổ hoặc Thông báo Thanh Trạng thái.

Thông báo cửa sổ là một thông báo xuất hiện một lúc trên bề mặt của cửa sổ hiện tại rồi biến mất, trong khi thông báo thanh trạng thái cung cấp một biểu tượng trong thanh trạng thái cùng một thông báo, người dùng có thể chọn 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 thanh trạng thái là kỹ thuật tốt nhất khi một công việc nền nào đó đã hoàn thành (chẳng hạn như một tệp đã hoàn thành việc tải xuống) và lúc này người dùng có thể hành động dựa trên nó. Khi người dùng chọn thông báo từ dạng xem mở rộng , thông báo có thể bắt đầu một hoạt động (chẳng hạn như xem tệp được tải xuống).

Xem hướng dẫn dành cho nhà phát triển Thông báo Cửa sổ hoặc Thông báo Thanh Trạng thái để biết thêm thông tin.

Chạy một Dịch vụ trong Tiền cảnh

Dịch vụ tiền cảnh là một dịch vụ được coi là điều mà người dùng đang chủ động quan tâm, vì thế nó không được đề nghị để hệ thống tắt bỏ khi bộ nhớ thấp. Dịch vụ tiền cảnh phải cung cấp một thông báo cho thanh trạng thái, nó được đặt dưới tiêu đề "Đang diễn ra", điều này có nghĩa là thông báo không thể loại bỏ được trừ khi dịch vụ bị dừng hoặc loại bỏ khỏi tiền cảnh.

Ví dụ, một trình chơi nhạc đang phát nhạc từ một dịch vụ nên được đặt để chạy trong tiền cảnh, vì người dùng rõ ràng ý thức được hoạt động của nó. Thông báo trong thanh trạng thái có thể cho biết bài hát đang chơi và cho phép người dùng khởi chạy một hoạt động để tương tác với trình chơi nhạc.

Để yêu cầu dịch vụ của bạn chạy trong tiền cảnh, hãy gọi startForeground(). Phương pháp này dùng hai tham số: một số nguyên để xác định duy nhất thông báo và Notification cho thanh trạng thái. Ví dụ:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

Chú ý: ID số nguyên mà bạn cấp cho startForeground() không được bằng 0.

Để xóa bỏ dịch vụ khỏi tiền cảnh, hãy gọi stopForeground(). Phương pháp này dùng một boolean, cho biết có loại bỏ cả thông báo thanh trạng thái hay không. Phương pháp này không dừng dịch vụ. Tuy nhiên, nếu bạn dừng dịch vụ trong khi nó vẫn đang chạy trong tiền cảnh, khi đó thông báo cũng bị loại bỏ.

Để biết thêm thông tin về thông báo, hãy xem phần Tạo Thông báo Thanh Trạng thái.

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, một điều thậm chí còn quan trọng hơn đó là bạn phải thật chú ý tới cách dịch vụ của bạn được tạo và hủy, bởi một dịch vụ có thể chạy ngầm mà người dùng không biết.

Vòng đời của dịch vụ—từ khi nó được tạo tới khi nó bị hủy—có thể đi theo hai con đường khác nhau:

  • Dịch vụ được 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ẽ hủy nó.

  • Dịch vụ gắn kết

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

Hai con đường này hoàn toàn riêng biệt. Cụ thể, bạn có thể gắn kết với một dịch vụ đã được bắt đầu bằng startService(). Ví dụ, một dịch vụ nhạc nền có thể được bắt đầu bằng cách gọi startService() bằng một Intent mà sẽ nhận biết nhạc để phát. Sau đó, có thể là khi người dùng muốn thực thi một quyền điều khiển đối với trình phát đó hoặc lấy thông tin về bài hát đang phát, hoạt động có thể gắ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ụ tới khi tất cả máy khách bỏ gắn kết.

Triển khai gọi lại vòng đời

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

public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // 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 mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @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 như các phương pháp gọi lại vòng đời của hoạt động, bạn không phải gọi triển khai siêu lớp với những phương pháp gọi lại này.

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

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

  • Toàn bộ vòng đời của một dịch vụ xảy ra giữa thời điểm gọi onCreate() và thời điểm onDestroy() trả về. Giống như hoạt động, dịch vụ thực hiện thiết lập ban đầu của nó trong onCreate() và giải phóng tất cả tài nguyên còn lại trong onDestroy(). Ví dụ, một dịch vụ phát lại nhạc có thể tạo luồng mà tại đó nhạc sẽ được phát trong onCreate(), sau đó dừng luồng trong onDestroy().

    Các phương pháp onCreate()onDestroy() được gọi cho tất cả dịch vụ, dù chúng được tạo bởi startService() hay bindService().

  • Vòng đời hiện hoạt của một dịch vụ sẽ bắt đầu bằng một lệnh gọi đến hoặc onStartCommand() hoặc onBind(). Mỗi phương pháp sẽ được giao Intent mà được chuyển tương ứng cho hoặc startService() hoặc bindService().

    Nếu dịch vụ được bắt đầu, vòng đời hiện hoạt sẽ chấm dứt tại cùng thời điểm khi toàn bộ vòng đời chấm dứt (dịch vụ sẽ vẫn hiện hoạt ngay cả sau khi onStartCommand() trả về). Nếu dịch vụ bị gắn kết, vòng đời hiện hoạt sẽ chấm dứt khi onUnbind() trả về.

Lưu ý: Mặc dù dịch vụ được bắt đầu bị dừng bởi một lệnh gọi đến hoặc stopSelf() hoặc stopService(), sẽ không có một lệnh gọi lại tương ứng cho dịch vụ (không có lệnh gọi lại onStop()). Vì thế, trừ khi dịch vụ được gắn kết với một máy khách, hệ thống sẽ hủy nó khi dịch vụ bị dừng—onDestroy() là lệnh gọi lại duy nhất nhận được.

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

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