Inter-Process Communication with android (phần 1)

by Cao Ngọc Hiệp
2K views

Content

  1. Inter-Process Communication(ipc) là gì?
  2. Tại sao cần giao tiếp?
  3. Các mô hình IPC
  4. IPC trong Android
  5. Sử dụng Messenger
  6. Demo với Messenger
  7. Kết luận

1. Inter-Process Communication(ipc) là gì?

Inter-process communication (IPC) là một cơ chế cho phép các quá trình giao tiếp với nhau và đồng bộ hóa các hành động của chúng.

Thông thường, các ứng dụng có thể sử dụng IPC, được phân loại là máy khách và máy chủ, trong đó máy khách (client) yêu cầu dữ liệu và máy chủ (server) đáp ứng yêu cầu của máy khách.

2. Tại sao cần giao tiếp?

  • Chia sẻ dữ liệu giữa 2 người dùng.
  • Mô-đun riêng biệt.
  • Thực thi nhiều quy trình dễ dàng hơn.

3. Các mô hình IPC

Đây là 2 mô hình IPC mình biết:

  • Share Memory: Giao tiếp giữa các tiến trình sử dụng bộ nhớ dùng chung yêu cầu các tiến trình phải chia sẻ một số biến và nó hoàn toàn phụ thuộc vào cách lập trình viên sẽ thực hiện nó.
  • Message passing: Các quá trình giao tiếp với nhau mà không cần sử dụng bất kỳ loại bộ nhớ dùng chung nào. Hai tiến trình p1 và p2 muốn giao tiếp với nhau, chúng tiến hành như sau:
    1. Thiết lập giao tiếp.
    2. Bắt đầu trao đổi tin nhắn bằng cách sử dụng cơ bản primitives.
      • send(message, destinaion) or send(message)
      • receive(message, host) or receive(message)

4. IPC trong Android

Thường có hai cách để triển khai IPC trong Android:

  • Using a Messenger: Thực hiện giao tiếp giữa các quá trình (IPC) với các ứng dụng khác bằng đối tượng Messenger và cho phép dịch vụ xử lý một cuộc gọi tại một thời điểm.
  • Using AIDL: Tạo tệp .aidl xác định IPC để cho phép máy khách từ các ứng dụng khác nhau truy cập dịch vụ và xử lý đa luồng đến dịch vụ.

5. Sử dụng Messenger

Messenger là một Trình xử lý được gửi đến quy trình từ xa.

Các bước triển khai IPC bằng Messenger:

  1. Service implements mmột Handler nhận một cuộc gọi lại cho mỗi cuộc gọi từ một máy khách.
  2. Service sử dụng Handler để tạo một đối tượng Messenger (là một tham chiếu đến Handler).
  3. Messenger tạo IBinder mà dịch vụ trả về cho khách hàng từ onBind().
  4. Clients sử dụng IBinder để khởi tạo Messenger (tham chiếu đến Handler của Service), mà client sử dụng để gửi các đối tượng Message tới service.
  5. Service từng Message trong Handler của nó — cụ thể là trong phương thức handleMessage().

6. Demo với Messenger

Server

Bắt đầu với ứng dụng Server.

Khai báo trong Manifest:

<service android:name=".IPCServerService">
            <intent-filter>
                <action android:name="messengerexample" />
            </intent-filter>
        </service>
// Messenger IPC - Messenger object contains binder to send to client
    private val mMessenger = Messenger(IncomingHandler())

    // Messenger IPC - Message Handler
    internal inner class IncomingHandler : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            // Get message from client. Save recent connected client info.
            val receivedBundle = msg.data
            RecentClient.client = Client(
                receivedBundle.getString(PACKAGE_NAME),
                receivedBundle.getInt(PID).toString(),
                receivedBundle.getString(DATA),
                "Messenger"
            )

            // Send message to the client. The message contains server info
            val message = Message.obtain(this@IncomingHandler, 0)
            val bundle = Bundle()
            bundle.putInt(CONNECTION_COUNT, connectionCount)
            bundle.putInt(PID, Process.myPid())
            message.data = bundle
            // The service can save the msg.replyTo object as a local variable
            // so that it can send a message to the client at any time
            msg.replyTo.send(message)
        }
    }
  • Tạo một đối tượng Handler trong dịch vụ để xử lý các thông báo từ client.
  • Cập nhật đối tượng RecentClient để cập nhật GUI theo thông báo đến.
  • Hãy tạo Messenger với đối tượng Handler. Giao tiếp sẽ diễn ra thông qua đối tượng này.
  • Các msg.replyTo đối tượng được lấy từ thông điệp của khách hàng.

Các hằng số chúng tôi sử dụng làm khóa gói trong thư mục:

