Android IPC机制
一、IPC简介
IPC是指两个进程之间进行数据交互的过程,即:跨进程通信。
进程是一个执行单,在移动设备上指一个程序或者一个应用。一个进程可以有多个线程,也可以只有一个线程,即主线程。在Android里边,主线程也叫作UI线程,要是在主线程执行大量耗时任务,就会造成界面无法响应,ANR问题,解决这类问题,把耗时操作放在子线程就好。
在Android中,最有特色的进程间通信就是Binder,Binder轻松的实现了进程间的通信。
二、Android中的多进程模式
1.开启多进程模式
给四大组件Activity、Service、Receiver、ContentProvider在AndroidMenifeist中指定android:process属性,可以指定其运行的进程。
:开头的线程是当前应用的私有进程,其它应用不可以和它跑在同一个进程中,而不以:开头的属于全局进程,其他应用通过ShareUID方式可以和它跑在一个进程中。
2.多进程模式的运行机制
Android为了每一个应用(进程)都分配了独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间。
多进程会造成如下几个反面方面的问题:
- 静态模式和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性下降
- Application会被多次创建
为了解决这些问题,系统提供了跨进程通信方法,虽然不能直接共享内存,但是可以实现数据共享。Intent来传递数据,共享文件,基于Binder的Messenger,ContentProvider,AIDL和Socket。
三、IPC技术概念介绍
当我们需要通过Intent和Binder传输数据,或者我们需要把对象持久化到存储设备上,再或者通过网络传输给其它客户端时,Serializable和Parcelable接口可以完成对象的序列化过程。
1.Serialzable接口
Serialzable是java提供的序列化接口,是一个空接口,为对象同序列化和反序列化操作。
想让一个类对象实现序列化,只需要这个类实现Serialzable接口,并声明一个serialVersionUID即可,serialVersionUID可以声明成1L或者IDE根据当前类接口自动生成它的hash值。
没有serialVersionUID不影响序列化,但是可能会影响反序列化。序列化时,系统当前类的serialVersionUID写入序列化文件中,当反序列化时,回去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果不一致,无法完成反序列化。
2.Parcelable接口
class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
//从序列化后的对象中创建原始数据
protected User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
}
//内容描述,基本都是0
@Override
public int describeContents() {
return 0;
}
//序列化方法
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
}
//反序列方法
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
Seriallizable用起来简单但是开销大,序列化和反序列过程需要大量的I/O操作,而Parcelable是Android序列化方式,更适合Android平台,效率更高。Parcelable主要用于内存序列化上,而Seriallizable更适用于序列化到本地存储设备,或者将对象序列化后通过网络传输到别的客户端。
四、Android中的IPC方式
1.使用Intent传递Bundle
Activity、Service、Receiver都支持在 Intent中传递Bundle数据,Bundle实现了Pareclable接口,所以它可以方便地在不同进程间传输。
2.使用文件共享
Android基于Linux,使得其并发读写文件可以没有限制的进行,两个进程可以通过读写一个文件来交换数据。共享数据对文件格式没有要求,双反约定就行。使用文件共享很有可能出问题。
SharedPreferences是个特例,虽然也是属于文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对他的读写变得不可靠,高并发的时候,很大可能会丢失数据。
3.使用Messenger
Messenger可以在不同的进程中传递Message对象,在Message中存入我们需要传递的数据,就可以实现数据的跨进程传递。它是一种轻量级的IPC方案,底层实现是AIDL。
Messenger对AIDL做了封装,使得我们可以更便捷的实现跨进程通信,它一次只处理一个请求,在服务端不用考虑线程同步问题,在服务端不存在并发执行的情形。实现一个Messenger有如下几个步骤:
服务端进程
在服务端创建一个Service,同时创建一个Handler,并通过它来创建一个Messenger对象,然后再Service的onBind中返回这个Messenger对象底层Binder即可。
客户端进程
绑定服务端Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger。通过这个对象就可以向服务端发消息了。如果需要服务端回应客户端,就需要和服务端一样,创建一个Handler,并通过它来创建一个Messenger对象,然后把这个Messenger对象通过Message的replyTo参数传给服务端,服务端可以通过这个replyTo参数回应客户端。
image.png
4.使用AIDL
服务端
首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现AIDL接口即可。
客户端
绑定服务端的Service,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可可以范文AIDL里边的方法了。
创建AIDL
在AIDL文件中,并不是所有的额数据类型都是可以使用的。
- 基本数据类型(int、long、char、boolean、double等)
- String和CharSequence
- List:只支持 ArrayList,里边每一个元素都需要被AIDL支持。
- Map:只支持HshMap,里边的每个元素都必须被AIDL支持,包括key和value
- Parcelable:实现了Parcelable接口的对象。
- AIDL:所有的AIDL接口本省也可以在AIDL文件中使用。
以上6种数据就是AIDL所支持的所有类型,其中自定义的Parecelable对象和AIDL对象必须显示的import,不管是否和当前的AIDL文件位于同一个包。
AIDL文件中用到了自定义的Parcelable对象,必须新建一个同名的AIDL文件,在其中声明它为parcelable类型。
AIDL中除了基础数据类型,其它类型参数都需要标上方向:in、out、inout,in是输入型参数,out是输出型参数,inout是输入输出型参数。
服务端实现
public class BookService extends Service {
private static final CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
public BookService() {
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "android"));
mBookList.add(new Book(2, "ios"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new BookBinder();
}
public static class BookBinder extends IBookManager.Stub {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
}
}
上面是远程服务端示例,AIDL方法在服务端的Binder线程池中执行,因此各个客户端同时连接的时候,会存在多个线程同时访问的情形,所以要在AIDL中处理线程同步,这个CopyOnWriteArrayList支持并发的读写。
AIDL所支持的是一个抽象的List,只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,当时Binder会按照List规范去范文数据并最终形成一个ArrayList传递给客户端。
5.客户端实现
class BookActivity : AppCompatActivity() {
var mIBookManager: IBookManager? = null
var mConn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
service?.let {
mIBookManager = IBookManager.Stub.asInterface(it)
val bookList = mIBookManager?.bookList
Toast.makeText(this@BookActivity, "书籍${bookList?.size ?: 0}", Toast.LENGTH_LONG)
.show()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
TODO("Not yet implemented")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book)
val intent = Intent(this, BookService::class.java)
bindService(intent, mConn, BIND_AUTO_CREATE)
}
override fun onDestroy() {
unbindService(mConn)
super.onDestroy()
}
}
ServiceConnection 的回调方法在UI线程中运行,服务端的方法有可能很久才能执行完毕,需要考虑ANR的问题。
服务的方法本省就运行再Binder线程池中,本身可以执行大量耗时操作,不要去服务端方法中开县城去进行异步任务。
服务端通知客户端示例
客户端
class BookActivity : AppCompatActivity() {
var mIBookManager: IBookManager? = null
var mConn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
service?.let { it ->
mIBookManager = IBookManager.Stub.asInterface(it)
val bookList = mIBookManager?.bookList
Toast.makeText(this@BookActivity, "书籍${bookList?.size ?: 0}", Toast.LENGTH_LONG)
.show()
mIBookManager?.let {
it.registerListener(mArrivedListener)
}
}
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
var mArrivedListener = object : IOnNewBookArrivedListener.Stub() {
override fun onNewBookArried(book: Book?) {
mHandler.obtainMessage(1, book).sendToTarget()
}
}
var mHandler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> Toast.makeText(this@BookActivity, msg.obj.toString(), Toast.LENGTH_LONG).show()
else -> super.handleMessage(msg)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book)
val intent = Intent(this, BookService::class.java)
bindService(intent, mConn, BIND_AUTO_CREATE)
}
override fun onDestroy() {
mIBookManager?.let {
if (it.asBinder().isBinderAlive) {
it.unRegisterListener(mArrivedListener)
}
}
unbindService(mConn)
super.onDestroy()
}
}
服务端
public class BookService extends Service {
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
private static final CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private static final RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =
new RemoteCallbackList<>();
public BookService() {
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "android"));
mBookList.add(new Book(2, "ios"));
new Thread(new ServiceWorker()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new BookBinder();
}
private void onNewArrived(Book book) throws RemoteException {
mBookList.add(book);
for (int i = 0; i < mListenerList.beginBroadcast(); i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
listener.onNewBookArried(book);
}
mListenerList.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
onNewArrived(new Book(mBookList.size() + 1, "a"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onDestroy() {
mIsServiceDestroyed.set(true);
super.onDestroy();
}
public static class BookBinder extends IBookManager.Stub {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
}
}
RemoteCallbackList是系统提供专门用于删除跨进程listener的,它的内部有一个Map结构,用来保存所有的AIDL回调,这个Map的key就是Binder类型,value是CallBack类型。
客户端解注册的时候,我们只需要遍历服务端所有的listener,找出那个和接注册listener具有相同的Binder对象的服务端listener并把它删除即可。
RemoteCallbackList的beginBroadcast和finishBroadcast必须配对使用。
5.使用ContentProvider
ContentProvider是Android专门提供不同应用间进行数据共享的方式。底层实现一样是Binder。
系统预置了许多ContentProvider,比如通讯录,日程信息表,只需要通过ContentResolver的query、update、insert、delete方法即可。
6.使用Socket
六、选用合适IPC方式
image.png