Android读书笔记(2)—— IPC机制
一、IPC与多线程
1、IPC简介
线程:① CPU最小的调度单元 ② 一种有限的系统资源
进程:一个执行单元。一般指一个程序或一个应用。一个进程可以包含多个线程。
IPC:进程间通信。
多线程的情况
1)因为某些原因自身需要采用多线程模式来实现。比如:某些模块需要运行在单独进程;为了加大一个应用可以使用的内存。
2)需要从其他应用获取数据。
2、Android中的多进程模式
2.1、开启多进程模式
在Android中一个应用开启多进程唯一办法:给四大组件在AndroidMenifest.xml中指定android:process
属性。
默认进程名是包名。“:remote”是一种省略写法,完整名为“com.example.xiang.myapplication:remote”进程名,以“:”开头,属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
2.2、多进程模式的运行机制
Android系统为每一个进程分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间。将导致存在如下问题:
1)静态成员和单例完全失效
2)线程同步机制完全失效
3)SharedPreferences的可靠性下降
4)Application会多次创建
不同的进程拥有独立的虚拟机、Application和内存空间,导致通过内存来共享数据,都会共享失败。
Android的IPC方式
1)Intent
2)文件共享方式
3)Binder(AIDL和Messenger)
4)ContentProvider
5)Socket
二、IPC的基础概念
为什么要序列化?
1)永久性保存对象的字节序列到本地文件中
2)通过序列化在网络中传递对象
3)通过序列化在进程间传递对象
1、Serializable接口
Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。
private static final long serialVersionUID = 8154678445665565611L;
serialVersionUID是用来辅助序列化和序列化的过程,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。一般我们应该手动指定serialVersionUID的值,比如1L(或者根据类结构生成hash值)。若不指定,反序列化时当前类有所改变(比如增加或者删除了成员变量),那么系统会重新计算当前类的hash值并赋给serialVersionUID,导致serialVersionUID不一致,于是反序列化失败,程序就会crash。
静态成员变量属于类不属于对象,不会参加序列化
用transient标记的成员变量不会参与序列化
2、Parcelable接口
public class User implements Parcelable { private int userId; private String userName; private Book book; protected User(Parcel in) { userId = in.readInt(); userName = in.readString(); //book是一个可序列化对象,需要传递当前线程的上下文类加载器 book = in.readParcelable(Thread.currentThread().getContextClassLoader()); } /** * 实现反序列化 */ public static final Creator CREATOR = new Creator() { @Override public User createFromParcel(Parcel in) { return new User(in); } @Override public User[] newArray(int size) { return new User[size]; } }; /** * 几乎所有情况都返回0 * @return */ @Override public int describeContents() { return 0; } /** * 实现序列化 * * @param parcel * @param i */ @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(userId); parcel.writeString(userName); parcel.writeParcelable(book, i); }}
3、Binder
3.1、AIDL接口的创建
Binder就是Android中实现了IBinder接口的一个类,是跨进程通信的媒介。在Android开发中,Binder主要用在Service中,包括Messenger(底层其实是AIDL)和AIDL。
//Book.javapackage com.example.xiang.myapplication;import android.os.Parcel;import android.os.Parcelable;public class Book implements Parcelable { private int bookId; private String bookName; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } /** * 几乎所有情况都返回0 * @return */ @Override public int describeContents() { return 0; } /** * 实现序列化 * @param parcel * @param i */ @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(bookId); parcel.writeString(bookName); } /** * 实现反序列化 */ public static final Creator CREATOR = new Creator() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; public Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); }}//Book.aidlpackage com.example.xiang.myapplication;parcelable Book;// IBookManager.aidlpackage com.example.xiang.myapplication;import com.example.xiang.myapplication.Book;//必须导入,否则报错interface IBookManager { List getBookList(); void addBook(in Book book);}
Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是定义的一个接口,虽然Book类和IBookManager在同一个包中,但是还是要显示导入Book类。目录结构如下:
AIDL目录结构(新建Book.aidl时候,直接填Book为名字时候会报错,只有先创建完之后再RENAME才不会报错)
3.2、Binder类分析
系统为IBookManager.aidl自动生成的Binder类
package com.example.xiang.myapplication;public interface IBookManager extends android.os.IInterface { public static abstract class Stub extends Binder implements IBookManager { //Binder的唯一标识 private static final String DESCRIPTOR = "com.example.xiang.myapplication.IBookManager"; public Stub() { this.attachInterface(this, DESCRIPTOR); } public static IBookManager asInterface(IBinder obj) { if ((obj == null)) { return null; } IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof IBookManager))) { return ((IBookManager) iin); } return new IBookManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); List _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); Book _arg0; if ((0 != data.readInt())) { _arg0 = Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements IBookManager { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } @Override public IBinder asBinder() { return mRemote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public List getBookList() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); List _result; try { _data.writeInterfaceToken(DESCRIPTOR); //_data写入参数 //发起远程调用,当前线程挂起 mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(Book book) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1); } public List getBookList() throws RemoteException; public void addBook(Book book) throws RemoteException;}
-
asInterface
//将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,如果C/S位于同一进程,此方法返回就是服务端的Stub对象本身,否则返回的就是系统封装后的Stub.proxy对象 -
asBinder
//返回当前的Binder对象 -
onTransact
//运行在服务端 -
Proxy#getBookList
//运行在客户端,内部实现过程如下:首先创建该方法所需要的输入型对象Parcel对象_data,输出型Parcel对象_reply和返回值对象List。然后把该方法的参数信息写入_data( 如果有参数);接着调用transact方法发起RPC( 远程过程调用),同时当前线程挂起(因此不能再UI线程中发起远程请求);然后服务端的onTransact方法会被调用(服务端的Binder方法运行在线程池,所以需要采用同步方式实现),直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。
AIDL的本质:系统提供的一个快速实现Binder的工具而已。
3.3、远程服务端Service的实现
public class BookManagerService extends Service { private static final String TAG = "BookManagerService"; //CopyOnWriteArrayList支持并发读/写 private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>(); private Binder mBinder = new IBookManager.Stub() { @Override public List getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android开发艺术探索")); mBookList.add(new Book(2, "Android进阶之光")); } @Override public IBinder onBind(Intent intent) { return mBinder; }}//注册Service
AIDL方法(getBookList和addBook)是运行在Binder线程池中的,所以需要处理线程同步,这里采用CopyOnWriteArrayList来进行自动的线程同步。
3.4、客户端的实现
public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity"; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); try { List list = bookManager.getBookList(); Log.d(TAG, "查询图书列表:" + list.toString()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(connection); super.onDestroy(); }}
服务端的方法可能需要很久才能执行完毕,上面这样写的目的是为了更好的了解AIDL的实现步骤。
对象是不能跨进程直接传输的,对象跨进程传输的本质是反序列化过程,这也是为什么AIDL中定义的对象必须实现Parcelable接口。
4、Binder的两个重要方法
linkToDeath(DeathRecipient recipient, int flags)
//设置Binder的死亡代理。当Binder死亡时,系统会回调DeathRecipient的binderDied()
方法,所以需要在此方法中移除之前绑定的binder代理(调用unlinkToDeath
)并重新绑定远程服务
unlinkToDeath(DeathRecipient recipient, int flags)
//移除死亡代理
isBinderAlive()
//判断Binder是否死亡
DeathRecipient是IBinder的一个内部接口,里面只有一个方法binderDied()
5、补充说明
AIDL文件支持的数据类型
1)基本数据类型(int、long、char、boolean等)
2)String和CharSequence
3)List:只支持ArrayList,里面的每个元素必须被AIDL所支持
4)Map:只支持HashMap,里面的每个元素必须被AIDL所支持,包括key和value
5)Parcelable:所有实现了Parcelable接口的对象
6)AIDL:所有AIDL接口本身也可以在AIDL文件中使用
- 自定义的Parcelable对象(如上例中的Book类),必须新建一个和它同名的AIDL文件(Book.aidl),并添加相应的内容。
- 自定义Parcelable对象和AIDL对象必须要显式import进来。
- 除了基本数据类型的其他类型参数,都需要标上方向:in、out或者inout。
- AIDL接口中只支持方法,不支持声明静态常量。
- 建议把所有和AIDL相关的类和文件全部放在同一个包中,好处是,若客户端在另一应用(模块),复制整个包即可。
- AIDL的包结构在服务端和客户端必须保持一致,否则运行出错。
DeathRecipient是IBinder的一个内部接口,里面只有一个方法binderDied()
三、Android中的IPC方式
1、使用Intent
启动另一个进程的Activity、Service和Receiver的时候,在Bundle(实现了Parcelable接口)中附加信息,并通过Intent进行传递
2、使用文件共享
序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象,适合对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写问题。
SharedPreferences底层实现采用XML文件来存储键值对。系统对它的读/写有一定的缓存策略,即在内存中会有一份 SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写变得不可靠,面对高并发读/写时SharedPreferences 有很大几率丢失数据,因此不建议在IPC中使用SharedPreferences。
3、使用Messenger(信使)
一种轻量级的IPC方案,底层实现是AIDL。服务端以串行的方式处理客户端发来的Message对象。
举个例子
//MessengerService.java//服务端代码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 Constants.MSG_FROM_CLIENT: //收到客户端的消息 Log.d(TAG, "收到客户端发来的信息:" + msg.getData().getString("msg")); Messenger client = msg.replyTo; Message replyMessage = Message.obtain(null, Constants.MSG_FROM_SERVER); Bundle bundle = new Bundle(); bundle.putString("reply","你的消息我已经收到,稍后回复你。"); try { client.send(replyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }}//注册Service //MainActivity.java//客户端实现public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Messenger mService; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mService = new Messenger(iBinder); Message msg = Message.obtain(null, Constants.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(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler()); private class MessengerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case Constants.MSG_FROM_SERVER: Log.d(TAG, "收到服务端的回复" + msg.getData().getString("reply")); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Intent intent = new Intent(this, MessengerService.class); findViewById(R.id.btn_bind).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { bindService(intent, connection, Context.BIND_AUTO_CREATE); } }); } @Override protected void onDestroy() { unbindService(connection); super.onDestroy(); }}
Messenger中传递的数据必须将数据放入Message,Message和Messenger都实现了Parcelable接口(放入Message中的对象也要实现Parcelable接口才能传递)。工作原理如图所示:
[站外图片上传中...(image-7382db-1516020836113)]
Messenger常用方法
Messenger(Handler target)
Messenger(IBinder target)
send(Message message)
getBinder() IBinder
Message相关属性
replyTo
//返回一个可以答复的Messenger
4、使用AIDL
见上面的例子
5、使用ContentProvider
6、使用Socket
四、Binder连接池
五、IPC总结
更多相关文章
- Android之Http网络编程(二)
- 【Android(安卓)Training - 09】高效地显示Bitmap图片 [ Lesson
- Android(安卓)SDK Document 框架导读的翻译和注解[7]——Intents
- Android(安卓)面试题目总结【持续更新...】
- Android获取三轴加速度和view的重绘
- Android登录界面设计
- 每天学习一个Android中的常用框架——2.greenDao
- Android(安卓)SDK 2.1 - Dev Guide - Best Practives - Designin
- Java->Android并发编程引气入门篇