Service
là một thành phần ứng dụng có thể thực hiện các thao tác chạy trong nền trong thời gian dài. Không cung cấp giao diện người dùng. Sau khi khởi động, một dịch vụ có thể tiếp tục chạy trong một khoảng 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 dịch vụ đó và thậm chí thực hiện giao tiếp giữa các 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ả đều ở 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 biệt 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 biệt 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ụ
Dưới đâ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ụ: một ứng dụng âm thanh sẽ sử dụng dịch vụ trên nền trước để phát mộ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. Dịch vụ trên nền trước 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 rằng dịch vụ đang chạy. Bạn 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.
Lưu ý: API WorkManager cung cấp 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 WorkManager thay vì sử dụng trực tiếp các dịch vụ trên nền trước.
- 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 nhận thấy. Ví dụ: nếu một ứng dụng sử dụng một dịch vụ để nén bộ nhớ, thì đó thường là một dịch vụ trong 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 chính ứng dụng không chạy ở 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 thông tin vị trí ở chế độ nền. Thay vào đó, hãy lên lịch tác vụ bằng WorkManager.
- Đã liên kết
- 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ụ liên kết 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í thực hiện việc này trên các quy trình có giao tiếp giữa các quy trình (IPC). Dịch vụ liên kết chỉ chạy miễn là một thành phần ứng dụng khác đượ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ỷ.
Mặc dù tài liệu này thường thảo luận riêng 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. Bạn chỉ cần 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 khởi động và onBind()
để cho phép liên kết.
Bất kể dịch vụ của bạn đã được khởi động, liê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ụ đó (ngay cả từ một ứng dụng riêng biệt) theo cách tương tự như bất kỳ thành phần nào có thể sử dụng một hoạt động – bằng cách bắt đầu hoạt động đó bằng 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 dịch vụ và luồng
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 của bạn. 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 công việc bên ngoài luồng chính, nhưng chỉ khi người dùng đang tương tác với ứng dụng, bạn nên tạo một luồng mới trong ngữ cảnh của một thành phần ứng dụng khác. 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à trình thực thi từ gói java.util.concurrent
hoặc coroutine Kotlin thay vì lớp Thread
truyền thống. Hãy xem tài liệu Tạo luồng trên Android để biết thêm thông tin về việc di chuyển quá trình thực thi sang luồng ở chế độ nền.
Hãy nhớ rằng nếu bạn sử dụng một dịch vụ, thì dịch vụ đó vẫn chạy trong luồng chính của ứng dụng theo mặc định. 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ụ đó thực hiện các thao tác chuyên sâu hoặc chặn.
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 lớp này. 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). Đây là những 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ư một hoạt động) yêu cầu khởi động 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 ở chế độ nền vô thời hạn. Nếu triển khai tính năng này, bạn có trách nhiệm dừng dịch vụ khi công việc của dịch vụ hoàn tất bằng cách gọistopSelf()
hoặcstopService()
. Nếu chỉ muốn cung cấp liên kết, bạn không cần 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 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 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 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ặconBind()
). Nếu dịch vụ đang chạy, phương thức này sẽ không được gọi. onDestroy()
- Hệ thống gọi phương thức này khi dịch vụ không còn được sử dụng và đang bị huỷ. Dịch vụ của bạn nên triển khai phương thức này để dọn dẹp mọi tài nguyên như luồng, trình nghe đã đăng ký hoặc trình thu. Đâ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 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ự dừng bằ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 miễn là thành phần được liên kết với dịch vụ đó. Sau khi dịch vụ được huỷ liên kết với tất cả ứng dụng khách, hệ thống sẽ huỷ bỏ dịch vụ đó.
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 có tiêu điểm người dùng. Nếu dịch vụ được liên kết với một hoạt động có tiêu điểm của người dùng, thì dịch vụ đó ít có khả năng bị huỷ; nếu dịch vụ được khai báo là chạy ở nền trước, thì dịch vụ đó hiếm khi bị huỷ.
Nếu dịch vụ được khởi động và chạy trong thời gian dài, theo thời gian, hệ thống sẽ hạ thấp vị trí của dịch vụ trong danh sách tác vụ ở chế độ nền và dịch vụ sẽ dễ bị tắt. Nếu dịch vụ của bạn được khởi động, bạn phải thiết kế dịch vụ đó để xử lý linh hoạt việc khởi động lại của hệ thống. Nếu hệ thống tắt dịch vụ của bạn, hệ thống sẽ khởi động lại dịch vụ đó ngay khi có tài nguyên, nhưng điều này cũng phụ thuộc vào giá trị mà bạn trả về từ onStartCommand()
. Để biết thêm thông tin về thời điểm hệ thống có thể huỷ một dịch vụ, hãy xem tài liệu Quy trình và Luồng.
Trong các phần sau, bạn sẽ thấy cách tạo các phương thức dịch vụ startService()
và bindService()
, cũng như cách sử dụng các phương thức đó từ các thành phần ứng dụng khác.
Khai báo dịch vụ trong tệp kê khai
Bạn phải khai báo tất cả dịch vụ trong tệp kê khai của ứng dụng, giống như cách bạn làm với các hoạt động và 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 về phần tử <service>
để biết thêm thông tin về cách khai báo dịch vụ của bạn trong tệp kê khai.
Bạn có thể đưa các thuộc tính khác vào phần tử <service>
để xác định các thuộc tính như 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ơ mã bị lỗi do phụ thuộc vào ý định rõ ràng để bắt đầu hoặc liên kết dịch vụ (hãy đọ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 của bạn an toàn, hãy luôn sử dụng ý định rõ ràng khi bắt đầu Service
và không khai báo bộ lọc ý định cho các dịch vụ của bạn. 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ụ nào 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ẽ gửi một 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ỉ dành 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 giúp ngăn các ứng dụng khác khởi động dịch vụ của bạn một cách hiệu quả, ngay cả khi sử dụng ý định rõ ràng.
Lưu ý: Người dùng có thể xem những dịch vụ nào đang chạy trên thiết bị của họ. 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ể dừ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 nội dung mô tả, hãy cung cấp một câu ngắn giải thích về chức năng của dịch vụ và những lợi ích mà dịch vụ đó mang lại.
Tạo 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ụ bắt đầu, dịch vụ đó sẽ 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 trong nền vô thời hạn, ngay cả khi thành phần đã khởi động dịch vụ đó bị huỷ. Do đó, dịch vụ sẽ tự dừng khi công việc 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 dịch vụ bằng cách gọi stopService()
.
Một thành phần ứng dụng như một hoạt động có thể khởi động dịch vụ bằng cách gọi startService()
và truyền một 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 cần lưu cho dịch vụ đó bằng cách truyền một ý định đến startService()
. Dịch vụ nhận được ý đị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: Theo mặc định, một dịch vụ sẽ chạy trong cùng một quy trình với ứng dụng mà dịch vụ đó được khai báo và trong luồng chính của ứng dụng đó. Nếu dịch vụ của bạn thực hiện các thao tác chuyên sâu hoặc chặn trong khi người dùng tương tác với một hoạt động trong 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 bên trong dịch vụ.
Lớp Service
là lớp cơ sở cho tất cả các dịch vụ. Khi mở rộng lớp này, điều quan trọng là bạ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 của mình; theo mặc định, dịch vụ sử dụng luồng chính của ứng dụng, đ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 luồng worker để xử lý từng yêu cầu khởi động. 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 việc triển khai Giới hạn thực thi trong nền.
Hơn nữa, tính năng này không còn được dùng nữa kể từ Android 11.
Bạn có thể sử dụng JobIntentService để thay thế IntentService
tương thích với các phiên bản Android mới hơn.
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 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 xử lý ở chế độ 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à 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ả các lệnh gọi đến trong onStartCommand()
và đăng công việc lên Handler
chạy trên luồng ở chế độ nền. Phương thức này hoạt động giống như IntentService
và xử lý tuần tự tất cả các yêu cầu, lần lượt từng yêu cầu một.
Bạn có thể thay đổi mã để chạy công việc trên một nhóm luồng, ví dụ: nếu bạn muốn chạy nhiều yêu cầu cùng một 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 tiếp tục dịch vụ trong trường hợp hệ thống huỷ 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 tắt dịch vụ sau khi
onStartCommand()
trả về, đừng tạo lại dịch vụ trừ khi có ý định đang chờ xử lý để phân phối. Đây là lựa chọ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ủa bạn chỉ cần khởi động lại mọi công việc chưa hoàn tất. START_STICKY
- Nếu hệ thống tắt dịch vụ sau khi
onStartCommand()
trả về, hãy tạo lại dịch vụ và gọionStartCommand()
, nhưng không phân phối lại ý định cuối cùng. Thay vào đó, hệ thống sẽ gọionStartCommand()
bằng ý định rỗng, trừ phi 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. Điều này phù hợp với 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à chờ một công việc. START_REDELIVER_INTENT
- Nếu hệ thống tắt dịch vụ sau khi
onStartCommand()
trả về, hãy tạo lại dịch vụ và gọionStartCommand()
bằng ý định gần đây nhất đã được phân phối đến dịch vụ. Mọi ý định đang chờ xử lý đều đượ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 một 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 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 truyền Intent
đến startService()
hoặc startForegroundService()
. Hệ thống Android gọi phương thức onStartCommand()
của dịch vụ và truyền Intent
vào phương thức đó. Intent
sẽ chỉ định dịch vụ cần khởi động.
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 quy định hạn chế đối với việc sử dụng hoặc tạo dịch vụ nền, trừ phi chính ứng dụng đó đang chạy ở nền trước. Nếu cần tạo một dịch vụ trên nền trước, ứng dụng phải gọi startForegroundService()
. Phương thức đó tạo một dịch vụ trong nền, nhưng phương thức này sẽ báo cho hệ thống biết rằng dịch vụ sẽ tự quảng bá lên nền trước. Sau khi được 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 rõ ràng 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()
trả về ngay lập tức và hệ thống Android sẽ 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 đó 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à 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 lại kết quả, thì ứng dụng khởi động dịch vụ có thể tạo PendingIntent
cho thông báo truyền tin (bằng getBroadcast()
) và phân phố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 khởi động 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 một yêu cầu để dừng dịch vụ (bằng stopSelf()
hoặc stopService()
) là có thể dừng dịch vụ.
Dừng một dịch vụ
Dịch vụ đã bắt đầu phải quản lý vòng đời của riêng dịch vụ đó. Tức 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ụ 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ụ bằng cách gọi stopService()
.
Sau khi yêu cầu dừng bằng stopSelf()
hoặc stopService()
, hệ thống sẽ huỷ bỏ dịch vụ càng sớm càng tốt.
Nếu dịch vụ của bạn xử lý đồng thời nhiều yêu cầu đến onStartCommand()
, bạn không nên dừng dịch vụ khi xử lý xong yêu cầu bắt đầu, vì bạn có thể đã nhận được 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 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ụ 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ã nhận dạng 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. Sau đó, nếu dịch vụ nhận được yêu cầu khởi động 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.
Thận trọng: Để tránh lãng phí tài nguyên hệ thống và tiêu thụ pin, hãy đảm bảo rằng ứng dụng của bạn dừng các dịch vụ khi hoàn tất công việc.
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 phải luôn tự dừng dịch vụ nếu dịch vụ đó 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 dưới đây về Quản lý vòng đời của một dịch vụ.
Tạo dịch vụ liên kết
Dịch vụ liên kết 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 kết nối lâu dài.
Phương thức này thường không cho phép các thành phần bắt đầu bằng cách gọi startService()
.
Tạo một dịch vụ liên kết 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ụ liên kết, 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 ứng dụng khác 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ụ này chỉ tồn tại để phân phát thành phần ứng dụng được liên kết với dịch vụ. Vì vậy, khi không có thành phần nào được liên kết với dịch vụ, hệ thống sẽ huỷ bỏ dịch vụ đó.
Bạn không cần dừng một dịch vụ liên kết theo cách tương tự như khi bắt đầu dịch vụ thông qua onStartCommand()
.
Để tạo một dịch vụ liên kết, 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 cách triển khai 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 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 khách có thể liên kết với dịch vụ cùng một lúc. Khi hoàn tất việc tương tác 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 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ụ liên kết 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, phần thảo luận về dịch vụ liên kết sẽ xuất hiện trong một tài liệu riêng về Dịch vụ liên kết.
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 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 thanh thông báo nhanh là một thông báo chỉ xuất hiện trên nền của cửa sổ hiện tại trong chốc lát 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 trong thanh trạng thái cùng với một 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 để sử dụng khi công việc ở chế độ nền (chẳng hạn như tải tệp xuống) đã hoàn tất và người dùng hiện có thể hành động theo thông báo đó. Khi người dùng chọn thông báo trong chế độ xem 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, điều quan trọng hơn là bạn phải chú ý đến cách tạo và huỷ dịch vụ vì dịch vụ có thể chạy ở chế độ nền mà người dùng không biết.
Vòng đời của dịch vụ (từ khi được tạo đến khi bị huỷ) có thể tuân theo một trong hai lộ trình sau:
- Dịch vụ đã bắt đầu
Dịch vụ này đượ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ọistopSelf()
. Một thành phần khác cũng có thể dừng dịch vụ bằng cách gọistopService()
. Khi dịch vụ bị dừng, hệ thống sẽ huỷ bỏ dịch vụ đó. - Dịch vụ liên kết
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ệnIBinder
. Ứng dụng có thể đóng kết nối bằng cách gọiunbindService()
. Nhiều ứng dụng khách 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 khách đó huỷ liên kết, hệ thống sẽ huỷ dịch vụ. Dịch vụ không cần 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ụ nhạc nền bằng cách gọi startService()
với Intent
xác định nhạc cần phát. Sau đó, có thể khi người dùng muốn thực hiện một số thao tác kiểm soát đối với trình phát hoặc 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 huỷ liên kết.
Triển khai lệnh 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 trong trạng thái của dịch vụ và thực hiện công việc vào 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 funonCreate
() { // The service is being created } override funonStartCommand
(intent: Intent?, flags: Int, startId: Int): Int { // The service is starting, due to a call to startService() return startMode } override funonBind
(intent: Intent): IBinder? { // A client is binding to the service with bindService() return binder } override funonUnbind
(intent: Intent): Boolean { // All clients have unbound with unbindService() return allowRebind } override funonRebind
(intent: Intent) { // A client is binding to the service with bindService(), // after onUnbind() has already been called } override funonDestroy
() { // 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 voidonCreate
() { // The service is being created } @Override public intonStartCommand
(Intent intent, int flags, int startId) { // The service is starting, due to a call tostartService()
return startMode; } @Override public IBinderonBind
(Intent intent) { // A client is binding to the service withbindService()
return binder; } @Override public booleanonUnbind
(Intent intent) { // All clients have unbound withunbindService()
return allowRebind; } @Override public voidonRebind
(Intent intent) { // A client is binding to the service withbindService()
, // after onUnbind() has already been called } @Override public voidonDestroy
() { // 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 cha của các phương thức gọi lại này.
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 các 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ể cách bắt đầu, đều có thể cho phép ứng dụng liên kết với dịch vụ đó.
Một dịch vụ ban đầu được khởi động bằng onStartCommand()
(bởi một ứng dụng gọi startService()
) vẫn có thể nhận được lệnh gọi đến onBind()
(khi một ứng dụng 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 hai 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ụ xảy ra trong khoảng thời gian gọi
onCreate()
và thời điểmonDestroy()
trả về. Giống như một hoạt động, dịch vụ sẽ thiết lập ban đầu trongonCreate()
và giải phóng tất cả tài nguyên còn lại trongonDestroy()
. Ví dụ: một dịch vụ phát nhạc có thể tạo luồng phát nhạc trongonCreate()
, sau đó dừng luồng đó trongonDestroy()
.Lưu ý: Phương thức
onCreate()
vàonDestroy()
được gọi cho tất cả các dịch vụ, cho dù các dịch vụ đó dostartService()
haybindService()
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ặconBind()
. Mỗi phương thức được chuyểnIntent
đã được truyền đếnstartService()
hoặcbindService()
.Nếu dịch vụ được khởi động, thì 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 (dịch vụ vẫn hoạt động ngay cả sau khi
onStartCommand()
trả về). Nếu dịch vụ được liên kết, thì thời gian hoạt động sẽ kết thúc khionUnbind()
trả về.
Lưu ý: Mặc dù một 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 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 dịch vụ bị dừng – onDestroy()
là lệnh gọi lại duy nhất được nhận.
Để biết thêm thông tin về cách tạo một dịch vụ cung cấp tính năng liên kết, hãy xem tài liệu về Dịch vụ liên kết. Tài liệu này bao gồm 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ụ liên kết.