1 需要的知识点
1.1 进程与线程
要理解跨进程通信,首先需要理解以下几个知识点1:
- 进程:按照操作系统的描述,进程是资源分配的最小单位,一个进程可以包含多个线程
- 线程:线程是CPU调度的最小单位,多线程需要考虑并发问题。
1.2 Android中的多进程
Android多进程指的是一个应用中存在多个进程的情况,在Android中,一般一个应用存在一个进程。多进程的情况2:
- 某个应用由于自身原因需要采用多进程模式实现
- 为了加大一个应用可使用的内存,通过多进程来获取多份内存空间
开启多进程模式是通过在AndroidManifest
中指定Android:process
属性
<activity
android:name=".SecondActivity"
android:process=":twoprocess"></activity>
<activity
android:name=".ThirdActivity"
android:process="com.yds.thirdprocess"></activity>
- 以
“:”
开头的进程属于应用的私有进程,其它应用的组件不可以与它跑在同一个进程中,而不以冒号开头的数据全局进程,其它应用通过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
对象。
/**
* Created by yds
* on 2020/4/13.
*/
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
中声明服务,并将该服务放在另外的一个进程中
<service android:name=".MessengerService"
android:process="com.yds.test.messengerservice"/>
使用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);
}
}
运行截图:
2.2 使用Bundle
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
进行跨进程通信不安全。
2.4 使用文件共享
利用多进程同时读写同个外部文件达到是数据交互的目的,存储形式没有限制:xml,文本,对象序列化等等。但其有着明显的缺点,由于Linux系统对文件并发读写没有限制,会导致数据不同步问题,所以该方式只适合于对数据同步要求不高的进程间通信
2.5 使用AIDL
为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)
方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)
来公开服务的接口。很多IPC通信方式都是基于AIDL
的。如Service
、ContentProvider
等
AIDL
的简单使用可见:AIDL简单使用