Service ถือว่าเป็นหนึ่งใน Component พื้นฐานของแอนดรอยด์ที่หลายๆงานนั้นจำเป็นต้องใช้ และเพื่อให้เรียกใช้งาน Service ได้อย่างถูกต้องและมีประสิทธิภาพ ผู้ที่หลงเข้ามาอ่านก็ต้องเข้าใจกันก่อนว่า Service บนแอนดรอยด์นั้นเป็นยังไง ทำงานยังไง และเรียกใช้งานแบบไหนได้บ้าง โดยบทความนี้จะมาพูดถึงเรื่องพื้นฐานๆของ Service กัน

บทความในซีรีย์เดียวกัน

Service ในแอนดรอยด์คืออะไร?

Service เป็น 1 ใน 4 Component พื้นฐานของแอปพลิเคชัน (มี Activity, Service, Broadcast Receiver และ Content Provider) โดยมีหน้าที่ทำงานบางอย่างที่ใช้เวลานานและไม่จำเป็นต้องแสดง UI ให้ผู้ใช้เห็นตลอดเวลา

แอปฯจำพวก Music Player ที่สามารถเล่นเพลงได้ถึงแม้ว่าผู้ใช้จะย่อหรือปิดแอปฯแล้วก็ตาม หรือแอปฯจำพวก Running Tracker ที่สามารถรู้ตำแหน่งของผู้ใช้ได้ตลอดเวลาถึงแม้ว่าจะไม่ได้เปิดแอปฯอยู่ ก็เพราะว่าใช้ Service เข้ามาช่วยนั่นเอง ซึ่ง Service นั้นสามารถทำงานอยู่เบื้องหลัง (Background) ได้ โดยไม่ต้องแสดง UI ให้ผู้ใช้เห็นตลอดเวลาแบบ Activity

Service จะถูกเรียกใช้งานจากที่ไหนก็ได้ที่มี Context ให้ใช้งานหรือเป็น Component ที่สืบทอดมาจาก Context ซึ่งในบทความนี้จะเรียกฝั่งที่เรียกใช้งาน Service ว่า Component นะครับ

Service ตัวนึงจะถูกสร้างขึ้นมาเพียงครั้งเดียวเท่านั้น แต่จะเรียกใช้งานกี่ครั้งก็ได้ จนกว่าจะหยุดทำงานหรือถูกทำลาย

Service จะต่างจาก Activity ตรงที่ว่า Service ตัวนึงจะสามารถสร้างขึ้นมาได้แค่ตัวเดียวเท่านั้น ไม่สามารถสร้างตัวเดิมซ้ำๆได้เหมือนอย่าง Activity

นั่นหมายความว่าตอนที่ Component เรียกใช้งาน Service ครั้งแรกสุด มันจะสร้าง Service ขึ้นมาแล้วเริ่มทำงาน แต่เมื่อเรียกใช้งานครั้งต่อไปก็จะเริ่มทำงานจาก Service ตัวที่เคยสร้างไว้ทันที ไม่มีการสร้างขึ้นมาใหม่ เว้นแต่ว่ามันจะถูกทำลายทิ้ง ถึงจะสร้างขึ้นมาใหม่อีกครั้ง

ดังนั้น Service จึงเหมาะแก่การออกแบบให้เป็นศูนย์กลางในการทำงานที่ต้องจำเป็นต้องอยู่เบื้องหลัง โดยที่ไม่ได้สนใจว่าผู้ใช้จะทำอะไรอยู่หรือเปิดหน้าไหนอยู่ เช่น Service สำหรับส่งข้อมูลขึ้น Service หรือ Sync ข้อมูลลงใน Local Database เป็นระยะๆเพื่อที่ User จะได้ไม่ต้องมานั่งรอทุกครั้งเมื่อเปิดแอปฯขึ้นมา

สามารถแบ่งรูปแบบการทำงานได้ 3 แบบ

Service นั้นมีรูปแบบการทำงานได้หลายแบบ ซึ่งขึ้นอยู่กับว่านักพัฒนาจะกำหนดไว้แบบไหน

  • Background Service
  • Foreground Service
  • Bound Service

Background Service

เป็น Service ที่จะทำงานโดยไม่มีการแสดงผลหรือแจ้งเตือนให้ผู้ใช้ทราบ ซึ่งเป็นรูปแบบพื้นฐานที่สุดของ Service เหมาะกับการทำงานบางอย่างที่ต้องใช้เวลานานแต่ว่าไม่จำเป็นต้องแสดงให้ผู้ใช้เห็นว่ากำลังทำงานอยู่หรือทำงานเสร็จแล้ว

