进程间通信方式 的简单理解和使用
进程间通信方式
1、背景
在讲解Binder前,我们先了解一些Linux的基础知识
1.1、进程空间划分
IPC 即 Inter-Process Communication
(进程间通信)。Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。
1.2、进程隔离 & 跨进程通信( IPC)
进程隔离
- 为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的
跨进程通信( IPC )
- 即进程间需进行数据交互、通信
跨进程通信的基本原理
Android应用和系统services运行在不同进程中是为了安全,稳定,以及内存管理的原因,但是应用和系统服务需要通信和分享数据。
优点
- 安全性:每个进程都单独运行的,可以保证应用层对系统层的隔离。
- 稳定性:如果某个进程崩溃了不会导致其他进程崩溃。
- 内存分配:如果某个进程以及不需要了可以从内存中移除,并且回收相应的内存。
1.3、基础概念:序列化方式
Serializable & Parcelable 原理和区别
1.3.1、Serializable接口
Serializable是Java所提供的一个序列化接口,空接口。
- serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的
- serialVersionUID要和当前类的serialVersionUID相同才能正常的序列化。
- 静态成员变量属于类不属于对象,所以不会参加序列化过程;
- 其次用transient关键字标明的成员变量也不参加序列化过程。
重写如下两个方法可以重写系统默认的序列化和反序列化过程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
1.3.2、SParcelable接口
Android中特有的序列化方式,效率相对Serializable更高,占用内存相对也更少,但使用起来稍微麻烦点。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;//返回当前对象的内容描述,含有文件描述符返回1,否则0
}
public void writeToParcel(Parcel out, int flags) {//将当前对象写入序列号结构中
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
@Override
public String toString() {
return String.format(
"User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
}
}
- 序列化功能由
writeToParcel
方法来完成,最终通过Parcel中的一系列write方法完成的。 - 反序列化功能由
CREATOR
来完成,其内部标明了如何创建序列号对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。 - 内容描述功能由
describeContents
方法来完成,几乎所有情况都返回0,只有当前对象存在文件描述符时,才返回1。
对比
- Serializable是Java中的序列化接口,简单但开销大,序列化和反序列化需要大量的IO操作。
- Parceable是Android中的序列化方式,使用起来麻烦,但是效率高。
2、通讯方式种类
进程间通信(IPC)方式
- 使用Bundle
- 使用文件共享
- 使用Messenger
- 使用AIDL
- 使用COntentProvider
- 使用Socket
3、使用Bundle
我们都知道Android中三大组件Activity,Service,Receiver
都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间进行传输。
当我们在一个进程中启动另外一个进程的Activity、Service、Receiver时,我们就可以在Bundle中附加我们所需要传输给远程进程的信息并通过intent发送出去。这里注意,我们传输的数据必须能够被序列化。
下面我们看一下利用Bundle进行进程间通信的例子:
private void startWithIntent(){
Intent intent = new Intent();
//制定要打开的程序的包名(必须写全包名,不然会报错)和地址(activity名)
intent.setComponent(new ComponentName("PackageName",
"PackageName.intentIpcActivity"));
//通过budle传递数据,可以携带序列化数据
Bundle bundle = new Bundle();
bundle.putInt("intextra", 0);
bundle.putString("stringextra", "测试数据");
intent.putExtras(bundle);
try{
startActivity(intent);
}catch(Exception e){
ToastUtils.showMessage("没有找到对应文件");
}
}
利用Bundle进行进程间通信是很容易的,大家应该也注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它的使用有一定的局限性。
4、使用文件共享
共享文件也是种不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据
- 比如A进程把数据写入文件FILE,B进程可以通过读取这个文件来获取这个数据。
- 通过这种方式,除了可以交换简单的文本信息外,我们还可以序列化一个对象到文件系统中,另一个进程可以通过反序列化恢复这个对象。
比如A在进程中创建一个线程进行写数据
new Thread(new Runnable(){
@Override
public void run(){
User user = new User(1, "user", false);
File cachedFile = new File(CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
在B进程创建一个线程进行读取数据
new Thread(new Runnable(){
@Override
public void run(){
User user = null;
File cachedFile = new File(CACHE_FILE_PATH);
if (cachedFile.exists()){
ObjectInputStream objectInputStream = null;
try{
objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
user = objectInputStream.readObject(user);
} catch(IOException e){
e.printStackTrace();
}finally{
objectInputStream.close();
}
}
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch (IOException e){
e,printStackTrace();
}finally{
objectOutputStream.close();
}
}
- 通过文件共享的这种方式来共享数据对文件的格式是没有具体要求的,比如可以是文本文件、也可以是XML文件,只要读写双方约定数据格式即可。
- 这种方式进行进程间通信虽然方便,可是也是有局限性的,比如并发读/写,这会导致比较严重的问题,如读取的数据不完整或者读取的数据不是最新的。
- 因此通过
文件共享的方式适合在对数据同步要求不高的进程之间通信
,并且要妥善处理并发读/写问题。 SharedPreferences
是个特例,虽然也是文件的一种,但系统在内存中有一份SharedPreferences
文件的缓存,因此在多线程模式下,系统的读/写就变得不可靠
,高并发读写SharedPreferences
有一定几率会丢失数据,因此不建议在多进程通信中使用SharedPreferences
。
5、使用Messenger
5.1、Messenger是什么?
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,可以在不同进程中传递Messenger对象,在Messenger中放入我们需要传递的数据。
- 它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。
- 底层实现是AIDL,对AIDL进行了封装。Messenger 服务端是以串行的方式来处理客户端的请求的,不存在并发执行的情形。
- 优势在于我们可以免于自己去定义.aidl文件,使用系统中提前定义好的Messenger.aidl
Messenger
通常和Message、Handler
一起使用Messenger
中封装了Handler
,通过Messenger.send(Message)
最终也就是调用了Handler.sendMessage()
5.1.1、Messenger源码简单说明
Messenger 有两个构造函数:
- 以 Handler 为参数
- 以 Binder 为参数
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target); //和前面的 AIDL 很相似吧
}
- 1、Messenger构造方法一(创建clientMessenger)
调用了Handler的getIMessenger
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
- 2、Messenger构造方法二(得到serviceMessenger)
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
- 3、看下
Handler.getIMessenger
() 源码:getIMessenger
方法获取MessengerImpl
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}
这个 IMessanger
应该也是个 AIDL 生成的类
吧,看下源码,果然是:
public interface IMessenger extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements
android.os.IMessenger {
private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static android.os.IMessenger asInterface(...}
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {...}
private static class Proxy implements android.os.IMessenger {...}
public void send(android.os.Message msg)
throws android.os.RemoteException;
}
IMessenger
是 AIDL 生成的跨进程接口,里面定义了一个发送消息的方法:
public void send(android.os.Message msg)
throws android.os.RemoteException;
Handler
中 MessengerImpl
实现了这个send
方法,就是使用 Handler 将消息发出去:
public void send(Message message) throws RemoteException {
mTarget.send(message);
}
所以,在Handler中有如下代码
getIMessenger
方法获取MessengerImpl
MessengerImpl
又继承自IMessenger.Stub
,并且实现了send
方法,send
方法最终调用的是Handler.sendMessage
总结
- 1、
Messenger
中持有一个IMessenger
的引用,在构造函数中可以通过Handler
或者Binder
的形式获得最终的IMessenger
实现,然后调用它的send
() 方法。 - 2、
Messenger
其实就是AIDL
的简化版,它把接口都封装好,我们只需在一个进程创建一个 Handler 传递给 Messenger
,Messenger
帮我们把消息跨进程传递到另一个进程,我们在另一个进程的 Handler 在处理消息
就可以了。
5.2、使用步骤
实现一个Messenger有如下几步,分为服务端和客户端:
5.2.1、服务端:
1、创建一个Service
来处理客户端的连接请求
2、创建一个Handler
并通过它来创建一个Messager
对象
3、在Service
的onBind
中返回这个Messager
对象底层的Binder
即可
5.2.2、客户端
1、绑定这个服务端的Server
2、用服务端返回的IBinder
对象创建一个Messager
对象,通过这个Messager
对象向服务端发送Message消息
3、若需要服务端回应客户端,需要创建一个Handler
并创建一个新的Messager
,并通过Messa
的replyTo
参数传递给服务端,服务端通过这个replyTo
参数就可以回应客户端
5.3、使用案例
1、AndroidManifest.xml
添加
<service
android:name=".MyService"
android:process=":wangrui"></service>
2、activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="50dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_ipc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="IPC连接"/>
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_gravity="center"
android:text="IPC通信"/>
</LinearLayout>
3、MyBean.java
public class MyBean implements Parcelable {
private String name;
public MyBean(){
}
protected MyBean(Parcel in) {
name = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<MyBean> CREATOR = new Creator<MyBean>() {
@Override
public MyBean createFromParcel(Parcel in) {
return new MyBean(in);
}
@Override
public MyBean[] newArray(int size) {
return new MyBean[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4、MyService.java
1、创建一个Service来处理客户端的连接请求
2、创建一个Handler并通过它来创建一个Messager对象
3、在Service的onBind中返回这个Messager对象底层的Binder即可
public class MyService extends Service {
private Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//客户端→服务端
Bundle bundle = msg.getData();
bundle.setClassLoader(MyBean.class.getClassLoader());
MyBean myBean = bundle.getParcelable("message");
Toast.makeText(MyService.this,myBean.getName(),Toast.LENGTH_SHORT).show();
//服务端→客户端
try {
Messenger clientMessenger = msg.replyTo;
myBean = new MyBean();
myBean.setName("皮卡丘对王睿使用了十万伏特");
bundle = new Bundle();
bundle.putParcelable("message",myBean);
Message message = new Message();
message.setData(bundle);
message.replyTo = clientMessenger;
clientMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private Messenger messenger = new Messenger(handler);
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}
5、MainActivity.java
1、绑定这个服务端的Server
2、用服务端返回的IBinder对象创建一个Messager对象
3、回应客户端,需要创建一个Handler并创建一个新的Messager,并通过Messa的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端
public class MainActivity extends AppCompatActivity {
private Button btnIPC;
private Button btnSend;
private Messenger messengerProxy;
private Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
bundle.setClassLoader(MyBean.class.getClassLoader());
MyBean myBean = bundle.getParcelable("message");
handler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,myBean.getName(),Toast.LENGTH_SHORT).show();
}
},3000);
}
};
private Messenger clientMessenger = new Messenger(handler);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnIPC = findViewById(R.id.btn_ipc);
btnIPC.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder iBinder) {
messengerProxy = new Messenger(iBinder);
Toast.makeText(MainActivity.this,"连接成功",Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
});
btnSend = findViewById(R.id.btn_send);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyBean myBean = new MyBean();
myBean.setName("王睿对皮卡丘使用了精灵球");
try {
Message message = new Message();
message.replyTo = clientMessenger;
Bundle bundle = new Bundle();
bundle.putParcelable("message",myBean);
message.setData(bundle);
messengerProxy.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
5.4、特征分析
特征:
- Messenger内部消息处理使用Handler实现的,所以他是
以串行的方式处理客户端发送过来的消息的
- 如果有大量的消息发送给服务端,服务端只能一个一个处理,如果
并发量大的话用Messenger就不合适了
- 而且Messenger的主要作用是为了传递消息的,很多时候我们需要跨进程调用服务端的方法,这种需求Messenger就无法做到了。
注意:
客户端和服务端是通过拿到对方的 Messenger
来发送 Message
的。只不过客户端通过 bindService onServiceConnected
而服务端通过 message.replyTo
来获得对方的 Messenger
。Messenger
中有一个 Hanlder
以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。
6、使用AIDL
6.1、AIDL是什么?
AIDL (Android Interface Definition Language)
是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类构架,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:
- 定义一个AIDL接口
- 为远程服务(Service)实现对应Stub
- 将服务“暴露”给客户程序使用
“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。
6.2、AIDL的使用
AIDL文件支持的数据类型
使用
- 1、服务端
- 首先创建一个Service用来监听客户端的连接请求,
- 然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,
- 最后在Service中实现这个AIDL接口即可。
- 2、客户端
- 首先绑定服务端的Service,
- 绑定成功后,将服务端返回的Binder对象转化成AIDL接口所属的类型,调用相对应的AIDL中的方法。
6.3、注意事项
1、如果ALDL文件中用到了自定义的Parcelable类型
,必须新建一个和它同名ALDL
文件,并在其中声明它为Parcelable
类型。
2、AIDL中除了基本数据类型,其他类型参数必须标上方向:in、out或inout
。
3、AIDL接口中只支持方法,不支持声明静态常量。
4、为了方便AIDL开发,建议把所有和AIDL相关的类和文件都放在同一个包中,好处在于,当客户端是另一个应用的时候,我们可以直接把整个包复制到客户端工程中去。
5、AIDL的包结构在服务端和客户端要保持一致,否则会运行出错。
6、客户端的listener和服务端的listener不是同一个对象,RemoteCallbackList是系统专门提供用于删除跨进程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因为所有AIDL接口都继承自android.os.IInterface接口。
7、需注意AIDL客户端发起RPC过程的时候,客户端的线程会挂起,如果是UI线程发起的RPC过程,如果服务端处理事件过长,就会导致ANR。
6.4、使用案例
AIDL浅显易懂
1、 新建AIDL接口文件
// RemoteService.aidl
package com.example.mystudyapplication3;
interface IRemoteService {
int getUserId();
}
2、创建远程服务
public class RemoteService extends Service {
private int mId = -1;
private Binder binder = new IRemoteService.Stub() {
@Override
public int getUserId() throws RemoteException {
return mId;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
mId = 1256;
return binder;
}
}
3、声明远程服务
<service
android:name=".RemoteService"
android:process=":aidl" />
4、绑定远程服务
public class MainActivity extends AppCompatActivity {
public static final String TAG = "wzq";
IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iRemoteService = IRemoteService.Stub.asInterface(service);
try {
Log.d(TAG, String.valueOf(iRemoteService.getUserId()));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iRemoteService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(MainActivity.this, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);
}
}
7、使用ContentProvider
7.1、ContentProvider定义和使用
ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider
- ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过接口来操作接口内的数据,包括增、删、改、查等。
- ContentProvider分为系统的和自定义的,系统的也就是例如联系人,图片等数据。
开发一个ContentProvider的步骤:
- 1、定义自己的
ContentProvider
类,该类继承ContentProvider
基类; - 2、在
AndroidManifest.xml
中注册这个ContentProvider
,类似于Activity
注册,注册时要给ContentProvider
绑定一个域名; - 3、当我们注册好
ContentProvider
后,其他应用就可以访问ContentProvider
暴露出来的数据了。
ContentProvider
只是暴露出来可供其他应用操作的数据,其他应用则需要通过ContentReslover
来操作ContentProvider
所暴露出来的数据。Context提供了getContentResolver
()方法来获取ContentProvider
对象,获取之后皆可以对暴露出来的数据进行增、删、改、查操作了。
使用ContentResolver操作数据的步骤:
- 1、调用Activity的getContentResolver()获取ContentResolver对象
- 2、根据调用的ContentResolver的insert()、delete()、update()、和query()方法操作数据库即可。
8、不同IPC优缺点对比
参考
1、[学习笔记]Android开发艺术探索:IPC机制
2、Android基础-Android进程间通信方式
3、Messenger实现跨进程双向通信
4、Android面试吃透这一篇就没有拿不到的offer!