Android探索:六种IPC方式(上)——Bundle、文件共享、Messenger
引言
我们了解完Android IPC基础,接下来学习Android中的六种IPC方式:Bundle、文件共享、AIDL、Messenger、ContentProvider、Socket。
注意:本文代码均为kotlin语言
1、Bundle
Android四大组件中的Activity、Service、Receiver都是支持在Intent中传递Bundle数据的,例如在开发中,Activity界面跳转我们经常会使用intent.putExtra(name,value) 来设置数据,从源码来看其实是把值赋给了一个Bundle对象。Bundle像是一个Map集合,但它只能存储序列化的对象,比如基本数据类型、实现了Parcel able或Serializable接口的对象等。组件之间通过这种通讯方式最为简单。
2、文件共享
文件共享就是两个进程间通过读/写同一个文件来交换数据。注意这两个进程 操作的对象不会是同一个,只是内容一样。使用这种方式有个缺点,如果进程的读写并发操作的话,可能会导致数据不同步甚至丢失数据。我们知道SharedPreferences是Android提供的一种轻量级存储方案,也是数据文件操作的一种,不建议具有并发性的进程使用此类方式。
3、Messenger
在Message对象中放入要传递的对象,通过Messenger(信使)就可以在进程间传递Message对象了。其底层实现就是AIDL,由于它一次处理一个请求,因此在服务端不存在并发执行的情形。使用方法也比较简单,下面来看一个简单的例子:客户端把消息传递给服务端。
/**
* 服务端
*/
class MessengerService : Service() {
/**
* 2、将Handler与Messenger绑定
*/
private val mMessenger = Messenger(MessengerHandler())
override fun onBind(intent: Intent?): IBinder {
// 3、返回Messenger对象中的Binder
return mMessenger.binder
}
/**
* 1、定义一个Handler内部类用来处理收到的信息
*/
inner class MessengerHandler : Handler(){
override fun handleMessage(msg: Message?) {
if (msg!!.what == 0x123){
Log.e("--------","来自客户端:${msg.data["msg"]}")
}
super.handleMessage(msg)
}
}
}
注册service
<service android:name=".messenger.MessengerService"
android:process=":remote"/>
/**
* 客户端
*/
class MessengerActivity : Activity(){
/**
* 1、定义ServiceConnection 链接回掉
*/
private val mConnection = object : ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
Log.e("------","服务已断开")
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// 2、将Binder 封装给Messenger对象
val service = Messenger(service)
val msg = Message.obtain(null,0x123)
val data = Bundle()
data.putString("msg","你好,我是客户端")
msg.data = data
try {
// 3、向服务端发送信息
service.send(msg)
}catch (e: RemoteException){
e.printStackTrace()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 4、绑定服务
val intent = Intent(this,MessengerService::class.java)
bindService(intent,mConnection, Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
unbindService(mConnection)
super.onDestroy()
}
}
上面的例子演示了服务端如何接受客户端的消息,但是有时候我们还需要服务端向客户端回复结果,如何实现呢,下面我们接着修改上面的例子。先看看 服务端的修改:
override fun handleMessage(msg: Message?) {
if (msg!!.what == 0x123){// 0x123是消息标识,代表来自客户端的消息,可随意指定
Log.e("--------","来自客户端:${msg.data["msg"]}")
// 收到消息后,立即回应客户端
val client = msg.replyTo
val replyMsg = Message.obtain(null,0x124)// 0x124是消息标识,代表来自服务端的消息,可随意指定
val bundle = Bundle()
bundle.putString("reply","哈哈,我收到你的消息了")
replyMsg.data = bundle
try {
client.send(replyMsg)// 向客户端发送消息
} catch (e: RemoteException) {
e.printStackTrace()
}
return
}
super.handleMessage(msg)
}
然后,客户端准备一个接收消息的Messenger和Handler,并将Messenger发送给服务端
private val mGetServiceMessenger = Messenger(MessengerHandler())
...省略部分代码
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// 2、将Binder 封装给Messenger对象
val service = Messenger(service)
val msg = Message.obtain(null,0x123)
val data = Bundle()
data.putString("msg","你好,我是客户端")
msg.data = data
// 注意这一句,为了可以接收到服务端的回应,需要把Messenger对象传递给服务端
msg.replyTo = mGetServiceMessenger
try {
// 3、向服务端发送信息
service.send(msg)
}catch (e: RemoteException){
e.printStackTrace()
}
}
...
inner class MessengerHandler : Handler(){
override fun handleMessage(msg: Message?) {
if (msg!!.what == 0x124){// 0x124代表来自服务端
Log.e("------","来自服务端:${msg.data["reply"]}")
return
}
super.handleMessage(msg)
}
}
运行程序,可以看到Log
05-12 02:32:44.145 4357-4357/com.sange.ipc:remote E/--------: 来自客户端:你好,我是客户端
05-12 02:32:44.172 4320-4320/com.sange.ipc E/------: 来自服务端:哈哈,我收到你的消息了
Messenger的使用比较简单,下图是上述例子的工作原理
两个APP如何通讯
上述例子同一APP中的进程间的通讯,其实 和两个APP通讯原理是一样的,这一点要明白,下面讲下两个APP如何通讯
新建一个APP module,名字叫Service。
将上个例子中的MessengerService文件拷贝进来。
注册 AndroidManifest.xml
<service android:name="com.sange.service.MessengerService"
android:enabled="true"
android:exported="true">
<!--android:exported属性表示是否可被外部的App绑定,true为允许,false为不允许-->
<intent-filter>
<action android:name="com.sange.service.MessengerService"/>
</intent-filter>
</service>
在客户端APP 绑定service
// 4、绑定服务
val intent = Intent()
// 注意一定要写包名,然后是action名字
intent.setClassName("com.sange.service","com.sange.service.MessengerService")
bindService(intent,mConnection, Context.BIND_AUTO_CREATE)
不写包名,会报以下类似错误
05-12 03:18:24.358 6752-6752/com.sange.ipc E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.sange.ipc, PID: 6752
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sange.ipc/com.sange.ipc.messenger.MessengerActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.sange.service.MessengerService }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
由于篇幅较长,另外三种 下篇分析。