package com.pmirkelam.ipcserver
// Bundle keys
const val PID = "pid"
const val CONNECTION_COUNT = "connection_count"
const val PACKAGE_NAME = "package_name"
const val DATA = "data"

Trả lại cho client:

// Pass the binder object to clients so they can communicate with this service
    override fun onBind(intent: Intent?): IBinder? {
        connectionCount++
        // Choose which binder we need to return based on the type of IPC the client makes
        return when (intent?.action) {
            "aidlexample" -> aidlBinder
            "messengerexample" -> mMessenger.binder
            else -> null
        }
    }

Client

Hãy thêm các hằng số mà tôi sử dụng làm khóa gói ở đây:

package com.pmirkelam.ipcclient

// Bundle keys
const val PID = "pid"
const val CONNECTION_COUNT = "connection_count"
const val PACKAGE_NAME = "package_name"
const val DATA = "data"

Trong tệp xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".ui.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="@string/say_something_to_server"
        android:textSize="25sp" />

    <EditText
        android:id="@+id/edt_client_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="@string/salut"
        android:inputType="text"
        android:textAlignment="center"
        android:textSize="35sp" />

    <Button
        android:id="@+id/btn_connect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="Connect via Messenger"
        android:textSize="18sp" />

    <LinearLayout
        android:id="@+id/linear_layout_client_info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        android:visibility="invisible">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:text="@string/process_id_of_server"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/txt_server_pid"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="@string/connection_count_of_server"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/txt_server_connection_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="50sp" />
    </LinearLayout>
</LinearLayout>

Trong tệp code:

class MessengerFragment : Fragment(), ServiceConnection, View.OnClickListener {

    private var _binding: FragmentMessengerBinding? = null
    private val viewBinding get() = _binding!!

    // Is bound to the service of remote process
    private var isBound: Boolean = false

    // Messenger on the server
    private var serverMessenger: Messenger? = null

    // Messenger on the client
    private var clientMessenger: Messenger? = null

    // Handle messages from the remote service
    var handler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
		    // Update UI with remote process info
            val bundle = msg.data
            viewBinding.linearLayoutClientInfo.visibility = View.VISIBLE
            viewBinding.btnConnect.text = getString(R.string.disconnect)
            viewBinding.txtServerPid.text = bundle.getInt(PID).toString()
            viewBinding.txtServerConnectionCount.text =
            bundle.getInt(CONNECTION_COUNT).toString()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMessengerBinding.inflate(inflater, container, false)
        return viewBinding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewBinding.btnConnect.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        if(isBound){
            doUnbindService()
        } else {
            doBindService()
        }
    }

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        serverMessenger = Messenger(service)
        // Ready to send messages to remote service
        sendMessageToServer()
    }

    override fun onServiceDisconnected(className: ComponentName) {
        clearUI()
        serverMessenger = null
    }

    private fun clearUI(){
        viewBinding.txtServerPid.text = ""
        viewBinding.txtServerConnectionCount.text = ""
        viewBinding.btnConnect.text = getString(R.string.connect)
        viewBinding.linearLayoutClientInfo.visibility = View.INVISIBLE
    }

    override fun onDestroy() {
        doUnbindService()
        super.onDestroy()
    }

    private fun doBindService() {
        clientMessenger = Messenger(handler)
        Intent("messengerexample").also { intent ->
            intent.`package` = "com.pmirkelam.ipcserver"
            activity?.applicationContext?.bindService(intent, this, Context.BIND_AUTO_CREATE)
        }
        isBound = true
    }

    private fun doUnbindService() {
        if (isBound) {
            activity?.applicationContext?.unbindService(this)
            isBound = false
        }
    }

    private fun sendMessageToServer() {
        if (!isBound) return
        val message = Message.obtain(handler)
        val bundle = Bundle()
        bundle.putString(DATA, viewBinding.edtClientData.text.toString())
        bundle.putString(PACKAGE_NAME, context?.packageName)
        bundle.putInt(PID, Process.myPid())
        message.data = bundle
        message.replyTo = clientMessenger // we offer our Messenger object for communication to be two-way
        try {
            serverMessenger?.send(message)
        } catch (e: RemoteException) {
            e.printStackTrace()
        } finally {
            message.recycle()
        }
    }
}

7. Kết Luận

Trên Đây là cách đơn giản nhất để thực hiện IPC, bởi vì Messenger xếp hàng tất cả các requests vào một luồng đơn để bạn không phải thiết kế service của mình là an toàn luồng.

Ở phần tiếp theo mình sẽ nói tiếp cách sử dụng ADIL trong IPC.

Cảm ơn mọi người đã đọc bài viết.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.