Foreground Service

เป็น Service ที่จะทำงานบางอย่างที่ใช้เวลานานๆ แต่จะแสดงการทำงานหรือแจ้งเตือนให้ผู้ใช้เห็นผ่าน Notification ใน Status Bar

Bound Service

เป็น Service ที่ทำงานโดยสามารถรับส่งข้อมูลระหว่าง Component ที่เรียกใช้งาน Service ได้ตลอดเวลาที่ Service ทำงาน โดยจะมีตัวกลางในการสื่อสารระหว่างทั้งสองฝั่งที่เรียกว่า IBinder โดย Service แบบนี้จะทำงานได้นานเท่าที่ Component นั้นๆยังทำงานอยู่

สามารถเรียกใช้งาน Service ได้ 2 แบบ

นอกจากจะแบ่งรูปแบบการทำงานออกเป็น 3 แบบแล้ว ยังสามารถแบ่งตามรูปแบบการเรียกใช้งาน Service ได้ 2 แบบดังนี้

  • Started Service
  • Bound Service

การสร้าง Service จะเรียกผ่านคำสั่ง startService(...) หรือ bindService(...) ซึ่งจะเป็นตัวกำหนดรูปแบบการใช้งาน Service นั่นเอง

Started Service

Service ที่ถูกสร้างด้วยคำสั่ง startService(...) จะถือว่าเป็น Started Service ซึ่งการสร้าง Background Service และ Foreground Service จะใช้คำสั่งแบบนี้

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    fun startAwesomeService() {
        ...
        val intent = Intent(this, AwesomeService::class.java)
        startService(intent)
    }
    ...
}

เมื่อต้องการให้ Started Service หยุดทำงานจะมีคำสั่งให้ใช้งาน 2 แบบด้วยกันคือให้ฝั่ง Component ใช้คำสั่ง stopService(...) หรือเรียกคำสั่ง stopSelf() จากใน Service นั้นๆโดยตรง

// สั่งให้ Service หยุดทำงาน โดยสั่งงานจากฝั่ง Component 
// MainActivity.kt

class MainActivity : AppCompatActivity() {
    ...
    fun stopAwesomeService() {
        ...
        val intent = Intent(this, AwesomeService::class.java)
        stopService(intent)
    }
}

// สั่งให้ Service หยุดทำงาน โดยสั่งงานจากใน Service เอง
// AwesomeService.kt

class AwesomeService : Service() {
    ...
    fun doSomethingFinished() {
        stopSelf()
    }
}

สรุปก็คือ Started Service จะเริ่มทำงานก็ต่อเมื่อเรียกคำสั่ง startService(...) เท่านั้น และจะหยุดทำงานก็ต่อเมื่อเรียกคำสั่ง stopService(...) หรือ stopSelf()

Bound Service

Service ที่ถูกสร้างด้วยคำสั่ง bindService(...) จะถือว่าเป็น Bound Service โดยจะมีรูปแบบคำสั่งแบบนี้

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun bindAwesomeService {
        val intent = Intent(this, AwesomeService::class.java)
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
    
    private val serviceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
            // Do something
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            // Do something
        }
    }
    ...
}

เมื่อต้องการให้ Bound Service หยุดทำงานจะต้องใช้คำสั่ง unbindService(...) เท่านั้น

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun unbindAwesomeService {
        unbindService(serviceConnection)
    }
    
    private val serviceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
            // Do something
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            // Do something
        }
    }
    ...
}

สรุปก็คือ Bound Service จะถูกสร้างด้วยคำสั่ง bindService(...) และจะหยุดทำงานและถูกทำลายเมื่อเรียกคำสั่ง unbindService(...) เท่านั้น

กรณีที่ Bound Service ถูก Bind จากหลายๆ Component ถ้าสั่ง Unbind จาก Component ตัวใดตัวหนึ่ง Service จะยังไม่หยุดทำงาน เพราะว่ายังมี Component ตัวอื่นที่ Bind อยู่ ต้อง Unbind จนครบทุกตัว Service ถึงจะหยุดทำงานและถูกทำลาย

คลาสของ Service มี 2 แบบด้วยกัน

ในการสร้างคลาส Service ขึ้นมาเพื่อใช้งานนั้นจะต้องสืบทอดจากคลาส Service ซึ่งมีอยู่ 2 แบบด้วยกัน

  • Service
  • IntentService

Service

