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

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

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

Service เป็น 1 ใน 4 Component พื้นฐานของระบบแอนดรอยด์ โดยมีหน้าที่ทำงานบางอย่างที่ใช้เวลานานและไม่จำเป็นต้องแสดง 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 จะได้ไม่ต้องมานั่งรอทุกครั้งเมื่อเปิดแอปขึ้นมา

สามารถเรียกใช้งาน 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() {
    private 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() {
    private 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]

Background Execution Limits | Android Developers
New background limits for apps that target Android 8.0 or higher.

สรุป

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

ซึ่ง 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 คือจะทำงานทีละอันจนเสร็จแล้วค่อยทำตัวถัดไปจนครบทุกตัว