Service trong Android – Những khái niệm cơ bản – Phần 2

phần 1 chúng ta đã tìm hiểu về khái niệm Service trong Android, các loại service, unbound service và một phần của bound service. Ở phần 2 này chúng ta sẽ tìm hiểu phần còn lại của bound service và một số thông tin thêm hữu ích nha 😀

4. Bound service

c. Một số khái niệm

Trong những phần tiếp theo mình sẽ có nói về thread, process, về đa luồng, đa tiến trình, IPC … => Nghe lằng nhằng phải không nào 😀

Vậy nên dưới đây mình sẽ nói sơ qua để các bạn có thể dễ dàng đọc những phần tiếp theo nhé

Trước hết là so sánh giữa processthread

  • Process: nằm ở mức hệ thống, kiểm soát bởi hệ thống:

Khi ứng dụng chạy, hệ thống tạo ra … một process. Ứng dụng sẽ được chạy và quản lý trong process đó.

Mỗi process sẽ có tài nguyên, bộ nhớ … độc lập với nhau. Trong Android, một process không thể bình thường mà truy cập tới bộ nhớ của process khác.

Mỗi ứng dụng được chạy và quản lý trong “một” process
  • Thread: ở mức ứng dụng:

Khi một process được tạo, ứng dụng bên trong đó có thể … tạo nhiều thread khác nhau.

Đầu tiên, một thread sẽ được tạo ra, gọi là “main“. Thread này – hay còn được gọi là UI thread sẽ nhận, truyền sự kiện khi người dùng tương tác, tương tác với Android UI toolkit.

Nếu bạn không tạo ra một thread mới thì tất cả các xử lý logic của bạn sẽ xảy ra … trong thread này.

Thread chỉ hoạt động bên trong giới hạn của process, chia sẻ tài nguyên trong 1 process.

Process tạo được nhiều thread
Process tạo được nhiều thread

Tiếp theo sẽ là về đa tiến trình ( multi-process ), đa luồng ( multi-thread )

Hiểu đơn giản là tạo ra nhiều tiến trình, nhiều luồng chạy … trong cùng một thời điểm.

  • Được lợi là giúp bạn chạy nhiều tác vụ, làm được nhiều việc trong cùng một khoảng thời gian. Nghe hay phải không nào 😀
  • Còn cái … không lợi ^.^ . Là việc nhận kết quả, nhận biết cái nào xong trước, cái nào xong sau, tương tác, trao đổi giữa các process, thread nhiều lúc thường phức tạp.

Với thread thì cần xử lý thread-safe, với đa luồng thì có IPC ( interprocess communication – giao tiếp liên tiến trình).

Sẽ khá là phức tạp, nên mình chỉ nói vậy thôi nha 😀 Bạn nào có thông tin gì thêm thì để lại ở comment nhé !!!

Đây là một vấn đề " to to " trong lập trình :D
Một vấn đề ” to to ” trong lập trình 😀

d. Cách khởi tạo

Nào giờ chúng ta sẽ quay lại với Bound Service trong Android. Trước khi bắt đầu thì các bạn hãy nhớ khai báo Service trong AndroidManifest trước nhé.

Bạn có ba cách để tạo một Bound service

Cách 1: Extend lại Binder class

Đây là cách … đơn giản nhất, dễ triển khai nhất để tạo Bound Service.

Trường hợp sử dụng: service chỉ cần trong app của bạn và chạy cùng process với Client.

Cách làm:

  • Tạo interface extend lại class Binder và trả về instance trong onBind().
  • Client giờ có thể nhận instance này rồi gọi trực tiếp đến phương thức public trong nó hoặc trong Service.

Nghe lý thuyết cũng khó hiểu phải không nào ? Vậy nên mình sẽ đưa ra các bước cụ thể cho các bạn

B1: Tạo class Service

class RandomService : Service() {

    val mIbinder: IBinder = TestBinder()

    private val mGenerator = Random()

