Service
คือคอมโพเนนต์แอปพลิเคชันที่ดําเนินการต่อเนื่องเป็นเวลานานในเบื้องหลังได้ โดยไม่มีอินเทอร์เฟซผู้ใช้ เมื่อเริ่มต้นแล้ว บริการอาจทํางานต่อไปอีกระยะหนึ่ง แม้ว่าผู้ใช้จะเปลี่ยนไปใช้แอปพลิเคชันอื่นแล้วก็ตาม นอกจากนี้ คอมโพเนนต์ยังเชื่อมโยงกับบริการเพื่อโต้ตอบกับบริการนั้นและดำเนินการการสื่อสารระหว่างกระบวนการ (IPC) ได้ด้วย ตัวอย่างเช่น บริการสามารถจัดการธุรกรรมเครือข่าย เล่นเพลง ดำเนินการ I/O ของไฟล์ หรือโต้ตอบกับผู้ให้บริการเนื้อหาได้ทั้งหมดจากเบื้องหลัง
ข้อควรระวัง: บริการจะทำงานในเธรดหลักของกระบวนการโฮสติ้ง บริการจะไม่สร้างเธรดของตัวเองและจะไม่ทำงานในกระบวนการแยกต่างหาก เว้นแต่คุณจะระบุไว้เป็นอย่างอื่น คุณควรเรียกใช้การดำเนินการที่บล็อกในเธรดแยกต่างหากภายในบริการเพื่อหลีกเลี่ยงข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR)
ประเภทบริการ
บริการ 3 ประเภทดังกล่าวมีดังนี้
- เบื้องหน้า
-
บริการที่ทำงานอยู่เบื้องหน้าจะดำเนินการบางอย่างที่ผู้ใช้สังเกตเห็นได้ เช่น แอปเสียงจะใช้บริการที่ทำงานอยู่เบื้องหน้าเพื่อเล่นแทร็กเสียง บริการที่ทำงานอยู่เบื้องหน้าต้องแสดงการแจ้งเตือน บริการที่ทำงานอยู่เบื้องหน้าจะทำงานต่อไปแม้ว่าผู้ใช้ไม่ได้โต้ตอบกับแอป
เมื่อใช้บริการที่ทำงานอยู่เบื้องหน้า คุณต้องแสดงการแจ้งเตือนเพื่อให้ผู้ใช้ทราบว่าบริการกำลังทำงานอยู่ การแจ้งเตือนนี้จะปิดไปไม่ได้ เว้นแต่ว่าบริการจะหยุดหรือนำออกจากเบื้องหน้า
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีกำหนดค่าบริการที่ทำงานอยู่เบื้องหน้าในแอป
หมายเหตุ: WorkManager API มีวิธีกำหนดเวลางานที่มีความยืดหยุ่น และสามารถเรียกใช้งานเหล่านี้เป็นบริการที่ทำงานอยู่เบื้องหน้าได้หากจำเป็น ในหลายกรณี การใช้ WorkManager นั้นดีกว่าการใช้บริการที่ทำงานอยู่เบื้องหน้าโดยตรง
- ฉากหลัง
- บริการที่ทำงานอยู่เบื้องหลังจะดำเนินการที่ผู้ใช้ไม่สังเกตเห็นโดยตรง ตัวอย่างเช่น หากแอปใช้บริการเพื่อบีบอัดพื้นที่เก็บข้อมูล โดยทั่วไปบริการดังกล่าวจะเป็นบริการที่ทำงานอยู่เบื้องหลัง
หมายเหตุ: หากแอปกำหนดเป้าหมายเป็น API ระดับ 26 ขึ้นไป ระบบจะจำกัดบริการที่ทำงานอยู่เบื้องหลังเมื่อแอปไม่ได้อยู่เบื้องหน้า ตัวอย่างเช่น ในกรณีส่วนใหญ่ คุณไม่ควรเข้าถึงข้อมูลตำแหน่งจากเบื้องหลัง แต่ให้กำหนดเวลางานโดยใช้ WorkManager แทน
- เชื่อมโยงแล้ว
- บริการจะเชื่อมโยงเมื่อคอมโพเนนต์แอปพลิเคชันเชื่อมโยงกับบริการดังกล่าวโดยการเรียกใช้
bindService()
บริการที่เชื่อมโยงมีอินเทอร์เฟซไคลเอ็นต์-เซิร์ฟเวอร์ที่ช่วยให้คอมโพเนนต์โต้ตอบกับบริการ ส่งคําขอ รับผลลัพธ์ และทําสิ่งต่างๆ ดังกล่าวข้ามโปรเซสด้วยการสื่อสารระหว่างโปรเซส (IPC) บริการที่เชื่อมโยงจะทํางานก็ต่อเมื่อคอมโพเนนต์แอปพลิเคชันอื่นเชื่อมโยงกับบริการนั้น คอมโพเนนต์หลายรายการสามารถเชื่อมโยงกับบริการพร้อมกันได้ แต่เมื่อยกเลิกการเชื่อมโยงทั้งหมดแล้ว บริการก็จะถูกทำลาย
แม้ว่าโดยทั่วไปเอกสารนี้จะกล่าวถึงบริการที่เริ่มต้นและบริการที่เชื่อมโยงแยกกัน แต่บริการของคุณจะทํางานได้ 2 แบบ คือสามารถเริ่มต้น (เพื่อทํางานแบบไม่จํากัดเวลา) และอนุญาตการเชื่อมโยงได้ เพียงแค่คุณใช้เมธอดการเรียกกลับ 2 รายการหรือไม่ นั่นคือ onStartCommand()
เพื่ออนุญาตให้คอมโพเนนต์เริ่มการทำงาน และ onBind()
เพื่ออนุญาตให้มีการเชื่อมโยง
ไม่ว่าบริการจะเริ่มต้น เชื่อมโยง หรือทั้ง 2 อย่าง คอมโพเนนต์แอปพลิเคชันใดๆ ก็สามารถใช้บริการ (แม้กระทั่งจากแอปพลิเคชันแยกต่างหาก) ในลักษณะเดียวกับที่คอมโพเนนต์ใดๆ สามารถใช้กิจกรรมได้ โดยการเริ่มต้นด้วย Intent
อย่างไรก็ตาม คุณสามารถประกาศบริการเป็นส่วนตัวในไฟล์ Manifest และบล็อกการเข้าถึงจากแอปพลิเคชันอื่นๆ ได้
โปรดดูข้อมูลเพิ่มเติมในส่วนการประกาศบริการในไฟล์ Manifest
การเลือกระหว่างบริการกับชุดข้อความ
บริการเป็นเพียงคอมโพเนนต์ที่ทำงานอยู่เบื้องหลังได้ แม้ว่าผู้ใช้จะไม่โต้ตอบกับแอปพลิเคชันก็ตาม คุณจึงควรสร้างบริการก็ต่อเมื่อต้องการเท่านั้น
หากต้องทํางานนอกเธรดหลัก แต่ทําเฉพาะในขณะที่ผู้ใช้โต้ตอบกับแอปพลิเคชัน คุณควรสร้างเธรดใหม่ในบริบทของคอมโพเนนต์แอปพลิเคชันอื่นแทน ตัวอย่างเช่น หากต้องการเล่นเพลงเฉพาะในขณะที่กิจกรรมทำงานอยู่ คุณอาจสร้างเธรดใน onCreate()
เริ่มเธรดใน onStart()
และหยุดเธรดใน onStop()
นอกจากนี้ ให้พิจารณาใช้พูลเธรดและโปรแกรมดำเนินการจากแพ็กเกจ java.util.concurrent
หรือ โคโริวทีน Kotlin แทนคลาส Thread
แบบดั้งเดิม ดูข้อมูลเพิ่มเติมเกี่ยวกับการย้ายการดำเนินการไปยังเธรดเบื้องหลังได้ในเอกสารการแยกเธรดใน Android
โปรดทราบว่าหากใช้บริการ บริการจะยังคงทำงานในเธรดหลักของแอปพลิเคชันโดยค่าเริ่มต้น คุณจึงควรสร้างเธรดใหม่ภายในบริการหากบริการดำเนินการอย่างหนักหน่วงหรือมีการบล็อก
ข้อมูลเบื้องต้น
หากต้องการสร้างบริการ คุณต้องสร้างคลาสย่อยของ Service
หรือใช้คลาสย่อยที่มีอยู่ ในการใช้งาน คุณต้องลบล้างเมธอดการเรียกกลับบางรายการที่จัดการแง่มุมสําคัญของวงจรบริการ และระบุกลไกที่อนุญาตให้คอมโพเนนต์เชื่อมโยงกับบริการ หากเหมาะสม ต่อไปนี้คือเมธอดการเรียกคืนที่สำคัญที่สุดที่คุณควรเขียนทับ
onStartCommand()
- ระบบจะเรียกใช้เมธอดนี้โดยการเรียก
startService()
เมื่อคอมโพเนนต์อื่น (เช่น กิจกรรม) ขอให้เริ่มบริการ เมื่อเรียกใช้เมธอดนี้ ระบบจะเริ่มบริการและสามารถทำงานในเบื้องหลังได้แบบไม่จำกัด หากคุณใช้วิธีนี้ คุณมีหน้าที่รับผิดชอบในการหยุดบริการเมื่อดำเนินการเสร็จสิ้นแล้วโดยโทรไปที่stopSelf()
หรือstopService()
หากต้องการระบุการเชื่อมโยงเท่านั้น คุณไม่จำเป็นต้องใช้วิธีการนี้ onBind()
- ระบบจะเรียกใช้เมธอดนี้โดยการเรียก
bindService()
เมื่อคอมโพเนนต์อื่นต้องการเชื่อมโยงกับบริการ (เช่น เพื่อดำเนินการ RPC) ในการใช้งานเมธอดนี้ คุณต้องระบุอินเทอร์เฟซที่ลูกค้าใช้เพื่อสื่อสารกับบริการโดยแสดงผลIBinder
คุณต้องใช้งานเมธอดนี้เสมอ แต่หากไม่ต้องการอนุญาตการเชื่อมโยง คุณควรแสดงผลเป็นค่า null onCreate()
- ระบบจะเรียกใช้เมธอดนี้เพื่อดำเนินการตามขั้นตอนการตั้งค่าแบบครั้งเดียวเมื่อสร้างบริการเป็นครั้งแรก (ก่อนที่จะเรียกใช้
onStartCommand()
หรือonBind()
) หากบริการทำงานอยู่แล้ว ระบบจะไม่เรียกใช้เมธอดนี้ onDestroy()
- ระบบจะเรียกใช้เมธอดนี้เมื่อไม่มีการใช้บริการอีกต่อไปและกำลังถูกทำลาย บริการของคุณควรใช้วิธีนี้เพื่อล้างทรัพยากร เช่น เทรด โปรแกรมฟังที่ลงทะเบียนไว้ หรือตัวรับ นี่คือการเรียกใช้ครั้งสุดท้ายที่บริการได้รับ
หากคอมโพเนนต์เริ่มบริการด้วยการเรียกใช้ startService()
(ซึ่งส่งผลให้มีการเรียกใช้ onStartCommand()
) บริการจะยังคงทำงานต่อไปจนกว่าจะหยุดด้วยตนเองด้วย stopSelf()
หรือคอมโพเนนต์อื่นหยุดบริการด้วยการเรียกใช้ stopService()
หากคอมโพเนนต์เรียกใช้ bindService()
เพื่อสร้างบริการและไม่ได้เรียกใช้ onStartCommand()
บริการจะทํางานก็ต่อเมื่อคอมโพเนนต์เชื่อมโยงกับบริการเท่านั้น หลังจากยกเลิกการเชื่อมโยงบริการกับไคลเอ็นต์ทั้งหมดแล้ว ระบบจะทำลายบริการนั้น
ระบบ Android จะหยุดบริการก็ต่อเมื่อหน่วยความจำเหลือน้อยและต้องกู้คืนทรัพยากรของระบบสำหรับกิจกรรมที่ผู้ใช้โฟกัสอยู่ หากบริการเชื่อมโยงกับกิจกรรมที่มีโฟกัสของผู้ใช้ ระบบก็มีโอกาสที่จะหยุดบริการนั้นน้อยลง หากมีการประกาศให้บริการทำงานอยู่เบื้องหน้า ระบบก็แทบจะไม่หยุดบริการนั้นเลย
หากบริการเริ่มทำงานและทำงานเป็นเวลานาน ระบบจะลดลำดับของบริการในรายการงานเบื้องหลังเมื่อเวลาผ่านไป และบริการมีแนวโน้มสูงที่จะถูกหยุดทำงาน หากบริการเริ่มทำงาน คุณต้องออกแบบบริการให้จัดการกับการรีสตาร์ทโดยระบบได้อย่างราบรื่น หากระบบหยุดบริการของคุณ ระบบจะรีสตาร์ทบริการทันทีที่มีทรัพยากรพร้อมใช้งาน แต่ทั้งนี้ขึ้นอยู่กับค่าที่คุณแสดงผลจาก onStartCommand()
ด้วย ดูข้อมูลเพิ่มเติมเกี่ยวกับกรณีที่ระบบอาจทำลายบริการได้ที่เอกสารกระบวนการและเทรดดดิ้ง
ในส่วนต่อไปนี้ คุณจะเห็นวิธีสร้างเมธอดบริการ startService()
และ bindService()
รวมถึงวิธีใช้เมธอดเหล่านั้นจากคอมโพเนนต์แอปพลิเคชันอื่นๆ
การประกาศบริการในไฟล์ Manifest
คุณต้องประกาศบริการทั้งหมดในไฟล์ Manifest ของแอปพลิเคชัน เช่นเดียวกับที่ประกาศสำหรับกิจกรรมและคอมโพเนนต์อื่นๆ
หากต้องการประกาศบริการ ให้เพิ่มองค์ประกอบ <service>
เป็นองค์ประกอบย่อยขององค์ประกอบ <application>
มีตัวอย่างดังต่อไปนี้
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
ดูข้อมูลเพิ่มเติมเกี่ยวกับการประกาศบริการในไฟล์ Manifest ได้ที่การอ้างอิงองค์ประกอบ <service>
ยังมีแอตทริบิวต์อื่นๆ ที่คุณใส่ไว้ในองค์ประกอบ <service>
เพื่อกำหนดพร็อพเพอร์ตี้ได้ เช่น สิทธิ์ที่จําเป็นในการเริ่มบริการและกระบวนการที่บริการควรทํางาน แอตทริบิวต์ android:name
เป็นแอตทริบิวต์เดียวที่ต้องระบุ ซึ่งจะระบุชื่อคลาสของบริการ หลังจากเผยแพร่แอปพลิเคชันแล้ว โปรดอย่าเปลี่ยนชื่อนี้เพื่อหลีกเลี่ยงความเสี่ยงที่จะเกิดข้อผิดพลาดในโค้ดเนื่องจากต้องอาศัย Intent ที่ชัดเจนเพื่อเริ่มหรือเชื่อมโยงบริการ (อ่านบล็อกโพสต์สิ่งที่เปลี่ยนแปลงไม่ได้)
ข้อควรระวัง: ใช้ Intent แบบชัดแจ้งเสมอเมื่อเริ่ม Service
และอย่าประกาศตัวกรอง Intent สำหรับบริการของคุณเพื่อให้แอปปลอดภัย การใช้ Intent แบบไม่เจาะจงปลายทางเพื่อเริ่มบริการเป็นอันตรายด้านความปลอดภัยเนื่องจากคุณไม่สามารถมั่นใจได้ว่าบริการใดจะตอบสนองต่อ Intent และผู้ใช้จะไม่เห็นบริการใดที่เริ่มทำงาน ตั้งแต่ Android 5.0 (API ระดับ 21) เป็นต้นไป ระบบจะแสดงข้อยกเว้นหากคุณเรียกใช้ bindService()
ด้วย Intent ที่ไม่ชัด
คุณสามารถตรวจสอบว่าบริการพร้อมให้บริการสำหรับแอปของคุณเท่านั้นได้ด้วยการใส่แอตทริบิวต์ android:exported
และตั้งค่าเป็น false
ซึ่งจะหยุดไม่ให้แอปอื่นๆ เริ่มบริการของคุณได้อย่างมีประสิทธิภาพ แม้ว่าจะใช้ Intent แบบชัดแจ้งก็ตาม
หมายเหตุ:
ผู้ใช้จะเห็นบริการที่ทำงานอยู่ในอุปกรณ์ หากเห็นบริการที่ไม่รู้จักหรือไม่เชื่อถือ ผู้ใช้สามารถหยุดบริการดังกล่าวได้ คุณต้องเพิ่มแอตทริบิวต์ android:description
ลงในองค์ประกอบ <service>
ในไฟล์ Manifest ของแอปเพื่อไม่ให้ผู้ใช้หยุดบริการของคุณโดยไม่ตั้งใจ ในคำอธิบาย ให้เขียนประโยคสั้นๆ ที่อธิบายว่าบริการทำอะไรและมีประโยชน์อย่างไร
การสร้างบริการที่เริ่มต้นแล้ว
บริการที่เริ่มแล้วคือบริการที่คอมโพเนนต์อื่นเริ่มโดยเรียก startService()
ซึ่งจะส่งผลให้มีการเรียกใช้เมธอด onStartCommand()
ของบริการ
เมื่อเริ่มบริการ บริการจะมีวงจรที่ไม่ขึ้นอยู่กับคอมโพเนนต์ที่เริ่มบริการ บริการสามารถทำงานในเบื้องหลังได้แบบไม่มีกำหนด แม้ว่าคอมโพเนนต์ที่เริ่มต้นบริการจะหยุดทำงานไปแล้วก็ตาม ดังนั้น บริการควรหยุดเองเมื่องานเสร็จสิ้นโดยการเรียกใช้ stopSelf()
หรือคอมโพเนนต์อื่นสามารถหยุดบริการได้โดยเรียกใช้ stopService()
คอมโพเนนต์แอปพลิเคชัน เช่น กิจกรรม สามารถเริ่มบริการได้โดยเรียกใช้ startService()
และส่ง Intent
ที่ระบุบริการและรวมข้อมูลสำหรับบริการที่จะใช้ บริการได้รับ Intent
นี้ในเมธอด onStartCommand()
ตัวอย่างเช่น สมมติว่ากิจกรรมหนึ่งต้องบันทึกข้อมูลบางอย่างลงในฐานข้อมูลออนไลน์ กิจกรรมดังกล่าวสามารถเริ่มบริการที่ใช้ร่วมกันและส่งข้อมูลให้บันทึกได้โดยส่ง Intent ไปยัง startService()
บริการจะรับ Intent ใน onStartCommand()
, เชื่อมต่อกับอินเทอร์เน็ต และทำธุรกรรมฐานข้อมูล เมื่อธุรกรรมเสร็จสมบูรณ์แล้ว บริการจะหยุดทำงานและถูกทำลาย
ข้อควรระวัง: บริการจะทํางานในกระบวนการเดียวกับแอปพลิเคชันที่ประกาศบริการนั้นไว้ และในเธรดหลักของแอปพลิเคชันนั้นโดยค่าเริ่มต้น หากบริการของคุณทํางานอย่างหนักหน่วงหรือบล็อกขณะที่ผู้ใช้โต้ตอบกับกิจกรรมจากแอปพลิเคชันเดียวกัน บริการจะทําให้ประสิทธิภาพของกิจกรรมช้าลง เริ่มชุดข้อความใหม่ภายในบริการเพื่อไม่ให้ประสิทธิภาพของแอปพลิเคชันได้รับผลกระทบ
คลาส Service
เป็นคลาสพื้นฐานสำหรับบริการทั้งหมด เมื่อขยายคลาสนี้ คุณควรสร้างเธรดใหม่เพื่อให้บริการทํางานทั้งหมดได้ โดยบริการจะใช้เธรดหลักของแอปพลิเคชันโดยค่าเริ่มต้น ซึ่งอาจทําให้ประสิทธิภาพของกิจกรรมที่แอปพลิเคชันใช้งานอยู่ช้าลง
เฟรมเวิร์ก Android ยังมีIntentService
คลาสย่อยของ Service
ที่ใช้เธรดสำหรับงานเพื่อจัดการคําขอเริ่มต้นทั้งหมดทีละรายการ ไม่แนะนําให้ใช้คลาสนี้กับแอปใหม่ เนื่องจากจะทํางานได้ไม่ดีใน Android 8 Oreo เป็นต้นไป เนื่องด้วยการเปิดตัวขีดจํากัดการดําเนินการในเบื้องหลัง
นอกจากนี้ ระบบจะเลิกใช้งานฟีเจอร์นี้ตั้งแต่ Android 11 เป็นต้นไป
คุณสามารถใช้ JobIntentService แทน IntentService
ที่ใช้งานร่วมกับ Android เวอร์ชันใหม่ได้
ส่วนต่อไปนี้จะอธิบายวิธีใช้บริการที่กําหนดเองของคุณเอง แต่คุณควรพิจารณาใช้ WorkManager แทนสําหรับ Use Case ส่วนใหญ่ โปรดดูคำแนะนำเกี่ยวกับการประมวลผลในเบื้องหลังบน Android เพื่อดูว่ามีโซลูชันที่เหมาะกับความต้องการของคุณหรือไม่
การขยายคลาส Service
คุณสามารถขยายคลาส Service
เพื่อจัดการ Intent ที่เข้ามาแต่ละรายการ การติดตั้งใช้งานขั้นพื้นฐานอาจมีลักษณะดังนี้
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(); } }
โค้ดตัวอย่างจัดการสายเรียกเข้าทั้งหมดใน onStartCommand()
และโพสต์งานไปยัง Handler
ที่ทำงานบนเธรดในเบื้องหลัง โดยจะทํางานเหมือนกับ IntentService
และประมวลผลคําขอทั้งหมดตามลําดับทีละรายการ
คุณอาจเปลี่ยนโค้ดให้ทำงานในพูลเธรด เช่น หากต้องการเรียกใช้คําขอหลายรายการพร้อมกัน
โปรดทราบว่าเมธอด onStartCommand()
ต้องแสดงผลลัพธ์เป็นจำนวนเต็ม จำนวนเต็มคือค่าที่อธิบายวิธีที่ระบบควรให้บริการต่อไปในกรณีที่ระบบหยุดบริการ ค่าที่แสดงผลจาก onStartCommand()
ต้องเป็นค่าคงที่ค่าใดค่าหนึ่งต่อไปนี้
START_NOT_STICKY
- หากระบบหยุดบริการหลังจากที่
onStartCommand()
แสดงผล อย่าสร้างบริการขึ้นมาใหม่ เว้นแต่จะมีความตั้งใจที่จะแสดงผลที่รอดำเนินการอยู่ ตัวเลือกนี้เป็นตัวเลือกที่ปลอดภัยที่สุดในการหลีกเลี่ยงการเรียกใช้บริการเมื่อไม่จำเป็น และเมื่อแอปพลิเคชันสามารถเริ่มงานที่ยังทำไม่เสร็จอีกครั้งได้ START_STICKY
- หากระบบหยุดบริการหลังจากที่
onStartCommand()
แสดงผล ให้สร้างบริการขึ้นมาใหม่และเรียกใช้onStartCommand()
แต่อย่าส่ง Intent สุดท้ายอีกครั้ง แต่ระบบจะเรียกonStartCommand()
ด้วย Intent แบบ Null แทน เว้นแต่จะมี Intent ที่รอดำเนินการเพื่อเริ่มบริการ ในกรณีดังกล่าว ระบบจะส่ง Intent เหล่านั้น ซึ่งเหมาะสำหรับโปรแกรมเล่นสื่อ (หรือบริการที่คล้ายกัน) ที่ไม่ได้ดำเนินการตามคําสั่ง แต่ทํางานอยู่อย่างไม่มีกำหนดและรองาน START_REDELIVER_INTENT
- หากระบบหยุดบริการหลังจากที่
onStartCommand()
แสดงผล ให้สร้างบริการขึ้นมาใหม่และเรียกใช้onStartCommand()
ด้วย Intent สุดท้ายที่ส่งไปยังบริการ ระบบจะแสดง Intent ที่รอดำเนินการตามลำดับ ซึ่งเหมาะสำหรับบริการที่ทำงานอยู่อย่างต่อเนื่องและควรกลับมาทำงานต่อทันที เช่น การดาวน์โหลดไฟล์
ดูรายละเอียดเพิ่มเติมเกี่ยวกับค่าที่แสดงผลเหล่านี้ได้ในเอกสารอ้างอิงแบบลิงก์สำหรับค่าคงที่แต่ละรายการ
การเริ่มบริการ
คุณเริ่มบริการจากกิจกรรมหรือคอมโพเนนต์แอปพลิเคชันอื่นๆ ได้โดยการส่ง Intent
ไปยัง startService()
หรือ startForegroundService()
ระบบ Android จะเรียกใช้เมธอด onStartCommand()
ของบริการและส่ง Intent
ไปให้ ซึ่งจะระบุบริการที่จะเริ่มต้น
หมายเหตุ: หากแอปกำหนดเป้าหมายเป็น API ระดับ 26 ขึ้นไป ระบบจะจำกัดการใช้หรือสร้างบริการที่ทำงานอยู่เบื้องหลัง เว้นแต่แอปจะทำงานอยู่เบื้องหน้า หากแอปต้องการสร้างบริการที่ทำงานอยู่เบื้องหน้า แอปควรเรียกใช้ startForegroundService()
เมธอดดังกล่าวจะสร้างบริการในเบื้องหลัง แต่เมธอดจะส่งสัญญาณให้ระบบทราบว่าบริการจะโปรโมตตัวเองไปยังเบื้องหน้า เมื่อสร้างบริการแล้ว บริการต้องเรียกใช้เมธอด startForeground()
ภายใน 5 วินาที
ตัวอย่างเช่น กิจกรรมสามารถเริ่มบริการตัวอย่างในส่วนก่อนหน้า (HelloService
) โดยใช้ Intent แบบชัดแจ้งที่มี startService()
ดังที่แสดงที่นี่
Kotlin
startService(Intent(this, HelloService::class.java))
Java
startService(new Intent(this, HelloService.class));
เมธอด startService()
จะแสดงผลทันที และระบบ Android จะเรียกเมธอด onStartCommand()
ของบริการ หากบริการไม่ได้ทำงานอยู่ ระบบจะเรียกใช้ onCreate()
ก่อน แล้วจึงเรียกใช้
onStartCommand()
หากบริการไม่ได้ให้การเชื่อมโยงด้วย Intent ที่ส่งมาพร้อมกับ startService()
จะเป็นโหมดการสื่อสารเพียงโหมดเดียวระหว่างคอมโพเนนต์แอปพลิเคชันกับบริการ อย่างไรก็ตาม หากต้องการให้บริการส่งผลลัพธ์กลับ ไคลเอ็นต์ที่เริ่มบริการสามารถสร้าง PendingIntent
สําหรับการออกอากาศ (ที่มี getBroadcast()
) และส่งไปยังบริการใน Intent
ที่เริ่มบริการ จากนั้นบริการจะใช้การออกอากาศเพื่อแสดงผลลัพธ์
คำขอเริ่มบริการหลายรายการส่งผลให้มีการเรียกใช้ onStartCommand()
ของบริการหลายครั้ง อย่างไรก็ตาม คุณส่งคำขอหยุดบริการ (ด้วย stopSelf()
หรือ stopService()
) ได้เพียงครั้งเดียว
การหยุดบริการ
บริการที่เริ่มแล้วต้องจัดการวงจรของตนเอง กล่าวคือ ระบบจะไม่หยุดหรือทำลายบริการ เว้นแต่จะต้องกู้คืนหน่วยความจำของระบบและบริการยังคงทำงานต่อไปหลังจากที่ onStartCommand()
กลับมา บริการต้องหยุดเองโดยการเรียก stopSelf()
หรือคอมโพเนนต์อื่นสามารถหยุดบริการได้โดยเรียก stopService()
เมื่อมีการขอให้หยุดด้วย stopSelf()
หรือ stopService()
ระบบจะทำลายบริการโดยเร็วที่สุด
หากบริการจัดการคําขอ onStartCommand()
หลายรายการพร้อมกัน คุณไม่ควรหยุดบริการเมื่อประมวลผลคําขอเริ่มแล้ว เนื่องจากคุณอาจได้รับคําขอเริ่มใหม่ (การหยุดเมื่อสิ้นสุดคําขอแรกจะเป็นการสิ้นสุดคําขอที่ 2) หากต้องการหลีกเลี่ยงปัญหานี้ คุณสามารถใช้ stopSelf(int)
เพื่อให้มั่นใจว่าคำขอหยุดบริการจะอิงตามคำขอเริ่มล่าสุดเสมอ กล่าวคือ เมื่อคุณเรียกใช้ stopSelf(int)
คุณจะส่งรหัสของคำขอเริ่มต้น (startId
ที่ส่งไปยัง onStartCommand()
) ให้กับคำขอหยุด จากนั้น หากบริการได้รับคําขอเริ่มต้นใหม่ก่อนที่คุณจะเรียก stopSelf(int)
ได้ รหัสจะไม่ตรงกัน และบริการจะไม่หยุด
ข้อควรระวัง: ตรวจสอบว่าแอปพลิเคชันหยุดให้บริการเมื่อทำงานเสร็จแล้ว เพื่อไม่ให้สิ้นเปลืองทรัพยากรของระบบและพลังงานแบตเตอรี่
คอมโพเนนต์อื่นๆ สามารถหยุดบริการได้โดยเรียกใช้ stopService()
หากจำเป็น แม้ว่าคุณจะเปิดใช้การเชื่อมโยงสําหรับบริการแล้ว คุณก็ต้องหยุดบริการด้วยตนเองเสมอหากบริการได้รับการโทรไปยัง onStartCommand()
ดูข้อมูลเพิ่มเติมเกี่ยวกับวงจรชีวิตของบริการได้ที่ส่วนการจัดการวงจรชีวิตของบริการด้านล่าง
การสร้างบริการที่เชื่อมโยง
บริการที่เชื่อมโยงคือบริการที่อนุญาตให้คอมโพเนนต์แอปพลิเคชันเชื่อมโยงกับบริการดังกล่าวโดยการเรียกใช้ bindService()
เพื่อสร้างการเชื่อมต่อระยะยาว
โดยทั่วไปแล้วจะไม่อนุญาตให้คอมโพเนนต์เริ่มการทำงานโดยเรียกใช้ startService()
สร้างบริการที่เชื่อมโยงเมื่อคุณต้องการโต้ตอบกับบริการจากกิจกรรมและคอมโพเนนต์อื่นๆ ในแอปพลิเคชัน หรือเพื่อแสดงฟังก์ชันบางอย่างของแอปพลิเคชันต่อแอปพลิเคชันอื่นๆ ผ่านการติดต่อระหว่างกระบวนการ (IPC)
หากต้องการสร้างบริการที่เชื่อมโยง ให้ใช้เมธอดการเรียกกลับ onBind()
เพื่อแสดงผล IBinder
ที่กำหนดอินเทอร์เฟซสำหรับการสื่อสารกับบริการ จากนั้นคอมโพเนนต์แอปพลิเคชันอื่นๆ จะเรียกใช้ bindService()
เพื่อดึงข้อมูลอินเทอร์เฟซและเริ่มเรียกใช้เมธอดในบริการ บริการจะทำงานเพื่อให้บริการคอมโพเนนต์แอปพลิเคชันที่เชื่อมโยงกับบริการเท่านั้น ดังนั้นเมื่อไม่มีคอมโพเนนต์ที่เชื่อมโยงกับบริการ ระบบก็จะทำลายบริการนั้น
คุณไม่จําเป็นต้องหยุดบริการที่เชื่อมโยงในลักษณะเดียวกับที่ต้องทำเมื่อเริ่มบริการผ่าน onStartCommand()
หากต้องการสร้างบริการที่เชื่อมโยง คุณต้องกำหนดอินเทอร์เฟซที่ระบุวิธีที่ไคลเอ็นต์สื่อสารกับบริการได้ อินเทอร์เฟซระหว่างบริการกับไคลเอ็นต์นี้ต้องเป็นการใช้งาน IBinder
และเป็นสิ่งที่บริการของคุณต้องแสดงผลจากเมธอดการเรียกกลับ onBind()
หลังจากได้รับ IBinder
แล้ว ลูกค้าจะเริ่มโต้ตอบกับบริการผ่านอินเทอร์เฟซนั้นได้
หลายไคลเอ็นต์สามารถเชื่อมโยงกับบริการพร้อมกันได้ เมื่อไคลเอ็นต์โต้ตอบกับบริการเสร็จแล้ว ก็จะเรียก unbindService()
เพื่อยกเลิกการเชื่อมโยง
เมื่อไม่มีไคลเอ็นต์ที่เชื่อมโยงกับบริการ ระบบจะทำลายบริการนั้น
การติดตั้งใช้งานบริการที่เชื่อมโยงทำได้หลายวิธี และการติดตั้งใช้งานมีความซับซ้อนกว่าบริการที่เริ่มต้น ด้วยเหตุนี้ การพูดคุยเกี่ยวกับบริการที่เชื่อมโยงจึงปรากฏในเอกสารแยกต่างหากเกี่ยวกับบริการที่เชื่อมโยง
การส่งการแจ้งเตือนไปยังผู้ใช้
เมื่อบริการทํางานอยู่ บริการจะแจ้งเตือนผู้ใช้เกี่ยวกับเหตุการณ์ได้โดยใช้การแจ้งเตือนแถบแสดงข้อความหรือการแจ้งเตือนแถบสถานะ
การแจ้งเตือนในแถบนําทางคือข้อความที่ปรากฏบนพื้นผิวของหน้าต่างปัจจุบันเพียงชั่วครู่ก่อนจะหายไป การแจ้งเตือนในแถบสถานะจะแสดงไอคอนในแถบสถานะพร้อมข้อความ ซึ่งผู้ใช้สามารถเลือกเพื่อดำเนินการ (เช่น เริ่มกิจกรรม)
โดยทั่วไปแล้ว การแจ้งเตือนในแถบสถานะเป็นเทคนิคที่ดีที่สุดที่จะใช้เมื่องานเบื้องหลัง เช่น การดาวน์โหลดไฟล์ เสร็จสมบูรณ์แล้ว และผู้ใช้สามารถดำเนินการกับงานดังกล่าวได้ เมื่อผู้ใช้เลือกการแจ้งเตือนจากมุมมองแบบขยาย การแจ้งเตือนจะเริ่มกิจกรรมได้ (เช่น เพื่อแสดงไฟล์ที่ดาวน์โหลด)
การจัดการวงจรของบริการ
วงจรชีวิตของบริการนั้นง่ายกว่าวงจรชีวิตของกิจกรรมมาก อย่างไรก็ตาม สิ่งที่สำคัญยิ่งกว่าคือคุณควรให้ความสำคัญกับวิธีสร้างและทำลายบริการ เนื่องจากบริการอาจทำงานอยู่เบื้องหลังโดยที่ผู้ใช้ไม่รู้ตัว
วงจรบริการตั้งแต่สร้างจนถึงทำลายอาจเป็นไปตามเส้นทางใดเส้นทางหนึ่งต่อไปนี้
- บริการที่เริ่มแล้ว
ระบบจะสร้างบริการเมื่อคอมโพเนนต์อื่นเรียก
startService()
จากนั้นบริการจะทํางานไปเรื่อยๆ และต้องหยุดตัวเองโดยเรียกใช้stopSelf()
คอมโพเนนต์อื่นยังหยุดบริการได้โดยเรียกใช้stopService()
เมื่อบริการหยุดลง ระบบจะทำลายข้อมูล - บริการที่มีผลผูกพัน
ระบบจะสร้างบริการเมื่อคอมโพเนนต์อื่น (ไคลเอ็นต์) เรียกใช้
bindService()
จากนั้นไคลเอ็นต์จะสื่อสารกับบริการผ่านอินเทอร์เฟซIBinder
โดยสามารถปิดการเชื่อมต่อได้โดยเรียกใช้unbindService()
ไคลเอ็นต์หลายตัวสามารถเชื่อมโยงกับบริการเดียวกันได้ และเมื่อไคลเอ็นต์ทั้งหมดยกเลิกการเชื่อมโยงแล้ว ระบบจะทำลายบริการนั้น บริการไม่จําเป็นต้องหยุดเอง
เส้นทางทั้ง 2 นี้ไม่ได้แยกจากกันโดยสิ้นเชิง คุณสามารถเชื่อมโยงกับบริการที่เริ่มต้นด้วย startService()
อยู่แล้ว เช่น คุณสามารถเริ่มบริการเพลงที่เล่นอยู่เบื้องหลังได้โดยเรียกใช้ startService()
พร้อม Intent
ที่ระบุเพลงที่จะเล่น ต่อมา กิจกรรมจะเชื่อมโยงกับบริการได้โดยเรียกใช้ bindService()
เมื่อผู้ใช้ต้องการควบคุมโปรแกรมเล่นหรือรับข้อมูลเกี่ยวกับเพลงที่เล่นอยู่ ในกรณีเช่นนี้ stopService()
หรือ stopSelf()
จะไม่หยุดบริการจนกว่าไคลเอ็นต์ทั้งหมดจะยกเลิกการเชื่อมโยง
การใช้ Lifecycle Callback
บริการมีเมธอดการเรียกกลับของวงจรที่คุณสามารถใช้เพื่อตรวจสอบการเปลี่ยนแปลงในสถานะของบริการและทํางานในเวลาที่เหมาะสมได้เช่นเดียวกับกิจกรรม โครงร่างบริการต่อไปนี้แสดงเมธอดวงจรชีวิตของแต่ละรายการ
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 } }
หมายเหตุ: คุณไม่จําเป็นต้องเรียกใช้การใช้งานเมธอดการเรียกคืนเหล่านี้ของซุปเปอร์คลาส ต่างจากเมธอดการเรียกคืนของวงจรชีวิตของกิจกรรม
รูปที่ 2 แสดงเมธอดการเรียกกลับทั่วไปสําหรับบริการ แม้ว่ารูปภาพจะแยกบริการที่สร้างโดย startService()
ออกจากบริการที่สร้างโดย bindService()
แต่โปรดทราบว่าบริการใดก็ตามไม่ว่าจะเริ่มต้นอย่างไรก็อาจมีแนวโน้มที่จะอนุญาตให้ไคลเอ็นต์เชื่อมโยงกับบริการนั้น
บริการที่เริ่มต้นด้วย onStartCommand()
(เมื่อลูกค้าโทรหา startService()
) ในตอนต้นจะยังคงรับสายที่โทรเข้ามายัง onBind()
ได้ (เมื่อลูกค้าโทรหา bindService()
)
การใช้เมธอดเหล่านี้จะช่วยให้คุณตรวจสอบลูปที่ซ้อนกัน 2 ลูปนี้ของวงจรชีวิตของบริการได้
- ตลอดอายุของบริการเกิดขึ้นระหว่างเวลาที่เรียก
onCreate()
กับเวลาที่onDestroy()
แสดงผล บริการจะตั้งค่าเริ่มต้นในonCreate()
และปล่อยทรัพยากรที่เหลือทั้งหมดในonDestroy()
เช่นเดียวกับกิจกรรม ตัวอย่างเช่น บริการเล่นเพลงสามารถสร้างเธรดที่เล่นเพลงในonCreate()
แล้วหยุดเธรดในonDestroy()
หมายเหตุ: ระบบจะเรียกใช้เมธอด
onCreate()
และonDestroy()
สำหรับบริการทั้งหมด ไม่ว่าจะสร้างโดยstartService()
หรือbindService()
- อายุการใช้งานที่ใช้งานอยู่ของบริการจะเริ่มต้นด้วยการเรียกใช้
onStartCommand()
หรือonBind()
แต่ละเมธอดจะได้รับIntent
ที่ส่งไปยังstartService()
หรือbindService()
หากเริ่มใช้บริการแล้ว อายุการใช้งานที่ใช้งานอยู่จะสิ้นสุดลงพร้อมกันกับอายุการใช้งานทั้งหมด (บริการจะยังคงใช้งานได้แม้ว่า
onStartCommand()
จะกลับมาแล้วก็ตาม) หากมีการเชื่อมโยงบริการ อายุการใช้งานที่ใช้งานอยู่จะสิ้นสุดลงเมื่อonUnbind()
กลับมา
หมายเหตุ: แม้ว่าการเรียกใช้ stopSelf()
หรือ stopService()
จะหยุดบริการที่เริ่มแล้ว แต่จะไม่มีการเรียกกลับที่เกี่ยวข้องสําหรับบริการนั้น (ไม่มีการเรียกกลับ onStop()
) ระบบจะทำลายบริการเมื่อหยุดบริการ เว้นแต่ว่าบริการจะเชื่อมโยงกับไคลเอ็นต์ onDestroy()
จะเป็นคอลแบ็กเดียวที่ได้รับ
ดูข้อมูลเพิ่มเติมเกี่ยวกับการสร้างบริการที่มีการเชื่อมโยงได้ที่เอกสารบริการที่เชื่อมโยง ซึ่งมีข้อมูลเพิ่มเติมเกี่ยวกับเมธอด onRebind()
callback ในส่วนการจัดการวงจรชีวิตของบริการที่เชื่อมโยง