要理解跨进程通信,首先需要理解以下几个知识点1:
进程:按照操作系统的描述,进程是资源分配的最小单位,一个进程可以包含多个线程 线程:线程是CPU调度的最小单位,多线程需要考虑并发问题。 1.2 Android中的多进程Android多进程指的是一个应用中存在多个进程的情况,在Android中,一般一个应用存在一个进程。多进程的情况2:
某个应用由于自身原因需要采用多进程模式实现 为了加大一个应用可使用的内存,通过多进程来获取多份内存空间开启多进程模式是通过在
AndroidManifest
中指定Android:process
属性
以“:”
开头的进程属于应用的私有进程,其它应用的组件不可以与它跑在同一个进程中,而不以冒号开头的数据全局进程,其它应用通过shareUID
方式可以和它跑在同一个进程中。
shareUID
:Android系统会为每个程序分配一个不同级别的UID,如果需要相互调用,只能是UID相同才行,这就使得共享数据具有了一定的安全性,每个软件之间是不能随意获得数据的,而同一个Application只有一个UID,所以Application之间不存在访问权限的问题。
一般来说,使用多进程会存在以下几个问题:
静态成员与单例模式完全失效 线程同步机制完全失效 SP(SharedPreference)可靠性会下降 Application创建多次 1.3 数据共享方法如果你需要做一个
Application
将某些服务的Service
,Provider
或者Activity
等的数据共享出去,有如下三个办法:
完全暴露 :使用android:exported="true"
,一旦设置了true
,则将会被完全暴露,可以被其它应用进程调用其暴露出去的数据。没有使用android:exported
的Service
,Provider
或者Activity
,其默认的exported
值为false
,但此时如果设置了intent filter
,则其默认值为true.
权限提示暴露:如果应用A设置了android:permission="xxx.xxx.xxx"
,则你必须在Manifest中使用use-permission
才能访问应用A的东西。
私有暴露:假如说一个公司做了两个产品,只想这两个产品之间可互相调用,那么这个时候就必须使用shareUserID
将两个软件的Uid强制设置为一样的。这种情况下必须使用具有该公司签名的签名文档才能,如果使用一个系统自带软件的ShareUID
,例如Contact,那么无须第三方签名。3
1.4 IPC基础概念介绍
Serialiazable
与Parcelable
:
序列化:讲对象转化为字节的过程
Serialiazable
:Java提供的序列化接口
Parcelable
:Android提供的序列化接口
与Serialiazable
的区别:Parcelable
Serialiazable
使用简单但是需要大量I/O操作,Parcelable
使用较繁琐,主要用于内存序列化,效率高。
Binder
:详见【IPC】Binder跨进程通信机制原理 2 实现IPC方式
实现IPC方式可以分为以下几种方式4:
使用Bundle
使用文件共享
使用SharedPreferences
使用Messenger
使用AIDL
使用ContentProvider
使用Binder
连接池
Broadcast
Socket
管道
2.1 使用Messenger
Messenger
被称为信使,常与Message
一起使用实现跨进程通信。底层只是对Binder
的简单包装
步骤
创建一个Service
,在Service
中的onBind
里返回一个IBinder
对象。
在AndroidManifest
中声明服务,并将该服务放在另外的一个进程中
通过bindService
绑定服务
在客户端创建ServiceConnection
对象,使用服务端返回的IBinder
对象创建一个Messenger
,该Messenger
是服务端的Messenger
,可以通过该Messenger
将客户端数据发送到服务端(该方法是通过客户端向服务端传递数据,如果想从服务端向客户端传递数据,可以通过在客户端创建一个Handler
对象,然后通过该对象创建一个客户端Messenger
,然后通过message.replyTo
5将客户端的Messenger
发送到服务端,然后在服务端获取客户端创建的Messenger
,然后通过Messenger将数据发送给客户端,这样就实现了服务端向客户端传递数据)
通过mMessenger.send(message)
来将数据发送给服务端,从而实现跨进程通信。
代码实现创建一个
Service
,在Service
中的onBind
里返回一个IBinder
对象。
public class MessengerService extends Service {
//服务端自己的信使,为了获取客户端数据
private Messenger mMessenger;
//客户端信使对象,为了将数据发送到客户端
private Messenger cMessenger;
@Override
public IBinder onBind(Intent intent) {
mMessenger = new Messenger(new MyHandler());
System.out.println("MessengerService onBind");
return mMessenger.getBinder();
}
class MyHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Message message = Message.obtain(null,1);
message.arg1 = 1000;
//获取客户端传递来的信使
cMessenger = msg.replyTo;
try {
//通过客户端信使发送服务端数据到客户端
cMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
System.out.println("MessengerService handleMessage");
break;
}
}
}
}
在
AndroidManifest
中声明服务,并将该服务放在另外的一个进程中
使用Messenger实现客户端和服务端双向通信
public class MainActivity extends AppCompatActivity {
//服务端信使,为了发送数据到客户端
private Messenger mMessenger;
//客户端自己的信使,为了获取服务端数据
private Messenger cMessenger;
private static class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
System.out.println("Service Data: " + msg.arg1);
break;
}
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
cMessenger = new Messenger(new MyHandler());
if (mMessenger != null) {
Message msg = Message.obtain(null, 0);
//将客户端自己的信使放在Message里传递到服务端
msg.replyTo = cMessenger;
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mMessenger = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
}
}
运行截图:
Bundle
是final
类型,不可被继承,它实现了Parcelable接口,是一个特殊的Map类型,支持进程间通信。使用Bundle进行进程间通信,其实还是通过Messenger+Message来实现的,核心代码如下:
mMessenger = new Messenger(service);
Message msg = Message.obtain(null, 0);
Bundle bundle = new Bundle();
bundle.putString("IPC", "Bundle");
bundle.putInt("Number", 20);
msg.setData(bundle);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
2.3 使用SharedPreferences
SharedPreferences
并不适合存储大量数据和频繁改变数据,只能用于轻量的存储,高并发读/写时有可能会丢失数据。SharedPreferences
存在以下性能问题6:
跨进程不安全。由于没有使用跨进程的锁,就算使用 MODE_MULTI_PROCESS
,SharedPreferences
在跨进程频繁读写有可能导致数据全部丢失。根据线上统计,SharedPreferences
大约会有万分之一的损坏率。
加载缓慢。SharedPreferences
文件的加载使用了异步线程,而且加载线程并没有设置优先级,如果这个时候读取数据就需要等待文件加载线程的结束。这就导致主线程等待低优先线程锁的问题,比如一个 100KB
的 SP
文件读取等待时间大约需要 50 ~ 100ms
,并且建议大家提前用预加载启动过程用到的 SP
文件。
全量写入。无论是commit()
还是 apply()
,即使我们只改动其中一个条目,都会把整个内容全部写到文件。而且即使我们多次写同一个文件,SP
也没有将多次修改合并为一次,这也是性能差的重要原因之一。
卡顿。由于提供了异步落盘(拷贝到磁盘)的apply
机制,在崩溃或者其它一些异常情况可能会导致数据丢失。所以当应用收到系统广播,或者被调用 onPause
等一些时机,系统会强制把所有的 SharedPreferences
对象的数据落地到磁盘。如果没有落地完成,这时候主线程会被一直阻塞。这样非常容易造成卡顿,甚至是ANR
,从线上数据来看 SP
卡顿占比一般会超过 5%
。
由于以上原因,不建议使用
进行跨进程通信,特别是SharedPreferences
进行跨进程通信不安全。SharedPreferences
利用多进程同时读写同个外部文件达到是数据交互的目的,存储形式没有限制:xml,文本,对象序列化等等。但其有着明显的缺点,由于Linux系统对文件并发读写没有限制,会导致数据不同步问题,所以该方式只适合于对数据同步要求不高的进程间通信
2.5 使用AIDL为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用
(Remote Procedure Call,RPC)
方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)
来公开服务的接口。很多IPC通信方式都是基于AIDL
的。如Service
、ContentProvider
等
AIDL
的简单使用可见:AIDL简单使用
待续
参考文章
带你了解android的IPC机制 ↩︎
Android开发之android中的多进程模式 ↩︎
Android中UID机制和共享进程 ↩︎
Android夸进程通信机制四:使用 Bundle进行进程间通信 ↩︎
Messenger:使用消息的跨进程通信 (Message.replyTo()的使用) ↩︎
Android之SharedPreferences简介及使用说明 ↩︎
作者:It一zhai男