    //  Extend lại class Binder, phương thức getService sẽ trả về  instance 
    //  của RandomService, từ đó có thể gọi được những phương thức public của 
    // service
    inner class TestBinder : Binder() {
        fun getService(): Service = this@RandomService
    }

    // Trả về instance trong onBind()
    override fun onBind(intent: Intent): IBinder? {
        return mIbinder
    }
    
   // Phương thức public, bên Client sẽ gọi phương thức này
   fun getNumber() = mGenerator.nextInt(100)

    }

B2: Implement bên Client

class BindingActivity : Activity() {
    private lateinit var mService: RandomService
    private var mBound: Boolean = false

    // Xác định callbacks cho service được bind tới, sẽ được truyền vào bindService()  
    private val connection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
         // Ta đã bind tới RandomService, cast IBinder và lấy RandomService instance
            val binder = service as LocalService.LocalBinder

            // Đối tượng RandomService của chúng ta. Giờ ta có thể lấy để sử dụng :D
            mService = binder.getService()
            mBound = true
        }

        override fun onServiceDisconnected(arg0: ComponentName) {
            mBound = false
        }
    }

     ...

    override fun onStart() {
        super.onStart()
        // Bind tới RandomService
        Intent(this, RandomService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    // Câu hỏi: Vậy bind ở phương thức khác onStart() được không ???
    }

    override fun onStop() {
        super.onStop()
        // Dừng việc bind tới RandomService
        unbindService(connection)
        mBound = false
    // Câu hỏi: Tại sao lại unbind ở đây ???
    }

    // Click một nút trên giao diện 
    fun onButtonClick(v: View) {
        if (mBound) {
       // Gọi phương thức từ RandomService.
       // Như mình đã nói ở bài trước, nên tạo một thread để cho service 
       // để tránh làm     
       // chậm máy, tránh lỗi ARN
            val num: Int = mService.randomNumber
            Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
        }
    }
}

// Có hai câu hỏi mình sẽ trả lời ở dưới nhé
Ngó qua sự kiện sách – hot sale trên Tiki nhé
Cách 2: Sử dụng Messenger
  • Trường hợp sử dụng: giúp làm việc giữa các process, giữa các app với nhau. Tuy nhiên trong một app, một process bạn hoàn toàn có thể sử dụng bình thường, thay thế cho cách 1 ở trên.

Đây là cách đơn giản nhất để thực hiện giao tiếp liên tiến trình (IPC), bởi vì Messager sắp hàng toàn bộ request vào một thread duy nhất. Vì thế bạn sẽ không phải thiết kế service dạng thread-safe (đồng bộ request – chỉ cho duy nhất 1 request truy suất vào tại 1 thời điểm)

IPC – giao tiếp liên tiến trình
  • Cách làm: Service định nghĩa Handler mà đáp lại với mỗi loại Message khác nhau. Handler này là nền tảng cho Messenger có thể chia sẻ IBinder với client, cho phép client gửi lệnh tới service sử dụng Message.
  • Service cũng có thể gửi message cho client khi client cũng định nghĩa 1 Messenger của chính nó.

Các bước cụ thể:

  1. Service implement một Handler mà sẽ nhận callback cho mỗi lần gọi từ client.
  2. Service dùng Handler để tạo đối tượng Messenger
  3. Đối tượng Messenger tạo một IBinder để cho service trả về cho client từ hàm onBind()
  4. Client sử dụng IBinder để tạo đối tượng Messenger. Đối tượng này sẽ được dùng để gửi đối tượng Message tới service
  5. Service nhận Message trong Handler được tạo ở bước 1, trong phương thức handleMessage() của Handler. Bạn sẽ xử lý những logic bạn muốn ở đây
  6. The end ^.^

Mình sẽ đưa ra code ví dụ

Code class Service

class MessengerService : Service() {

    private lateinit var mMessenger: Messenger

