บริการที่มีผลผูกพันคือเซิร์ฟเวอร์ในอินเทอร์เฟซไคลเอ็นต์-เซิร์ฟเวอร์ ซึ่งช่วยให้คอมโพเนนต์ต่างๆ เช่น กิจกรรม ผูกกับบริการ ส่งคำขอ รับการตอบกลับ และทำการสื่อสารระหว่างโปรเซส (IPC) ได้ โดยทั่วไปแล้ว บริการที่มีผลผูกพันจะทำงานเฉพาะในขณะที่ให้บริการคอมโพเนนต์ของแอปพลิเคชันอื่น และจะไม่ทำงานในเบื้องหลังอย่างไม่มีกำหนด
เอกสารนี้อธิบายวิธีสร้างบริการที่มีผลผูกพัน รวมถึงวิธีเชื่อมโยงกับบริการจากคอมโพเนนต์ของแอปพลิเคชันอื่นๆ ดูข้อมูลเพิ่มเติมเกี่ยวกับบริการโดยทั่วไป เช่น วิธีส่งการแจ้งเตือนจากบริการและตั้งค่าบริการให้ทำงานอยู่เบื้องหน้าได้ที่ ภาพรวมของบริการ
ข้อมูลเบื้องต้น
บริการที่มีผลผูกพันคือการใช้งานคลาส Service ที่อนุญาตให้แอปพลิเคชันอื่นๆ ผูกกับบริการและโต้ตอบกับบริการได้ หากต้องการเชื่อมโยงบริการ คุณต้องใช้เมธอด Callback onBind() เมธอดนี้จะแสดงผลออบเจ็กต์ IBinder ที่กำหนดอินเทอร์เฟซการเขียนโปรแกรมที่ไคลเอ็นต์ใช้เพื่อโต้ตอบกับบริการได้
เชื่อมโยงกับบริการที่เริ่มต้นแล้ว
ดังที่กล่าวไว้ในภาพรวมของบริการ คุณสามารถสร้างบริการที่ทั้งเริ่มต้นและเชื่อมโยงได้ กล่าวคือ คุณสามารถเริ่มบริการได้โดยเรียกใช้ startService() ซึ่งจะทำให้บริการทำงานอย่างไม่มีกำหนด นอกจากนี้ คุณยังอนุญาตให้ไคลเอ็นต์เชื่อมโยงกับบริการได้โดยเรียกใช้ bindService()
หากคุณอนุญาตให้เริ่มและเชื่อมโยงบริการได้ เมื่อบริการเริ่มทำงาน ระบบจะไม่ทำลายบริการเมื่อไคลเอ็นต์ทั้งหมดเลิกเชื่อมโยง
แต่คุณต้องหยุดบริการอย่างชัดแจ้งโดยเรียกใช้ stopSelf() หรือ stopService()
แม้ว่าโดยปกติแล้วคุณจะใช้ onBind()
หรือ onStartCommand() แต่บางครั้งก็จำเป็นต้องใช้ทั้ง 2 อย่าง ตัวอย่างเช่น เครื่องเล่นเพลงอาจพบว่าการปล่อยให้บริการทำงานอย่างไม่มีกำหนดและให้บริการการเชื่อมโยงด้วยนั้นมีประโยชน์ วิธีนี้จะช่วยให้กิจกรรมเริ่มบริการเพื่อเล่นเพลงได้ และเพลงจะเล่นต่อไปแม้ว่าผู้ใช้จะออกจากแอปพลิเคชันก็ตาม จากนั้นเมื่อผู้ใช้กลับมาที่แอปพลิเคชัน กิจกรรมจะเชื่อมโยงกับบริการเพื่อควบคุมการเล่นอีกครั้งได้
ดูข้อมูลเพิ่มเติมเกี่ยวกับวงจรของบริการเมื่อเพิ่มการเชื่อมโยงไปยังบริการที่เริ่มต้นแล้วได้ที่ส่วนจัดการวงจรของบริการที่มีผลผูกพัน
ไคลเอ็นต์จะเชื่อมโยงกับบริการโดยเรียกใช้ bindService() เมื่อดำเนินการดังกล่าว จะต้องมีการติดตั้งใช้งาน ServiceConnection ซึ่งจะตรวจสอบการเชื่อมต่อกับบริการ ค่าที่แสดงผลของ bindService() จะระบุว่ามีบริการที่ขอหรือไม่ และไคลเอ็นต์ได้รับอนุญาตให้เข้าถึงบริการหรือไม่
เมื่อระบบ Android สร้างการเชื่อมต่อระหว่างไคลเอ็นต์และบริการ ระบบจะเรียกใช้ onServiceConnected() ใน ServiceConnection เมธอด onServiceConnected() มีอาร์กิวเมนต์ IBinder ซึ่งไคลเอ็นต์จะใช้เพื่อสื่อสารกับบริการที่มีผลผูกพัน
คุณสามารถเชื่อมต่อไคลเอ็นต์หลายรายการกับบริการพร้อมกันได้ อย่างไรก็ตาม ระบบจะแคชช่องทางการสื่อสารของบริการ IBinder ไว้
กล่าวคือ ระบบจะเรียกใช้เมธอด onBind() ของบริการเพื่อสร้าง IBinder เฉพาะเมื่อไคลเอ็นต์แรกเชื่อมโยงเท่านั้น จากนั้นระบบจะส่ง IBinder เดียวกันนั้นไปยังไคลเอ็นต์เพิ่มเติมทั้งหมดที่เชื่อมโยงกับบริการเดียวกันนั้น โดยไม่ต้องเรียกใช้ onBind() อีกครั้ง
เมื่อไคลเอ็นต์รายสุดท้ายเลิกเชื่อมโยงกับบริการ ระบบจะทำลายบริการนั้น เว้นแต่ว่าบริการจะเริ่มต้นโดยใช้ startService()
ส่วนที่สำคัญที่สุดของการติดตั้งใช้งานบริการที่มีผลผูกพันคือการกำหนดอินเทอร์เฟซที่เมธอด Callback onBind() แสดงผล ส่วนต่อไปนี้จะกล่าวถึงวิธีต่างๆ ที่คุณสามารถกำหนดอินเทอร์เฟซ IBinder ของบริการได้
สร้างบริการที่มีผลผูกพัน
เมื่อสร้างบริการที่ให้การเชื่อมโยง คุณต้องระบุ IBinder ที่มีอินเทอร์เฟซการเขียนโปรแกรมซึ่งไคลเอ็นต์สามารถใช้เพื่อโต้ตอบกับบริการได้ คุณกำหนดอินเทอร์เฟซได้ 3 วิธีดังนี้
- ขยายคลาส Binder
- หากบริการของคุณเป็นแบบส่วนตัวสำหรับแอปพลิเคชันของคุณเองและทำงานในกระบวนการเดียวกับไคลเอ็นต์ ซึ่งเป็นเรื่องปกติ ให้สร้างอินเทอร์เฟซโดยขยายคลาส
Binderและแสดงผลอินสแตนซ์ของคลาสดังกล่าวจากonBind()ไคลเอ็นต์จะได้รับBinderและสามารถใช้เพื่อเข้าถึงเมธอดสาธารณะที่มีอยู่ในBinderหรือServiceได้โดยตรงวิธีนี้เป็นวิธีที่แนะนำเมื่อบริการของคุณเป็นเพียงโปรแกรมทำงานเบื้องหลังสำหรับแอปพลิเคชันของคุณเอง กรณีการใช้งานเดียวที่วิธีนี้ไม่ใช่วิธีที่เหมาะสมในการสร้างอินเทอร์เฟซคือเมื่อบริการของคุณถูกใช้โดยแอปพลิเคชันอื่นๆ หรือในกระบวนการที่แยกต่างหาก
- ใช้ Messenger
- หากต้องการให้อินเทอร์เฟซทำงานในกระบวนการต่างๆ ได้ คุณสามารถสร้างอินเทอร์เฟซสำหรับบริการด้วย
Messengerด้วยวิธีนี้ บริการจะกำหนดHandlerที่ตอบสนองต่อออบเจ็กต์Messageประเภทต่างๆHandlerนี้เป็นพื้นฐานสำหรับMessengerซึ่งสามารถแชร์IBinderกับไคลเอ็นต์ได้ ทำให้ไคลเอ็นต์ส่งคำสั่งไปยังบริการโดยใช้ออบเจ็กต์Messageได้ นอกจากนี้ ไคลเอ็นต์ยังสามารถกำหนดMessengerของตนเองเพื่อให้บริการส่งข้อความกลับได้วิธีนี้เป็นวิธีที่ง่ายที่สุดในการสื่อสารระหว่างโปรเซส (IPC) เนื่องจาก
Messengerจะจัดคิวคำขอทั้งหมดไว้ในชุดข้อความเดียว คุณจึงไม่ต้องออกแบบบริการให้ปลอดภัยต่อชุดข้อความ - ใช้ AIDL
- ภาษาที่ใช้สื่อสารข้อมูลระหว่างคอมโพเนนต์ของ Android (AIDL) จะแยกออบเจ็กต์ออกเป็นองค์ประกอบพื้นฐานที่ระบบปฏิบัติการเข้าใจได้ และจัดระเบียบองค์ประกอบเหล่านั้นในกระบวนการต่างๆ เพื่อดำเนินการ IPC เทคนิคก่อนหน้านี้ที่ใช้
Messengerนั้นอิงตาม AIDL เป็นโครงสร้างพื้นฐานตามที่กล่าวไว้ในส่วนก่อนหน้า
Messengerจะสร้างคิวของคำขอไคลเอ็นต์ทั้งหมดในชุดข้อความเดียว ดังนั้นบริการจึงได้รับคำขอทีละรายการ อย่างไรก็ตาม หากต้องการให้บริการจัดการคำขอหลายรายการพร้อมกัน คุณสามารถใช้ AIDL ได้โดยตรง ในกรณีนี้ บริการของคุณต้องปลอดภัยต่อเธรดและสามารถทำงานแบบมัลติเธรดได้หากต้องการใช้ AIDL โดยตรง ให้สร้างไฟล์
.aidlที่กำหนดอินเทอร์เฟซการเขียนโปรแกรม เครื่องมือ Android SDK จะใช้ไฟล์นี้เพื่อสร้างคลาสแอบสแตรกต์ที่ใช้ Interface และจัดการ IPC ซึ่งคุณสามารถขยายภายในบริการได้
หมายเหตุ: สำหรับแอปพลิเคชันส่วนใหญ่ AIDL ไม่ใช่ตัวเลือกที่ดีที่สุดในการสร้างบริการที่มีผลผูกพัน เนื่องจากอาจต้องใช้ความสามารถแบบมัลติเธรดและอาจส่งผลให้การติดตั้งใช้งานมีความซับซ้อนมากขึ้น ดังนั้น เอกสารนี้จึงไม่ได้กล่าวถึงวิธีใช้สำหรับบริการของคุณ หากคุณแน่ใจว่าต้องใช้ AIDL โดยตรง โปรดดูเอกสาร AIDL
ขยายคลาส Binder
หากมีเพียงแอปพลิเคชันในเครื่องที่ใช้บริการของคุณและไม่จำเป็นต้องทำงานข้ามกระบวนการ คุณสามารถใช้คลาส Binder ของคุณเองที่ให้ไคลเอ็นต์เข้าถึงเมธอดสาธารณะในบริการได้โดยตรง
หมายเหตุ: วิธีนี้ใช้ได้เฉพาะในกรณีที่ไคลเอ็นต์และบริการอยู่ในแอปพลิเคชันและกระบวนการเดียวกัน ซึ่งเป็นกรณีที่พบบ่อยที่สุด ตัวอย่างเช่น วิธีนี้เหมาะกับแอปพลิเคชันเพลงที่ต้องเชื่อมโยงกิจกรรมกับบริการของตนเองที่เล่นเพลงอยู่เบื้องหลัง
วิธีตั้งค่ามีดังนี้
- ในบริการ ให้สร้างอินสแตนซ์ของ
Binderที่ทำอย่างใดอย่างหนึ่งต่อไปนี้- มีเมธอดสาธารณะที่ไคลเอ็นต์เรียกใช้ได้
- แสดงผลอินสแตนซ์
Serviceปัจจุบัน ซึ่งมีเมธอดสาธารณะที่ไคลเอ็นต์เรียกใช้ได้ - แสดงผลอินสแตนซ์ของคลาสอื่นที่โฮสต์โดยบริการที่มีเมธอดสาธารณะที่ไคลเอ็นต์เรียกใช้ได้
- แสดงผลอินสแตนซ์นี้ของ
Binderจากเมธอด CallbackonBind() - ในไคลเอ็นต์ ให้รับ
Binderจากเมธอด CallbackonServiceConnected()และโทรไปยังบริการที่มีผลผูกพันโดยใช้วิธีการที่ระบุไว้
หมายเหตุ: บริการและไคลเอ็นต์ต้องอยู่ในแอปพลิเคชันเดียวกันเพื่อให้ไคลเอ็นต์แคสต์ออบเจ็กต์ที่แสดงผลและเรียกใช้ API ได้อย่างถูกต้อง บริการและไคลเอ็นต์ต้องอยู่ในกระบวนการเดียวกันด้วย เนื่องจากเทคนิคนี้ไม่ได้ทำการจัดระเบียบข้อมูลข้ามกระบวนการ
ตัวอย่างเช่น บริการที่ให้สิทธิ์เข้าถึงเมธอดในบริการแก่ไคลเอ็นต์ผ่านการติดตั้งใช้งาน Binder มีดังนี้
Kotlin
class LocalService : Service() { // Binder given to clients. private val binder = LocalBinder() // Random number generator. private val mGenerator = Random() /** Method for clients. */ val randomNumber: Int get() = mGenerator.nextInt(100) /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ inner class LocalBinder : Binder() { // Return this instance of LocalService so clients can call public methods. fun getService(): LocalService = this@LocalService } override fun onBind(intent: Intent): IBinder { return binder } }
Java
public class LocalService extends Service { // Binder given to clients. private final IBinder binder = new LocalBinder(); // Random number generator. private final Random mGenerator = new Random(); /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { // Return this instance of LocalService so clients can call public methods. return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return binder; } /** Method for clients. */ public int getRandomNumber() { return mGenerator.nextInt(100); } }
LocalBinder มีเมธอด getService() สำหรับไคลเอ็นต์เพื่อดึงข้อมูลอินสแตนซ์ปัจจุบันของ LocalService ซึ่งจะช่วยให้ไคลเอ็นต์เรียกใช้เมธอดสาธารณะในบริการได้ เช่น ลูกค้าสามารถโทรหา getRandomNumber() จากบริการได้
ต่อไปนี้เป็นกิจกรรมที่เชื่อมโยงกับ LocalService และเรียก getRandomNumber() เมื่อมีการคลิกปุ่ม
Kotlin
class BindingActivity : Activity() { private var mService: LocalService? = null private var mBound: Boolean = false /** Defines callbacks for service binding, passed to bindService(). */ private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to LocalService, cast the IBinder and get LocalService instance. val binder = service as LocalService.LocalBinder mService = binder.getService() mBound = true } override fun onServiceDisconnected(arg0: ComponentName) { mService = null mBound = false } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } override fun onStart() { super.onStart() // Bind to LocalService. Intent(this, LocalService::class.java).also { intent -> bindService(intent, connection, Context.BIND_AUTO_CREATE) } } override fun onStop() { super.onStop() unbindService(connection) // Release the reference so the service can be garbage-collected once it's // destroyed. onServiceDisconnected() isn't called when the client unbinds. mService = null mBound = false } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute). */ fun onButtonClick(v: View) { if (mBound) { // Call a method from the LocalService. // However, if this call is something that might hang, then put this request // in a separate thread to avoid slowing down the activity performance. val num: Int = mService?.randomNumber ?: return Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show() } } }
Java
public class BindingActivity extends Activity { LocalService mService; boolean mBound = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to LocalService. Intent intent = new Intent(this, LocalService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); unbindService(connection); // Release the reference so the service can be garbage-collected once it's // destroyed. onServiceDisconnected() isn't called when the client unbinds. mService = null; mBound = false; } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute). */ public void onButtonClick(View v) { if (mBound) { // Call a method from the LocalService. // However, if this call is something that might hang, then put this request // in a separate thread to avoid slowing down the activity performance. int num = mService.getRandomNumber(); Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); } } /** Defines callbacks for service binding, passed to bindService(). */ private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get LocalService instance. LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mService = null; mBound = false; } }; }
ตัวอย่างก่อนหน้านี้แสดงวิธีที่ไคลเอ็นต์เชื่อมโยงกับบริการโดยใช้การติดตั้งใช้งาน ServiceConnection และ Callback onServiceConnected() ส่วนถัดไปจะให้ข้อมูลเพิ่มเติมเกี่ยวกับกระบวนการเชื่อมโยงกับบริการนี้
หมายเหตุ: ในตัวอย่างก่อนหน้านี้ เมธอด onStop() จะยกเลิกการเชื่อมโยงไคลเอ็นต์จากบริการและปล่อยการอ้างอิงของไคลเอ็นต์ไปยังบริการนั้น
เลิกเชื่อมโยงไคลเอ็นต์จากบริการในเวลาที่เหมาะสมตามที่อธิบายไว้ในส่วนหมายเหตุเพิ่มเติม
ดูโค้ดตัวอย่างเพิ่มเติมได้ที่คลาส
LocalService.java และคลาส
LocalServiceActivities.java ใน ApiDemos
ใช้ Messenger
หากต้องการให้บริการสื่อสารกับกระบวนการระยะไกล คุณสามารถใช้ Messenger เพื่อจัดเตรียมอินเทอร์เฟซสำหรับบริการของคุณได้ เทคนิคนี้ช่วยให้คุณทำการสื่อสารระหว่างโปรเซส (IPC) ได้โดยไม่ต้องใช้ AIDL
การใช้ Messenger สำหรับอินเทอร์เฟซนั้นง่ายกว่าการใช้ AIDL เนื่องจาก Messenger จะจัดคิวการเรียกใช้บริการทั้งหมด อินเทอร์เฟซ AIDL แบบล้วนจะส่งคำขอไปยังบริการพร้อมกัน ซึ่งบริการจะต้องจัดการมัลติเธรด
สำหรับแอปพลิเคชันส่วนใหญ่ บริการไม่จำเป็นต้องดำเนินการแบบมัลติเธรด ดังนั้นการใช้ Messenger จะช่วยให้บริการจัดการการเรียกใช้ได้ทีละรายการ หากบริการของคุณจำเป็นต้องเป็นแบบมัลติเธรด ให้ใช้ AIDL เพื่อกำหนดอินเทอร์เฟซ
สรุปวิธีใช้ Messenger มีดังนี้
- บริการจะใช้
Handlerที่รับ Callback สำหรับการโทรแต่ละครั้งจากไคลเอ็นต์ - บริการจะใช้
Handlerเพื่อสร้างออบเจ็กต์Messenger(ซึ่งเป็นการอ้างอิงถึงHandler) Messengerจะสร้างIBinderที่บริการจะส่งกลับไปยังไคลเอ็นต์จากonBind()- ไคลเอ็นต์ใช้
IBinderเพื่อสร้างอินสแตนซ์Messenger(ซึ่งอ้างอิงHandlerของบริการ) ซึ่งไคลเอ็นต์ใช้เพื่อส่งออบเจ็กต์Messageไปยังบริการ - บริการจะได้รับ
Messageแต่ละรายการในHandlerโดยเฉพาะในเมธอดhandleMessage()
ด้วยวิธีนี้ ไคลเอ็นต์จะไม่มีเมธอดในการเรียกใช้บริการ แต่ไคลเอ็นต์จะส่งข้อความ (ออบเจ็กต์ Message) ที่บริการได้รับใน Handler แทน
ตัวอย่างบริการที่ใช้อินเทอร์เฟซ Messenger มีดังนี้
Kotlin
/** Command to the service to display a message. */ private const val MSG_SAY_HELLO = 1 class MessengerService : Service() { /** * Target we publish for clients to send messages to IncomingHandler. */ private lateinit var mMessenger: Messenger /** * Handler of incoming messages from clients. */ internal class IncomingHandler( context: Context, private val applicationContext: Context = context.applicationContext ) : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { MSG_SAY_HELLO -> Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show() else -> super.handleMessage(msg) } } } /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ override fun onBind(intent: Intent): IBinder? { Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show() mMessenger = Messenger(IncomingHandler(this)) return mMessenger.binder } }
Java
public class MessengerService extends Service { /** * Command to the service to display a message. */ static final int MSG_SAY_HELLO = 1; /** * Handler of incoming messages from clients. */ static class IncomingHandler extends Handler { private Context applicationContext; IncomingHandler(Context context) { applicationContext = context.getApplicationContext(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SAY_HELLO: Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ Messenger mMessenger; /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onBind(Intent intent) { Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); mMessenger = new Messenger(new IncomingHandler(this)); return mMessenger.getBinder(); } }
เมธอด handleMessage() ใน Handler คือตำแหน่งที่บริการได้รับ Message ขาเข้าและตัดสินใจว่าจะดำเนินการอย่างไร โดยอิงตามสมาชิก what
สิ่งที่ไคลเอ็นต์ต้องทำมีเพียงสร้าง Messenger โดยอิงตาม IBinder ที่บริการแสดงผล และส่งข้อความโดยใช้ send() ตัวอย่างเช่น ต่อไปนี้เป็นกิจกรรมที่เชื่อมโยงกับบริการและส่งข้อความ MSG_SAY_HELLO ไปยังบริการ
Kotlin
class ActivityMessenger : Activity() { /** Messenger for communicating with the service. */ private var mService: Messenger? = null /** Flag indicating whether we have called bind on the service. */ private var bound: Boolean = false /** * Class for interacting with the main interface of the service. */ private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // This is called when the connection with the service has been // established, giving us the object we can use to // interact with the service. We are communicating with the // service using a Messenger, so here we get a client-side // representation of that from the raw IBinder object. mService = Messenger(service) bound = true } override fun onServiceDisconnected(className: ComponentName) { // This is called when the connection with the service has been // unexpectedly disconnected—that is, its process crashed. mService = null bound = false } } fun sayHello(v: View) { if (!bound) return // Create and send a message to the service, using a supported 'what' value. val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0) try { mService?.send(msg) } catch (e: RemoteException) { e.printStackTrace() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } override fun onStart() { super.onStart() // Bind to the service. Intent(this, MessengerService::class.java).also { intent -> bindService(intent, mConnection, Context.BIND_AUTO_CREATE) } } override fun onStop() { super.onStop() // Unbind from the service. if (bound) { unbindService(mConnection) mService = null bound = false } } }
Java
public class ActivityMessenger extends Activity { /** Messenger for communicating with the service. */ Messenger mService = null; /** Flag indicating whether we have called bind on the service. */ boolean bound; /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the object we can use to // interact with the service. We are communicating with the // service using a Messenger, so here we get a client-side // representation of that from the raw IBinder object. mService = new Messenger(service); bound = true; } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected—that is, its process crashed. mService = null; bound = false; } }; public void sayHello(View v) { if (!bound) return; // Create and send a message to the service, using a supported 'what' value. Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); // Bind to the service. bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // Unbind from the service. if (bound) { unbindService(mConnection); mService = null; bound = false; } } }
ตัวอย่างนี้ไม่ได้แสดงวิธีที่บริการสามารถตอบกลับไคลเอ็นต์
หากต้องการให้
บริการตอบกลับ คุณต้องสร้าง Messenger ในไคลเอ็นต์ด้วย
เมื่อไคลเอ็นต์ได้รับ Callback onServiceConnected() ไคลเอ็นต์จะส่ง Message ไปยังบริการที่มี Messenger ของไคลเอ็นต์ในพารามิเตอร์ replyTo ของเมธอด send()
คุณดูตัวอย่างวิธีให้บริการรับส่งข้อความแบบ 2 ทางได้ในตัวอย่าง
MessengerService.java (บริการ) และ
MessengerServiceActivities.java (ไคลเอ็นต์)
เชื่อมโยงกับบริการ
คอมโพเนนต์ของแอปพลิเคชัน (ไคลเอ็นต์) สามารถเชื่อมโยงกับบริการได้โดยการเรียกใช้ bindService() จากนั้นระบบ Android จะเรียกใช้เมธอด onBind() ของบริการ ซึ่งจะแสดงผล IBinder สำหรับการโต้ตอบกับบริการ
การเชื่อมโยงเป็นแบบอะซิงโครนัส และ bindService() จะแสดงผลทันทีโดยไม่แสดงผล IBinder ไปยังไคลเอ็นต์ หากต้องการรับ IBinder ไคลเอ็นต์ต้องสร้างอินสแตนซ์ของ ServiceConnection และส่งไปยัง bindService() ServiceConnection มีเมธอด Callback ที่ระบบเรียกใช้เพื่อส่ง IBinder
หมายเหตุ: เฉพาะกิจกรรม บริการ และ Content Provider เท่านั้นที่เชื่อมโยงกับบริการได้ คุณไม่สามารถเชื่อมโยงกับบริการจาก Broadcast Receiver ได้
หากต้องการเชื่อมโยงกับบริการจากไคลเอ็นต์ ให้ทำตามขั้นตอนต่อไปนี้
- ใช้
ServiceConnectionการติดตั้งใช้งานต้องลบล้างเมธอด Callback 2 รายการต่อไปนี้
onServiceConnected()- ระบบจะเรียกใช้เมธอดนี้เพื่อส่ง
IBinderที่แสดงผลโดยเมธอดonBind()ของบริการ onServiceDisconnected()- ระบบ Android จะเรียกใช้เมธอดนี้เมื่อการเชื่อมต่อกับบริการขาดหายไปโดยไม่คาดคิด เช่น เมื่อบริการขัดข้องหรือถูกปิด ระบบจะไม่เรียกใช้เมธอดนี้เมื่อไคลเอ็นต์เลิกเชื่อมโยง
- เรียกใช้
bindService()โดยส่งผ่านการใช้งานServiceConnectionหมายเหตุ: หากเมธอดแสดงผลเป็นเท็จ แสดงว่าไคลเอ็นต์ไม่มีการเชื่อมต่อที่ถูกต้องกับบริการ อย่างไรก็ตาม ให้เรียกใช้
unbindService()ในไคลเอ็นต์ มิฉะนั้น ไคลเอ็นต์จะป้องกันไม่ให้บริการปิดตัวลงเมื่อไม่มีการใช้งาน - เมื่อระบบเรียกใช้เมธอด Callback
onServiceConnected()คุณจะเริ่มโทรหาบริการได้โดยใช้เมธอดที่อินเทอร์เฟซกำหนดไว้ - หากต้องการยกเลิกการเชื่อมต่อจากบริการ ให้โทร
unbindService()หากไคลเอ็นต์ยังคงเชื่อมโยงกับบริการเมื่อแอปทำลายไคลเอ็นต์ การทำลายจะทำให้ไคลเอ็นต์ยกเลิกการเชื่อมโยง แนวทางปฏิบัติที่ดีกว่าคือการเลิกเชื่อมโยงไคลเอ็นต์ทันทีที่โต้ตอบกับบริการเสร็จสิ้น การดำเนินการนี้จะช่วยให้ปิดบริการที่ไม่มีการใช้งานได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับเวลาที่เหมาะสมในการเชื่อมโยงและยกเลิกการเชื่อมโยงได้ที่ส่วนหมายเหตุเพิ่มเติม
ตัวอย่างต่อไปนี้จะเชื่อมต่อไคลเอ็นต์กับบริการที่สร้างไว้ก่อนหน้านี้โดยขยายคลาส Binder ดังนั้นสิ่งที่ต้องทำมีเพียงแคสต์ IBinder ที่แสดงผลไปยังคลาส LocalBinder และขออินสแตนซ์ LocalService ดังนี้
Kotlin
var mService: LocalService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // Because we have bound to an explicit // service that is running in our own process, we can // cast its IBinder to a concrete class and directly access it. val binder = service as LocalService.LocalBinder mService = binder.getService() mBound = true } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "onServiceDisconnected") mService = null mBound = false } }
Java
LocalService mService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established. public void onServiceConnected(ComponentName className, IBinder service) { // Because we have bound to an explicit // service that is running in our own process, we can // cast its IBinder to a concrete class and directly access it. LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } // Called when the connection with the service disconnects unexpectedly. public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "onServiceDisconnected"); mService = null; mBound = false; } };
เมื่อใช้ ServiceConnection นี้ ไคลเอ็นต์จะเชื่อมโยงกับบริการได้โดยส่งไปยัง bindService() ดังที่แสดงในตัวอย่างต่อไปนี้
Kotlin
Intent(this, LocalService::class.java).also { intent -> bindService(intent, connection, Context.BIND_AUTO_CREATE) }
Java
Intent intent = new Intent(this, LocalService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE);
- พารามิเตอร์แรกของ
bindService()คือIntentที่ระบุชื่อบริการที่จะเชื่อมโยงอย่างชัดเจนข้อควรระวัง: หากคุณใช้ Intent เพื่อเชื่อมโยงกับ
Serviceให้ตรวจสอบว่าแอปปลอดภัยโดยใช้ Intent ที่ชัดเจน การใช้ Intent โดยนัยเพื่อเริ่มบริการเป็นอันตรายด้านความปลอดภัยเนื่องจากคุณไม่สามารถแน่ใจได้ว่าบริการใดจะตอบสนองต่อ Intent และผู้ใช้ไม่สามารถดูได้ว่าบริการใดเริ่มทำงาน ตั้งแต่ Android 5.0 (ระดับ API 21) เป็นต้นไป ระบบจะส่งข้อยกเว้นหากคุณเรียกใช้bindService()ด้วย Intent แบบไม่เจาะจงปลายทาง - พารามิเตอร์ที่ 2 คือออบเจ็กต์
ServiceConnection - พารามิเตอร์ที่ 3 คือแฟล็กที่ระบุตัวเลือกสำหรับการเชื่อมโยง ซึ่งโดยปกติจะเป็น
BIND_AUTO_CREATEเพื่อสร้างบริการหากยังไม่มีอยู่ ค่าอื่นๆ ที่เป็นไปได้คือBIND_DEBUG_UNBIND,BIND_NOT_FOREGROUNDหรือ0สำหรับไม่มี
หมายเหตุเพิ่มเติม
หมายเหตุสำคัญเกี่ยวกับการเชื่อมโยงกับบริการมีดังนี้
- ดักจับข้อยกเว้น
DeadObjectExceptionเสมอ ซึ่งจะแสดงขึ้นเมื่อการเชื่อมต่อขาดหาย นี่เป็นข้อยกเว้นเดียวที่แสดงโดยเมธอดระยะไกล - ระบบจะนับการอ้างอิงออบเจ็กต์ในกระบวนการต่างๆ
- โดยปกติแล้ว คุณจะจับคู่การเชื่อมโยงและการยกเลิกการเชื่อมโยงระหว่างช่วงเวลาการเปิดและการปิดที่ตรงกันของวงจรของไคลเอ็นต์ ดังที่อธิบายไว้ในตัวอย่างต่อไปนี้
- หากคุณต้องการโต้ตอบกับบริการเฉพาะในขณะที่กิจกรรมของคุณปรากฏอยู่ ให้เชื่อมโยงระหว่าง
onStart()และยกเลิกการเชื่อมโยงระหว่างonStop() - หากต้องการให้กิจกรรมได้รับการตอบสนองแม้ว่าจะหยุดทำงานในเบื้องหลัง ให้เชื่อมโยงระหว่าง
onCreate()และยกเลิกการเชื่อมโยงระหว่างonDestroy()โปรดทราบว่าการดำเนินการนี้หมายความว่ากิจกรรมของคุณต้องใช้บริการตลอดเวลาที่ทำงานอยู่ แม้จะอยู่ในเบื้องหลังก็ตาม ดังนั้นเมื่อบริการอยู่ในกระบวนการอื่น คุณจะเพิ่มน้ำหนักของกระบวนการและมีแนวโน้มที่ระบบจะหยุดทำงานมากขึ้น
หมายเหตุ: โดยปกติแล้วคุณไม่ควรเชื่อมโยงและยกเลิกการเชื่อมโยงระหว่าง Callback
onResume()และonPause()ของกิจกรรม เนื่องจาก Callback เหล่านี้จะเกิดขึ้นในทุกการเปลี่ยนผ่านของวงจรชีวิต พยายามให้มีการประมวลผลที่เกิดขึ้นในช่วงการเปลี่ยนผ่านเหล่านี้ให้น้อยที่สุดนอกจากนี้ หากกิจกรรมหลายรายการในแอปพลิเคชันเชื่อมโยงกับบริการเดียวกันและมีการเปลี่ยนระหว่างกิจกรรม 2 รายการ บริการอาจถูกทำลายและสร้างขึ้นใหม่เมื่อกิจกรรมปัจจุบันยกเลิกการเชื่อมโยง (ระหว่างหยุดชั่วคราว) ก่อนที่กิจกรรมถัดไปจะเชื่อมโยง (ระหว่างกลับมาทำงานอีกครั้ง) การเปลี่ยนผ่านกิจกรรมนี้สำหรับวิธีที่กิจกรรมประสานงานวงจรของตนเองมีอธิบายไว้ในวงจรกิจกรรม
- หากคุณต้องการโต้ตอบกับบริการเฉพาะในขณะที่กิจกรรมของคุณปรากฏอยู่ ให้เชื่อมโยงระหว่าง
ดูโค้ดตัวอย่างเพิ่มเติมที่แสดงวิธีเชื่อมโยงกับบริการได้ที่คลาส
RemoteService.java ใน ApiDemos
จัดการวงจรของบริการที่มีผลผูกพัน
เมื่อบริการไม่ได้เชื่อมโยงกับไคลเอ็นต์ใดๆ ระบบ Android จะทำลายบริการนั้น (เว้นแต่จะเริ่มต้นโดยใช้ startService()) ดังนั้นคุณจึงไม่ต้องจัดการวงจรของบริการหากเป็นบริการที่มีผลผูกพันเท่านั้น ระบบ Android จะจัดการให้คุณโดยพิจารณาว่ามีการเชื่อมโยงกับไคลเอ็นต์หรือไม่
อย่างไรก็ตาม หากเลือกใช้เมธอด Callback onStartCommand() คุณต้องหยุดบริการอย่างชัดแจ้ง เนื่องจากตอนนี้ระบบถือว่าบริการเริ่มทำงานแล้ว ในกรณีนี้ บริการจะทำงานจนกว่าบริการจะหยุดตัวเองด้วย stopSelf() หรือคอมโพเนนต์อื่นเรียกใช้ stopService() ไม่ว่าจะเชื่อมโยงกับไคลเอ็นต์ใดก็ตาม
นอกจากนี้ หากบริการของคุณเริ่มต้นและยอมรับการเชื่อมโยง เมื่อระบบเรียกใช้เมธอด onUnbind() คุณสามารถเลือกที่จะแสดงผล true ได้หากต้องการรับการเรียกใช้ onRebind() ในครั้งถัดไปที่ไคลเอ็นต์เชื่อมโยงกับบริการ onRebind() จะแสดงผลเป็นโมฆะ แต่ไคลเอ็นต์จะยังคงได้รับ IBinder ใน Callback onServiceConnected()
รูปภาพต่อไปนี้แสดงตรรกะสำหรับวงจรประเภทนี้
ดูข้อมูลเพิ่มเติมเกี่ยวกับวงจรของบริการที่เริ่มต้นได้ที่ภาพรวมของบริการ