IPC机制
引言
在我们使用 Android 手机的时候,有时我们使用的软件会需要消耗比较大的内存,也经常会需要同时打开多个软件。这些时候,我们都会需要使用到多进程技术。作为 Android 开发者,相信我们都知道如何去开启应用的单个多进程,但是开启多进程之后,如何进行「进程间通信(IPC)」呢?进程通信的方法有很多,他们适用于不同的使用场景,下面我们来逐个了解。
前置知识
- Android 四大组件
- Java 序列化
IPC简介
相信学过大学「操作系统」这门课的同学都还记得 进程间通信
信号量机制
这些名词,今天我们学习的也是操作系统的通信,不过是针对以 Linux 为内核的 Android 操作系统。我们通常会以一个软件或者程序为进程。而 Android 是可以使用多进程的,对于稍微大型一些的软件也都会使用到多进程。使用多进程的目的有如下几个:
- 进程隔离,以达到某些特殊的业务需求
- 扩大软件的内存大小。
多进程的开启很简单,其唯一方法是给注册文件 AndroidManifest.xml
中的四大组件部分添加 android:process
属性即可
<activity
android:name="com.qxy.potatos.module.mine.activity.WebViewActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="false"
android:process=":h5"
android:screenOrientation="portrait"
android:theme="@style/BlackTheme" />
上述的 android:process=":h5"
指的是该程序下的私有进程,是 com.qxy.potatos:h5
的简写。如果属性值换为全局形式的 com.qxy.potatos.h5
,则表示该进程是全局的,可共享给其他应用的。
而多进程出现后会导致如下的一些问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharePreferences 的可靠性下降
- Application 多次创建
上述问题为何会出现呢?
由于 Android 为每个独立进程都分配了一个虚拟机,那么虚拟机的内存空间必然是不同的,所以不同的量在不同内存中都有一份副本,在不同进程中只能修改其内存下的副本。所以1和2中,无论是加何种锁,不作用在同一片内存空间中都是失效的。
而3则是由于 SharePreferences 是存储在 xml 文件中的,不同进程对该文件的并发读写时会导致数据出错的
4中则是由于 Android 的启动机制是每次都要由启动新的 Application,则每个进程都会有一个自己的 Application。我们也需要着重注意这个问题,在Application 中做好启动分类,在多进程启动阶段,防止不需要的资源多次加载
基于上述的原因和问题,我们需要深入了解 IPC 机制,让跨进程通信更好的服务于我们,解决多进程所带来的问题。
IPC基础知识
序列化与反序列化
概述
在谈论序列化与反序列化问题之前,我们需要先了解他们是什么,且作用有哪些。
序列化的意思就是将对象转化为字节序列的过程
反序列化则是将字节序列恢复为对象的过程
那么将对象序列化为字节序列有什么用呢?
将对象序列化为字节序列,可以在传递和保存对象的时候,保证对象的完整性和可传递性。使其易于保存在本地或者在网络空间中传输。
而反序列化,可以将字节流中保存的对象重建为对象
所以,其最核心的作用就是,对象状态的保存和重建
序列化优点
- 序列化后的为字节流的对象,存储在硬盘中方便JVM重启调用
- 序列化后的二进制序列能够减少存储空间,方便永久性保存对象
- 序列化成二进制字节流的对象方便进行网络传输
- 序列化后的对象可以进行进程间通信
Android中的序列化手段
基于上述的讨论,我们知道了何为序列化以及序列化的作用和优点。这其中提到序列化的一大特性就是用于进程间通信,而在后续提到的进程间通信手段中,他们共同的点都是传递信息时将对象序列化,接收信息时则是将对象反序列化。
在Android中需要学习使用到的序列化手段有两个,分别是 Serializable
和 Parcelable
Serializable
Serializable
是 Java 自带的序列化接口,我们使用者只需要继承Serializable
接口即可实现对该对象的序列化了。而具体去调用对其序列化和反序列化过程的也是 Java 提供的API。ObjectOutputStream
可以实现对象的序列化,ObjectInputStream
实现对象的反序列化。//序列化 User user = new User(1, "hello world", false); ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("cache.txt")); objectOutputStream.writeObject(user); objectOutputStream.close(); //反序列化 ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream("cache.txt")); User user = (User) objectInputStream.readObject();//会在此处进行检查,是否同一 serialVersionUID objectInputStream.close();
需要注意的是,被序列化的
User
类一般情况下需要指定一个serialVersionUID
,其作用是对该类做唯一标识,在反序列化时候会进行 serialVersionUID 的比对,如果不一致则会认为版本不同出现报错。但是,如果不指定该 ID 也是可以正常实现序列化和反序列化的,因为系统会自动生成该类的 hash 值赋给 serialVersionUID 。那么为什么我们还要建议手动复制呢?因为 hash 值是根据类的变化在变化的,如果 ID 是 hash 值的话,我们在序列化对象后更改了对象的结构就会导致前后 ID 不一致,使得该对象无法被反序列化。但是手动指定的 ID 可以让被更改过的对象依旧可以被反序列化,可以最大限度地恢复其内容。
public class User implements Serializable { private static final long serialVersionUID = 519067123721295773L;//静态成员不参与序列化过程,代表类的状态 public int userId; public String userName; public boolean isMale; public transient int hhhhh;//被 transient 关键字修饰不参与序列化过程,代表对象的临时数据 public Book book;//该类必须可以被序列化,即继承了 Serializable 接口,否则会报错。每个成员变量都必须可被序列化。 }
Parcelable
Parcelable
是 Android 特有的序列化方法。他也是一个接口,实现该接口比 Serializable 要复杂一些。由于他是 Android 自带的序列化方法,所以对 Android 更加友好,实现该接口后的对象可以通过 Binder 来实现跨进程传递。public class User implements Parcelable { public int userId; public String userName; public boolean isMale; public Book book; public User(int userId, String userName, boolean isMale) { this.userId = userId; this.userName = userName; this.isMale = isMale; } /** * 自定义构造函数,辅助反序列化过程,其中由于 book 是另一个可序列化对象 * 所以,反序列化过程需要传递当前线程的上下文类加载器 */ private User(Parcel in) { userId = in.readInt(); userName = in.readString(); isMale = in.readInt() == 1; book = in.readParcelable(Thread.currentThread().getContextClassLoader()); } /** * 内容描述,基本都返回 0 */ @Override public int describeContents() { return 0; } /** * 序列化过程 */ @Override 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>() { @Override public User createFromParcel(Parcel in) { return new User(in);//调用自写的构造函数 } @Override public User[] newArray(int size) { return new User[size]; } }; }
系统自带的实现了 Parcelable 接口的类有:Intent、Bundle、Bitmap…,其中 List 和 Map 也可以直接序列化,但是要保证其中的每个元素都是可序列化的。注意,基本数据类型是天然支持序列化的了。
优劣对比
Serializable:Java自带、操作简单,但是I/O操作多、开销大(多用于序列化到存储设备,以及网络传输中)
Parcelable:Android自带、效率高,使用稍微复杂些(Android 中首选,多用于内存序列化)
Binder_AIDL
上面提到过 Binder 可用于进程间通信。那么 Binder 是什么东西呢?我们该如何理解 Binder ?
Binder 实际上是 Android 内部的一个类,其实现了 IBinder 接口,主要作用是用于支持 Android 的跨进程通信。
如上图所示,我们可以视 Binder 为一个驱动,其连接客户端(进程1)与服务端(进程2),客户端和服务端绑定后就借助 Binder 驱动来通信和获取对应服务。而在 Android 的底层设计中,Binder 也是 ServiceManager 连接各种 Manager 的桥梁。
那标题的 AIDL 又是什么呢?AIDL 使用 Binder 进行进程间通信的较常用较典型的方法。查看一个简单的 AIDL 示例可以简单的了解它是如何使用 Binder 进行进程间通信的。
AIDL 全称为:Android Interface Definition Language,即Android接口定义语言。它是一种模板,我们根据对应的规则写好我们需要的通信接口之后, AIDL 会为我们自动生成对应的 IPC 代码。
这里做一个示例,假设我们需要进行进程间通信的相关类为 Book,需要的通信服务为获得 Book 书单,以及向 Book 书单中添加书籍。那么我么可以以如下方式编写一个 AIDL 文件。
首先需要将 Book 类序列化
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
private Book(Parcel source) {
bookId = source.readInt();
bookName = source.readString();
}
/**
* Describe the kinds of special objects contained in this Parcelable
* instance's marshaled representation. For example, if the object will
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
* the return value of this method must include the
* {@link #CONTENTS_FILE_DESCRIPTOR} bit.
*
* @return a bitmask indicating the set of special object types marshaled
* by this Parcelable object instance.
*/
@Override public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
*
* @param source The Parcel to read the object's data from.
* @return Returns a new instance of the Parcelable class.
*/
@Override public Book createFromParcel(Parcel source) {
return new Book(source);
}
/**
* Create a new array of the Parcelable class.
*
* @param size Size of the array.
* @return Returns an array of the Parcelable class, with every entry
* initialized to null.
*/
@Override public Book[] newArray(int size) {
return new Book[size];
}
};
}
然后要编写两个 AIDL 文件
// Book.aidl
package com.qxy.potatos.module.test.aidl;
parcelable Book;
// IBookManager.aidl
package com.qxy.potatos.module.test.aidl;
import com.qxy.potatos.module.test.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
Book.aidl
文件中,需要对自定义序列化的 Book 类进行序列化声明。然后在 IBookManager.aidl
文件中,声明两个方法,分别对应提出的两个需求,同时记得一定要手动导入 Book 类的路径。
编写好 AIDL 文件后,将项目 rebuild 一下,就可以在 build 的 generate 文件中看到生成的 IBookManager
类了。生成的这个类就可以用于辅助我们进行进程间通信了。
由于生成的文件较长,这里就不放出生成的文件源码了。我们看其生成的模板代码,可以得知,在不同进程时候,调用的是 Stub 内部的代理类 Proxy 来执行跨进程功能。同一进程的时候,则不会走这种跨进程的 transact 功能。
其中,几类方法或者成员变量值得关注
- DESCRIPTOR:Binder 唯一标识,一般是类名
- asInterface:负责将服务端的 Binder 对象转化为该接口类型。根据是否同一进程有不同转换返回值。
- asBinder:返回当前 Binder 值
- onTransact:运行在服务端线程池中,从 data 中提出参数,执行 code 的目标,然后再 reply 中返回。该方法是 Boolean 类型,返回值为
true
表示执行成功,false
表示请求执行失败,可以控制这个返回值做权限隔离。 - Proxy#getBookList Proxy#addBook:这两个接口的实现方法运行在客户端线程池中。把参数信息写入 data 中,然后通过序列化、Binder 等手段发送给服务端去执行,然后线程挂起等待执行结果。如果有结果返回后,在 reply 中取出结果。
由上述可知,这样的 Binder 过程是耗时的,不应执行在 UI 线程中;同时,由于其运行规则,我们需要采取同步的方式来进行,即使它很耗时,都需要客户端挂起等待服务端响应。
IPC各类手段
基于Binder的手段
Intent-Bundle
使用 Intent 是可以在启动四大组件的时候携带数据传递过去的,即使是不同进程间的数据,因为 Intent 的底层是使用了 Binder 机制来传递数据的。而在传递数据的时候,需要考虑的一点是就是数据需要时可序列化的。,此时我们可以使用 Bundle 类型来装载数据,Bundle 是支持序列化的一类常用数据。
大家都知道,通过intent是能够传递数据的,但是当传递的数据量过大是,会抛出TransactionTooLargeException的异常,这就证明我们用intent传递数据时,只能是一些很小的数据,以防应用崩溃。
安卓在TransactionTooLargeException异常的文档中,就已经进行了详细的说明。简单来说,是因为当使用Intent来传递数据时,用到了Binder机制,数据就存放在了Binder的事务缓冲区里面,而事务缓冲区是有大小限制的,在没有修改过ROM的手机里,一般都是1M的大小。但是这并不意味着小于1M的数据就是安全的,因为这个事务缓冲区,是当前整个进程共享,可能会同时有多个地方在传递数据。这也就是说我们用intent传递数据时,就一定要考虑数据的大小
如何解决
开发中如果activity之间要传递一个Bitmap图片、文件等内容,虽然Bitmap对象已经序列化,可以通过Bundle传递,但是Bitmap对象通常较大,很容易引发TransactionTooLargeException异常。所以我们可以只传递该图片的id、网址、文件路径等,然后在接收的activity通过这些参数去还原该图片或者文件,而不需要把Bitmap对象或文件给整体传过去。
Messenger
Messenger
是一种基于 AIDL 封装的轻量型 IPC 方式,其直译为信使,负责的就是在进程间传递 Message 信息。Messenger 作为一个单次传递的信使,一次只能出力一个请求,传递单份信件,无需做线程同步的处理。
由于 Messenger 基于 AIDL 封装的,所以它的实现思路也是基于 C/S 结构通信。我们可以在客户端进程中绑定服务端的 Service,利用服务端返回的 IBinder 来创建一个 Messenger 对象,然后再使用这个对象来进行通信。具体编写如下所示:
首先我们来看服务端,我们在 onBinder
中返回了当前创建的 Messenger 的底层 Binder 实例。而其中的 Messenger 则是由一个自定义的 Handler 来创建的,这个 Handler 中先是在接收到匹配的信息后,对其内容进行打印;然后 msg.replyTo 是客户端传过来的回复信使,使用其在服务端进行实例声明,接着使用回复信使发送 relpyMessage
。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
接者我们再看客户端,首先使用 ServiceConnection 来编写与服务端的连接逻辑,这部分逻辑中,使用了服务端返回的 IBinder 创建一个信使 mService,mService 发送的 Message 中,记得要设置 replyTo
参数,要跟着发送给服务端的。这个 replyTo 参数是一个新的信使,其也使用了一个新的 Handler 来处理服务端返回的信息,其作用是用于服务端传递返回的消息。
public class MessengerActivity extends Activity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Log.d(TAG, "bind service");
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg", "hello, this is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent("com.ryg.MessengerService.launch");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
由上述的代码实践中,我们可以看到在发送消息和返回消息这两次的消息传递中,我们使用了两个 Messenger 信使,这也说明了这一种 IPC 方式不是高效的,无法并发处理消息。而且在 Message 中,我们最常用到的数据类型就是 Bundle 了,由于它是已支持序列化的,所以可以进程间通信,这一个字段支持大量的数据类型,让我们可以进行传递的类型也更加丰富。还有一点需要注意的是,Message 的 object 字段,只有系统实现了 Parcelable 接口的数据类型才可以利用这个字段进行进程间通信。
AIDL
那么,作为 Messenger 的底层实现,AIDL 和 Messenger 又有什么不同呢?他们的不同点其实在于使用和功能方面
Messenger:不用编写 AIDL 文件,支持直接创建实例调用通信;串行处理消息;只支持传递消息
AIDL:需要编写 AIDL 文件生成支持 Binder 的代码;可以处理大量的并发请求;支持调用服务端的方法,不止于传递信息
前面已经介绍过 AIDL 的基本结构组成和生成方法了,这里再补充一些编写 AIDL 文件的规则
支持的数据类型
- 基本数据类型,int、short等
- String 和 CharSequence
- List:只支持 ArrayList ,且每个元素都需要被 AIDL 支持
- Map:只支持 HashMap ,且每个元素都需要被 AIDL 支持
- Parcelable:所有实现了 Parcelabel 接口的方法
- AIDL:所有 AIDL 接口文件本身
import规则:所有自定义的 Parcelable 对象和 AIDL 对象都需要显式 import 进来
自定义 Parcelable 对象规则:如果使用到自定义的 Parcelable 对象,那么需要为每一个这样的对象新建一个与它同名的 AIDL 文件
参数标志:除了基本类型,其他类型的参数必须标上
in
、out
或inout
。分别表示:输入型参数、输出型参数、输入输出型参数。不要乱用,因为各自开销不同,乱用影响性能接口支持:AIDL 中可以定义接口,但是这种接口只支持方法,不支持声明静态常量
文件位置:为了让方便迁移 AIDL 文件,建议把所有的 AIDL 文件放在同一个包中。因为如果需要将客户端和服务端的实现分到不同工程包中,或者要在不同 APP 中做 AIDL 通信,需要在客户端和服务端保持 AIDL 的包结构一致。所以如果放在同一个包中就方便迁移了。保持结构一致的原因是为了让反序列化可以正常执行。当然,新版的 AS 中,新建 AIDL 文件就会自动帮你归纳到 aidl 包中了。
下面我们看一个利用 AIDL 来进行进程间通信的例子,它一样使用的是 C/S 结构
这里要实现的功能是客户端进程从服务端中获取书籍的列表,以及对其添加新的书籍;并且需要实现一个观察者模式,在客户端订阅书籍之后,服务端定时检查书籍并向客户端发布最新的书籍内容。其中,AIDL 文件可以基于上文中提到的进行修改,首先,需要新建一个观察者模式中需要用到的监听接口。其次,在 IBookManager.aidl
中添加绑定监听和解绑监听的接口。
// IOnNewBookArrivedListener.aidl
package com.qxy.potatos.module.test.aidl;
import com.qxy.potatos.module.test.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
// IBookManager.aidl
package com.qxy.potatos.module.test.aidl;
import com.qxy.potatos.module.test.aidl.Book;
import com.qxy.potatos.module.test.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
由 AIDL 文件生成 Binder 模板代码后,接下来就可以编写服务端的代码了。服务端的主要逻辑是实例化 IBookManager.Stub()
返回给连接它的客户端,并且设置了一个 5 秒向客户端启用一次回调的子线程服务。其中需要注意的是,CopyOnWriteArrayList
是 自适应支持并发的 ArrayList,所以无需担心其中的并发问题。而另一个的 RemoteCallbackList
,是用于专门用于提供来绑定和解绑跨进程 Listener 接口的类,由于跨进程通信使用到的序列化和反序列化出来的示例并非同一示例,所以 RemoteCallbackList
中以 IBinder 为 key 存储监听对象,就可以解决多次传递 Listener 不是同一 Listener 的问题了。
CopyOnWrite
容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对
CopyOnWrite
容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite
容器也是一种读写分离的思想,读和写不同的容器。
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListeners = new RemoteCallbackList<>();
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
public BookManagerService() {
}
/**
* <p>根据 AIDL 生成的模板代码,建立一个 Binder 实例</p>
*
* <p>里面实现了包括权限检查,以及四个接口</p>
*/
private Binder mBinder = new IBookManager.Stub() {
/**
* <pre>
* 该方法是服务端用于处理客户端请求的方法,所以可以在这里进行权限的鉴别。
* </pre>
*
* @param code 客户端请求的目标方法
* @param data 包含目标方法所需的参数
* @param reply 目标方法的返回值
* @param flags 标志
* @return 如果不允许通信,则返回 false,否则返回 true
* @throws RemoteException 异常抛出
*/
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.qxy.potatos.permission.ACCESS_BOOK_SERVICE");
LogUtil.d(TAG, "onTransact check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
@Override public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
/**
* <p>
* 直接调用 RemoteCallbackList 的监听方法,该类专门为多进程监听而生
* 调用 RemoteCallbackList 的 beginBroadcast() 计数后,记得调用 finishBroadcast() 停止计数
* </p>
*
* @param listener 传入的监听实例
* @throws RemoteException 异常抛出
*/
@Override public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListeners.register(listener);
final int N = mListeners.beginBroadcast();
LogUtil.i(TAG,"current size :" + N);
mListeners.finishBroadcast();
}
@Override public void unRegisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListeners.unregister(listener);
final int N = mListeners.beginBroadcast();
LogUtil.i(TAG,"current size :" + N);
mListeners.finishBroadcast();
}
};
/**
* @param intent 传递的 Intent 类
* @return 返回值是当下的 Binder 实例,供客户端调用
*/
@Override public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.qxy.potatos.permission.ACCESS_BOOK_SERVICE");
LogUtil.d(TAG, "onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
/**
* Called by the system when the service is first created. Do not call this method directly.
*/
@Override public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(1,"IOS"));
new Thread(new ServiceWorker()).start();
}
/**
* Called by the system to notify a Service that it is no longer used and is being removed. The
* service should clean up any resources it holds (threads, registered
* receivers, etc) at this point. Upon return, there will be no more calls
* in to this Service object and it is effectively dead. Do not call this method directly.
*/
@Override public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
/**
* 在有新书之后,遍历所有以及进行注册的监听者,然后调用其中的回调方法,把书籍实例传递过去
* @param book 需要通知到客户端的书籍实例
* @throws RemoteException 抛出异常
*/
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListeners.getBroadcastItem(i);
if (l != null){
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListeners.finishBroadcast();
}
/**
* 每隔5秒,启动一次处理回调的方法
*/
private class ServiceWorker implements Runnable{
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override public void run() {
while (!mIsServiceDestroyed.get()){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
Book newBook = new Book(bookId,"new book# " + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
设计好服务端后,我们来看一下客户端.客户端在 ServiceConnection 中获取到 IBookManager 的实例,接着就可以调用服务端中的代码了。而 onNewBookArrived 回调接收到的信息,则由 Handler 转换到 UI 线程中处理。还需要注意的一点是,客户端中使用 DeathRecipient 来设置死亡代理,可以在服务端死亡断联后,重新对其发起连接。
public class CoupletsActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
/**
* <p>使用了弱引用和静态类,防止内存泄漏</p>
*
* <p>由于客户端接收到的消息后,一般是要使用 UI 线程将内容改变显示出来。但是监听接口是运行在客户端线程池中的,不是 UI
* 线程,所以需要使用到 Handler 再将此消息传递到 UI 线程中使用</p>
*/
private static class SafeHandler extends Handler{
private WeakReference<CoupletsActivity> ref;
public SafeHandler(@NonNull Looper looper,CoupletsActivity activity){
super(looper);
this.ref = new WeakReference<>(activity);
}
/**
* Subclasses must implement this to receive messages.
*
* @param msg
*/
@Override public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
LogUtil.d(TAG,"receive new book :" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 当服务端意外死亡后,进行断连和重连
*/
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override public void binderDied() {
if (mRemoteBookManager == null){
return;
}
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mRemoteBookManager = null;
//重新绑定
Intent intent = new Intent(CoupletsActivity.this, BookManagerService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
};
/**
* <p>连接 service 的方法,运行在主线程中,是连接调用服务端的第一使用位置</p>
*
* <p>在连接服务的方法中,可以接收到服务端返回的 Binder 实例,然后以此实例化用于通信的类,以及调用其中的接口方法</p>
*
* <p>注意,这里还进行了死亡代理的设置</p>
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient,0);
List<Book> list = bookManager.getBookList();
LogUtil.i(TAG,"list type: "+list.getClass().getCanonicalName());
LogUtil.i(TAG,"list: "+list.get(1).bookName);
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
}
};
/**
* 使用 Handler,将调用转移到 UI 线程中进行
*/
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub(){
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*
* @param newBook
*/
@Override public void onNewBookArrived(Book newBook) throws RemoteException {
new SafeHandler(getMainLooper(),CoupletsActivity.this)
.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook.bookName).sendToTarget();
}
};
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_couplets);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
@Override protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()){
LogUtil.i(TAG,"unregister listener: " + mOnNewBookArrivedListener);
try {
mRemoteBookManager.unRegisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(serviceConnection);
super.onDestroy();
}
}
AIDL 调用服务端的方法时候,特别要注意线程耗时问题,因为服务端的执行跨进程方法的时候,客户端的方法也会挂起等待。所以这时客户端对该方法的调用不能运行在 UI 线程中,否则很容易出现 ANR。而客户端连接服务端的 onServiceConnected
和 onServiceDisconnected
正是运行在 UI 线程中的,所以如果服务端的方法是耗时的,记得手动切换客户端的调用到子线程中。
同理,服务端要调用客户端的监听方法 onNewBookArrived 的时候,也要注意 onNewBookArrived 是否为耗时方法,因为服务端调用客户端的时候也会挂起。我们需要保证服务端的调用运行在子线程中,否则容易出现无法响应的情况,因为 Service 默认运行在主线程中的。
ContentProvider
ContentProvider
作为 Android 的四大组件之一,其功能是用于 Android 的不同程序间进行数据共享。它的底层实现也是 Binder ,但是由于 Android 的封装,我们对 ContentProvider 的使用就会简单方便很多。Android 的不同程序就是不同进程,所以 ContentProvider 就是用于进程间通信的官方手段。
由于 ContentProvider 是 Android 的基础内容,所以这里不做过多的基础使用介绍,需要复习的同学可以查看《第一行代码》,下面只做一些重点解释和需要注意的点。
ContentProvider 提供给开发者几个分别需要重写的抽象方法,其中最主要的分别是增删查改四个方法,这几个方法在涉及的结构上是很像数据库的四个操作方法。但是它所能支持的数据操作不限于数据库,图片、文本等都可以通过它提供给外界进行增删查改。下面的代码是一个内容提供器的实现,主要功能是提供给其他进程操作该进程的两个数据库,相信大家都能看懂下面的代码。
下面我们对一些重要的点做说明
- 需要重写的6个方法中,只有 onCreate() 是运行在主线程中的,其他的5个方法都是运行在 Binder 线程池的。
- 在进行了数据更新的地方使用
mContext.getContentResolver().notifyChange(uri, null);
通知注册了观察者的客户端。而在客户端可以使用registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer) unregisterContentObserver(ContentObserver observer)
注册和解除其注册。 - 增删查改方法是并发执行的,所以要做好其同步操作。但是由于调用的 SQLiteDatebase 是自支持线程同步的,所以使用单个 SQLiteDatebase 操作的时候无需考虑同步操作,但是同时使用多个 SQLiteDatebase 操作的时候就会导致数据操作出错。
- 如果共享的数据底层是 List 这种,则需要考虑多线程带来的线程不安全问题,并发操作时候容易出现脏数据。那么可以使用
Collection
集合、vector
类或者是CopyOnWrite
类来进行数据同步操作。
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.ryg.chapter_2.book.provider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://"
+ AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://"
+ AUTHORITY + "/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(
UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
private Context mContext;
private SQLiteDatabase mDb;
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate, current thread:"
+ Thread.currentThread().getName());
mContext = getContext();
initProviderData();
return true;
}
private void initProviderData() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from " + DbOpenHelper.USER_TALBE_NAME);
mDb.execSQL("insert into book values(3,'Android');");
mDb.execSQL("insert into book values(4,'Ios');");
mDb.execSQL("insert into book values(5,'Html5');");
mDb.execSQL("insert into user values(1,'jake',1);");
mDb.execSQL("insert into user values(2,'jasmine',0);");
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public String getType(Uri uri) {
Log.d(TAG, "getType");
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.d(TAG, "insert");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, "delete");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.d(TAG, "update");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return row;
}
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TALBE_NAME;
break;
default:break;
}
return tableName;
}
}
Binder之外的手段
共享文件
使用文件共享来进行进程通信的方式为:在A进程中写入数据到文件中,然后在B进程中读取该文件的数据,这就完成了两个进程间的通信。而若是需要传递对象的话,可以将对象序列化存入文件当中,然后再由其他进程来反序列化读取该数据出来。
文件共享来传递数据对文件格式是没有要求的,可以是 xml,也可以是文本文件,只要协商好了同一的格式就可以了。由于 Android 的底层是 Linux,所以是不会在读写文件的时候加锁的,所以 Android 是有利于并发读写的。当然,这种方式只适合在对数据同步要求不高的进程之间使用。
虽说文件共享方式对于文件格式是没有要求的,但是却并不建议使用 SharedPreferences 来进行进程间通信。为什么呢?SP 的底层不是 xml 文件么?事实上,不建议的原因并不是因为文件格式问题,而是由于 SP 的读取缓存策略问题。SP 在读取文件的时候,不同进程会在内存中复制一份数据以便于快速读写,所以当使用多进程访问该文件的时候,就会很容易出现并发性导致的数据丢失问题,所以不建议使用 SP 做进程间通信。
Socket
Socket 是网络套接字,分为流式套接字和用户数据报套接字,分别对应 TCP 和 UDP 协议。TCP 提供稳定的双向通信,三次握手使得数据可以稳定传输;而 UDP 则是注重于效率,不能保证数据能够正确传输。
使用 Socket 借助 TCP 或者 UDP 在 Android 中开辟端口来提供数据和读取数据,一样也可以实现进程间的通信。
下面例子是实现一个客户端向服务端发送信息,然后服务端接收到后就随机回复一条信息的功能。下面给出服务端的实现例子
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefinedMessages = new String[] {
"你好啊,哈哈",
"请问你叫什么名字呀?",
"今天北京天气不错啊,shy",
"你知道吗?我可是可以和多个人同时聊天的哦",
"给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
private class TcpServer implements Runnable {
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
try {
// 接受客户端请求
final Socket client = serverSocket.accept();
System.out.println("accept");
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用于接收客户端消息,接收到客户端消息后就直接发送消息
BufferedReader in = new BufferedReader(new InputStreamReader(
client.getInputStream()));
// 用于向客户端发送消息
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())), true);
out.println("欢迎来到聊天室!");
while (!mIsServiceDestoryed) {
String str = in.readLine();
System.out.println("msg from client:" + str);
if (str == null) {
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg); //发送消息
System.out.println("send :" + msg);
}
System.out.println("client quit.");
// 关闭流
MyUtils.close(out);
MyUtils.close(in);
client.close();
}
}
然后我们来看一下客户端的实现例子
public class TCPClientActivity extends Activity implements OnClickListener {
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG: {
mMessageTextView.setText(mMessageTextView.getText()
+ (String) msg.obj);
break;
}
case MESSAGE_SOCKET_CONNECTED: {
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
private void connectTCPServer() {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = br.readLine();
System.out.println("receive :" + msg);
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg
+ "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
.sendToTarget();
}
}
System.out.println("quit...");
MyUtils.close(mPrintWriter);
MyUtils.close(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
由于 Socket 的跨进程通信是使用了设备的端口来连接通信的,所以它也可以实现跨设备的通信。而 Socket 的进程间通信方式开销太大了,在性能上不利于用于设备内部的进程通信,但是多用于与设备外部的进程通信。
不同方式优缺点
使用 Binder 连接池
前面我们在使用 AIDL 来进行通信的时候,是一个 service 实现一个AIDL接口的功能,那么当一个进程中的 AIDL 很多的时候,应该怎么办呢?我们总不能使用很多个 service 来进行服务,这样子 App 的开销就太大了。所以这里使用 Binder 连接池来作一个服务化架构,Binder 连接池对服务端线程的所有的 AIDL 进行统一管理和统一分配,在服务端提供一个 queryBinder 接口返回相应 Binder 对象给服务端。这可以实现各功能的解耦、方便客户端调用以及易于功能的更新迭代。
具体的实现来自于任玉刚老师的《Android开发艺术探索》,可以在此查看源码。其中有几点做如下记录
CountDownLatch
可以阻塞线程,使得线程之间的执行同步化。保证线程池中方法的正确执行顺序BinderPool
运行在客户端进程中,其内部静态类BinderPoolImpl
运行在服务端进程中,这点可以从各自调用以及静态内部类特性看出。- 客户端访问 Binder 连接池获取对应 IBinder 的时候需要在子线程中执行,因为前面做了线程同步化工作,其有可能耗时,服务端调用 Binder 是在 Binder 线程池的,也可能是耗时的。
- 可以在 Application 中初始化 BinderPool,以减少初次加载耗时,优化体验
- 即使 BinderPool 中有死亡连接机制,断联后仍需在客户端获取最新的 IBinder
Binder 底层原理和优势
前面我们讲到了需要使用到 Binder 来进行进程间通信,但是为什么 Binder 可以进程间通信呢?不知道这个问题大家是否有思考过,下面我们就来看看 Binder 的底层通信原理,从原理上面来看一下其被 Android 所选择的优势是什么。
首先,我们先来看一下在 Linux 系统中的进程通信原理,在 Linux 中将寻址空间分为用户空间以及内核空间。内核空间是系统内核运行的空间,用户空间是用户程序运行的空间,为保证内核的安全性和系统的安全性,内核空间和系统空间是隔离的。
但是由于用户空间必然会需要和内核空间进行交流,所以就有一套在用户空间和内核空间的信息交流方式。两个空间突破隔离的方式就是使用系统调用,这样子可以保证所有访问内核空间的方式都是在内核的控制之下进行的,可以保证访问的安全性。而这个过程中,如果进程陷入了内核代码中执行的时候,那么进程就属于内核态中,如果是在执行用户程序自己的代码的时候,那么就是处于用户态中。
而在 Linux 中,就是利用系统调用,将需要发送的数据放到内核空间开辟的一块内核缓存区中,然后接收数据的进程则将内核缓存区中的数据又放到自己的一块内存缓存区中。这样子就完成了一次进程间通信。需要注意到的是,这一次通信,需要使用到两次的复制。
然后我们来看一下 Android 中的 Binder 的运行原理。Binder 的通信使用的是 Binder 驱动,这个驱动不是 Linux 内核里面的,是一个动态可加载的内核,是在 Android 系统中独有的。而 Binder 驱动使用到的数据传递方法是内存映射,这个内存映射是通过 mmap() 实现的。
Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
所以说,由于内存映射的存在,数据的拷贝次数就是只需要一次就可以了,比共享内存性能稍微差一些,但是 Binder 的 C/S 架构让进程通信机制更加的稳定、使用更加方便;而且 Binder 的安全性也是更好的,其为每个 APP 都分配 UID,让进程鉴定机制更加的完备。
最后提一点需要注意的地方,建立通信服务端(Service)的时候,是需要在 ServiceManager 处注册的,然后注册时候告知需要返回的 Binder 是哪个存入到注册表中,最后客户端(Client)再通过 ServiceManager 获取到注册表中对应的 Binder。但是需要注意的是,Service 与 ServiceManager 的通信也是使用 Binder 来通信的,而这里面的 Binder 是系统自动创建好的。
参考
序列化和反序列化有什么作用?_java序列化和反序列化的作用_小官学长的博客-CSDN博客
关于Java中Serializable的一些问题_viclee108的博客-CSDN博客
教妹学Java(九):一文搞懂Java中的基本数据类型_沉默王二的博客-CSDN博客_java基本数据类型
Intent、Bundle传递数据的那些秘密_请叫我鲜鲜哥的博客-CSDN博客