ถือว่าเป็นเป็น Base Class ของ Service ทั้งปวงเลยก็ว่าได้

// AwesomeService.kt
import android.app.Service

class AwesomeService : Service() {
    ...
}

สิ่งสำคัญที่ต้องรู้เกี่ยวกับการสร้าง Service แบบนี้ก็คือคำสั่งจะทำงานอยู่บน Main Thread ดังนั้นถ้าทำงานอะไรนานเกินก็จะ Block UI ได้ ดังนั้นสิ่งที่จะต้องทำเพิ่มก็คือต้องสร้าง Thread แยกขึ้นมาเพื่อให้ทำงานเป็น Background Thread แล้วเรียกใช้คำสั่งที่ต้องการไว้ในนั้น

โดย Service แบบนี้จะทำงานเป็น Parallel คือสั่งงานที่ไหนเมื่อไร มันก็จะทำให้ทันที และถ้าสั่งหลายๆครั้งพร้อมๆกัน มันก็จะทำให้พร้อมๆกันเลย อันไหนใช้เวลาทำงานน้อยกว่าก็จะเสร็จก่อน

ต่อให้สร้าง Background Thread ขึ้นมา เมื่อถูกเรียกจากหลายๆที่พร้อมๆกัน มันก็จะกลายเป็น Background Thread หลายๆตัว ที่ทำงานพร้อมๆกันอยู่ดี

IntentService

เป็น Subclass ของ Service อีกทีหนึ่ง จากเดิมที่ใช้ Service แล้วต้องสร้าง Thread ขึ้นมาเอง แต่ IntentService จะสร้างสิ่งที่เรียกว่า Worker Thread ให้แล้ว สามารถใช้งานได้เลย ไม่ต้องมานั่งสร้าง Background Thread เอง

// AwesomeIntentService.kt
import android.app.IntentService

class AwesomeIntentService : IntentService("AwesomeService") {
    ...
}

โดย Worker Thread ที่ว่านี้จะทำงานเป็นระบบคิว เมื่อถูกสั่งให้ทำงานหลายๆครั้งพร้อมๆกัน มันจะเริ่มจากทำงานแรกสุดก่อน เมื่อทำงานแรกสุดเสร็จแล้ว ก็จะทำงานต่อไป เป็นแบบนี้ไปเรื่อยๆ

และเมื่อทำงานเสร็จแล้วมันก็จะหยุดทำงานแล้วทำลายตัวเองทิ้งทันที ซึ่งต่างจาก Service แบบธรรมดาที่ต้องใช้คำสั่ง stopService(…) หรือ stopSelf() ถึงจะหยุดทำงาน

เฮ้ย ทำไมมันแบ่ง Service ได้หลายแบบจัง แล้วจะรู้ได้ไงว่าต้องใช้แบบไหน?

เพราะว่านักพัฒนาสามารถสร้าง Service ขึ้นมาให้ทำงานแบบไหนก็ได้ขึ้นอยู่กับโค้ดที่เขียน ดังนั้น Service บนแอนดรอยด์จึงถูกออกแบบมาให้ครอบคลุมเพื่อความยืดหยุ่นต่อการนำไปใช้งาน ก็เลยเป็นที่มาว่าทำไมมันถึงแบ่งออกมาได้หลายแบบ

เวลาที่จะสร้าง Service ขึ้นมาซักตัวหนึ่งแล้วไม่รู้ว่าจะต้องเลือกแบบไหน ก็ให้ลองใช้ Dicision Tree แบบนี้ดูครับ

ให้ดูก่อนว่า Service ที่จะสร้างเป็นแบบไหนระหว่าง Background Service, Foreground Service และ Bound Service จากนั้นก็ดูต่อว่าเวลาเรียกใช้งานจะให้ Service ทำงานแบบ Parallel หรือ Queue

พอรู้แล้วว่าจะต้องสร้าง Service แบบไหน ก็จะรู้ว่าต้องใช้คำสั่งอะไรบ้างทันที โดยจะเห็นว่ารูปแบบของ Service จริงๆมีอยู่แค่ 6 แบบเท่านั้นเอง

Lifecycle ของ Service

อย่างที่บอกไปในตอนแรกว่า Service เป็น Component พื้นฐานเหมือนกับ Activity ดังนั้นอย่าแปลกใจถ้ามันจะมี Lifecycle ด้วย แต่ว่า Lifecycle ของ Service นั้นค่อนข้างเรียบง่ายกว่ามาก

