Content
- Inter-Process Communication(ipc) là gì?
- Tại sao cần giao tiếp?
- Các mô hình IPC
- IPC trong Android
- Sử dụng Messenger
- Demo với Messenger
- 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:
- Thiết lập giao tiếp.
- 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:
- 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.
- Service sử dụng Handler để tạo một đối tượng Messenger (là một tham chiếu đến Handler).
- Messenger tạo IBinder mà dịch vụ trả về cho khách hàng từ onBind().
- 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.
- 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.