    /**
     * B1: Implement một Handler mà sẽ nhận callback cho mỗi lần gọi từ client.
     */
    internal class IncomingHandler(
            context: Context,
            private val applicationContext: Context = context.applicationContext
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            //B5. Service nhận Message trong Handler được tạo ở B1, 
            // trong phương thức handleMessage() của Handler. Logic bạn 
            // muốn xử lý sẽ được code ở đây
            when (msg.what) {
                MSG_SAY_HELLO ->
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
                else -> super.handleMessage(msg)
            }
        }
    }

    /**
     * B2, B3: Service dùng Handler ở trên để tạo đối tượng Messenger, 
     * trả về một IBinder cho client sử dụng từ hàm onBind()
     */
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }
}

Code bên Client

class ActivityMessenger : Activity() {

    /** Đối tượng Messenger để tương tác với Service  */
    private var mService: Messenger? = null

    /** Cờ để check xem client đã bound (liên kết) thành công 
     * hay chưa với Service
     */
    private var bound: Boolean = false

    /**
     * Class tương tác với Service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {

            // B4. Client sử dụng IBinder để tạo đối tượng Messenger. Đối 
            // tượng này sẽ được dùng để gửi đối tượng Message tới service
            mService = Messenger(service)
            bound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // Được gọi khi kết nối với service đã bị ngắt, đó là khi 
            // process của nó bị crash
            mService = null
            bound = false
        }
    }

    fun sayHello(v: View) {
        if (!bound) return
        // Gửi đối tượng Message tới Service tại đây, sử dụng Messager tạo 
        // ở onServiceConnected ở trên
        // Với B5: bạn quay lại code implement trong Service ở trên nhé
        val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        try {
            mService?.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }

    }

    ...

    override fun onStart() {
        super.onStart()
        // Bind tới service
        Intent(this, MessengerService::class.java).also { intent ->
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        }
    // Cùng câu hỏi với phía trên, bind ở method khác được không ??
    }

    override fun onStop() {
        super.onStop()
        // Unbind from the service
        if (bound) {
            unbindService(mConnection)
            bound = false
        }
    // Cùng câu hỏi với phía trên, unbind ở method khác được không ???
    }
}

Phần này phức tạp hơn cách implement bằng extend lại Binder … nhiều lần. Các bạn đọc kĩ, hiểu các bước nhé.

Tiếp theo mình sẽ trình bày cách thứ 3 – cũng là cách cuối cùng. Nhanh thôi :3

Cách 3: Sử dụng AIDL (Android Interface Definition Language)
Cách 3: sử dụng AIDL

Trường hợp sử dụng: hầu hết ứng dụng … không nên sử dụng AIDL (^.^) để tạo bound service. Bởi vì nó có thể yêu cầu khả năng xử lý multi-thread và có thể dẫn tới những xử lý … rất phức tạp.

Tuy nhiên trong trường hợp bạn cần sử dụng, ví dụ là khi muốn tương tác với app khác và xử lý multi-thread thì bạn có thể triển khai. Nhưng chắc chắc hiểu về Bound service, AIDL và xử lý đầy đủ các trường hợp nha.

*Tips*

Messenger ở Cách 2 là dựa trên cấu trúc của AIDL, nhưng chỉ là cho một thread. AIDL có thể giúp thực hiện multi-thread, nhưng service phải là dạng thread-safe và có khả năng xử lý multi-thread.

Vậy nên nếu không có nhu cầu chạy multi-thread, bạn hoàn toàn có thể dùng Messenger như Cách 2 ở trên cũng được.

Vui vui nhưng khá thực tế

Cách làm:

Bạn có thể xem tại đây: https://developer.android.com/guide/components/aidl

Hiểu đơn giản, AIDL chia nhỏ đối tượng thành … dạng nguyên thủy mà hệ điều hành có thể hiểu được và sắp vào các process để thực hiện IPC.

Như đã nói ở … mục c ở trên, một process không thể bình thường mà truy cập vào bộ nhớ của process khác. Nên hệ điều hành sẽ chính là … trung gian giao tiếp giữa các process.

d. Tips
  • Chỉ activity, service và content provider có thể bind tới service. Broadcast receiver không bind được. Từ fragment có thể bind tới service, bằng cách gọi getActivity() rồi bind tới. Nhưng cẩn thận vì đối tượng service lấy được dễ có thể bị null.
  • Nên bắt ngoại lệ DeadObjectException, được ném ra khi kết nối bị chết.
  • Các đối tượng là tham chiếu được tính qua các tiến trình

(Objects are reference counted across processes

Tham khảo: https://en.wikipedia.org/wiki/Reference_counting)

  • Nên binding và unbinding theo thời điểm đối ngược nhau trong vòng đời của client, như:

Bind khi onStart(), unbind khi onStop(). Dùng khi tương tác với service khi activity đang được nhìn thấy

Bind khi onCreate(), unbind khi onDestroy(). Dùng khi tương tác với service kể cả khi nó đang chạy background. Nên cẩn thận khi sử dụng vì service sẽ chạy toàn bộ thời gian activity chạy (kể cả dưới background).

  • Không nên bind, unbind trong onResume()onPause() vì 2 method thường được gọi rất nhiều lần trong vòng đời của client. Nhiều lỗi có thể xảy ra mà bạn không lường trước được 😀
  • Khi gọi bindService(), ta có tham số thứ 3 chính là các lựa chọn cho việc binding. Một số cờ như:

Context.BIND_AUTO_CREATE: tạo service nếu nó đang không tồn tại

Còn nhiều cờ khác bạn có thể xem ở dưới đây

Context#BIND_ABOVE_CLIENT

Context#BIND_ALLOW_OOM_MANAGEMENT

Context#BIND_WAIVE_PRIORITY

Context#BIND_IMPORTANT

Context#BIND_ADJUST_WITH_ACTIVITY.

5. Thời điểm bị kill

Hệ thống Android sẽ stop một service chỉ khi bộ nhớ thấp và lúc đó một activity cần focus:

  • 1 service gắn với activity đang được focus, nó ít có khả năng bị kill. Đây chính là loại … bound service
  • 1 service chạy foreground, nó hiếm khi bị kill. Đây chính là …foreground service
  • 1 service đã chạy lâu, được started thì dễ bị kill. Đây chính là … background service

Vậy trình tự bị kill của các loại service sẽ là: Bound service > Foreground service > Background service

Tạm kết

Vậy là hết bài 2 về Service, cũng là bài cuối cùng về Service trong Android rồi 🙁

Bài viết khá dài, nhiều code. Cảm ơn các bạn đã đọc đến đây nhé !!!

Trong bài chúng ta đã tìm hiểu sơ lược khái niệm về thread, process, multi-thread, IPC, các cách để tạo bound service, một số Tips hay và thời điểm bị kill của service.

Các bạn có ý kiến gì thì hãy để lại ở phần Comment nhé. Hãy subscibe mail để luôn nhận được những bài viết mới nhất nhé

Có rất nhiều các demo đơn giản để thực hành service như chơi nhạc, load file, … Bạn hãy bắt đầu thử ngay nào 😀 

Bạn nào chưa xem phần một có thể xem ngay tại đây

Service trong Android – Phần 1: https://codecungtrung.com/android/service-trong-android-phan-1/

Tham khảo

Bound service: https://developer.android.com/guide/components/bound-services

AIDL: https://developer.android.com/guide/components/aidl

Ai đi qua để lại ý kiến về bài viết, về blog để mình có thể hoàn thiện hơn nhé, tks nhiều !!!

Ví dụ như: nội dung còn dài, chưa phong phú, nhiều lý thuyết quá, cần ngắn gọn, …

Cảm ơn bạn đã đọc các bài viết của Code cùng Trung. Bạn hãy ủng hộ blog bằng cách:

  • Comment bên dưới mỗi bài nếu có thắc mắc
  • Để lại địa chỉ email của bạn để nhận được thông báo sớm nhất khi có bài viết mới
  • Chia sẻ các bài viết của Code cùng Trung đến nhiều người khác

1 thought on “Service trong Android – Những khái niệm cơ bản – Phần 2”

Leave a Reply