โดยจะแบ่ง Lifecycle ระหว่าง Started Service กับ Bound Service ถึงแม้ว่าจริงๆแล้ว Method เหล่านี้จะมีอยู่ในคลาส Service ตัวเดียวกันก็เถอะ และไม่ว่าจะเป็นคลาส Service หรือ IntentService ก็จะมี Lifecycle แบบนี้เช่นกัน

Service บน Android 8.0 Oreo (API 26)

ตั้งแต่ Android 8.0 Oreo ขึ้นไป ได้มีการเปลี่ยนแปลงรูปแบบการทำงานของ Background Service ใหม่ เพื่อไม่ให้ทำงานได้นานๆอีกต่อไป (เพราะมันทำให้เครื่องช้าและสูบแบต) ซึ่งจะมีผลกระทบกับ Background Service เท่านั้น โดยแนะนำให้เปลี่ยนไปใช้ JobScheduler แทน

ส่วน Foreground Service และ Bound Service สามารถใช้งานได้ปกติ จึงไม่ต้องทำอะไรเพิ่ม

สามารถอ่านเกี่ยวกับเรื่องนี้ได้ที่ Background Execution Limits [Android Developers]

สรุป

ในบทความตอนแรกสุดของเรื่อง Service นี้จะไม่ได้ลงรายละเอียดเกี่ยวกับโค้ดมากนัก เพราะสิ่งแรกสุดที่ต้องรู้ก่อนจะใช้งาน Service ก็คือต้องเข้าใจก่อนว่าจริงๆแล้ว Service มันมีรูปแบบการทำงานหลายแบบนะ เรียกใช้งานก็เรียกได้หลายแบบ แถมตอนสร้าง Service ก็มีคลาสให้สร้างหลายแบบด้วย

สำหรับรูปแบบการทำงานจะสรุปได้ดังนี้

  • Background Service เป็น Service ที่แอบทำงานเงียบๆอยู่เบื้องหลัง ไม่แสดงอะไรให้ผู้ใช้รับรู้
  • Foreground Service เป็น Service ที่แสดงการทำงานให้ผู้ใช้เห็นผ่าน Notification
  • Bound Service เป็น Service ที่สามารถรับส่งข้อมูลกับ Component ได้ตลอดเวลา หยุดทำงานก็ต่อเมื่อ Component หยุดทำงาน

ซึ่ง Background Service กับ Foreground Service จะอยู่ในกลุ่มของ Started Service ส่วน Bound Service ก็อยู่ในกลุ่มของ Bound Service นั่นแหละ โดยที่

  • Started Service เรียกใช้งาน Service ด้วยคำสั่ง startService(...) และสั่งให้หยุดทำงานด้วย stopService(...) หรือ stopSelf()
  • Bound Service เรียกใช้งาน Service ด้วยคำสั่ง bindService(...) และสั่งให้หยุดทำงานด้วย unbindService(...)

และในการสร้างคลาส Service ก็จะมีให้เลือกว่าจะ Extend จากคลาส Service หรือคลาส IntentService เพราะทั้งสองมีการทำงานที่ต่างกัน

  • คลาส Service ทำงานเป็น Parallel ถูกเรียกจากหลายที่พร้อมๆกันก็จะทำงานพร้อมๆกัน และจะทำงานอยู่บน Main Thread ดังนั้นควรสร้าง Background Thread ขึ้นมาทำงานแทน
  • คลาส IntentService เป็น Subclass ของ Service ที่สร้าง Thread ขึ้นมาให้ในตัวแล้ว โดยเรียกว่า Worker Thread โดยจะทำงานเป็นแบบ Queue คือจะทำงานทีละอันจนเสร็จแล้วค่อยทำตัวถัดไปจนครบทุกตัว

จะเห็นว่าจริงๆแล้วรูปแบบของ Service ไม่ได้ซับซ้อนเลย แค่มีหลากหลายรูปแบบเพื่อให้ยืดหยุ่นต่อการนำไปใช้งาน ซึ่งหัวใจสำคัญก็คือต้องเข้าใจในแต่ละรูปแบบว่าเหมาะสมกับงานแบบไหน ต้องเขียนโค้ดและใช้คำสั่งแบบไหน เพราะแต่ละรูปแบบก็จะใช้โค้ดที่ต่างกันเล็กน้อย รวมไปถึง Lifecycle ที่แตกต่างกันด้วย

สำหรับในบทความตอนต่อไปก็จะลงลึกเรื่องโค้ดกันต่อว่าการสร้าง Service แต่ละแบบเนี่ยมันต้องเขียนโค้ดยังไงบ้าง