参考《Android开发艺术探索》
一、 IPC简介
IPC含义为跨进程通信,通常不同的APP运行在不同的进程中,当两个app需要交互时,就要跨进程进行通信;或者一个app开了多个进程,大部分情况下也是需要跨进程通信的。
IPC有多种方式实现,有Bundle、文件共享、Messenger、AIDL、ContentProvider、Socket等。
首先是IPC的基础部分
二、IPC基础
1.开启多进程
在AndroidMenifest中指定activity的process属性,即可将这个这个activity放在单独的进程中。
<activity android:name=".ThirdActivity" android:process=":remote"/>
这个activity将运行在包名.remote
进程中。这个进程是当前应用的私有进程,其它应用不可以和它运行在同一个进程中。也可以直接指定一个字符串作为进程名。
多进程模式下进程运行在独立的虚拟机中,不同虚拟机分配有不同的内存地址,因此不同进程访问同一类会有多个副本,当一个进程改变了类的变量时对另一个进程并没有影响。因此
- 静态成员和单例会失效
- 线程同步机制失效
- Application存在多次创建现象
2.序列化和反序列化
- Serialable
Serialable
是Java中提供的序列化接口,一个类若要序列化只需要实现Serialable
接口即可。如:
public class User implements Serializable {
private String name;
private int id;
private boolean isMale;
public User(String name, int id, boolean isMale) {
this.name = name;
this.id = id;
this.isMale = isMale;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public boolean isMale() {
return isMale;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", isMale=" + isMale +
'}';
}
}
序列化时可以通过ObjectOutputStream#writeObject(Object)
方法将类实例写入通过流写入文件或者其它的地方,反序列化时通过ObjectInputStream#readObject()
方法从输入流中读取即可。
2. ParcelableParcelable
是Android提供的序列化和反序列化方法,使用相较于Serialable
稍显复杂但是效率较高。需要序列化的类要实现Parcelable
接口并实现序列化和反序列化的过程:
public class Book implements Parcelable {
private String bookName;
private int bookId;
public Book(String bookName, int bookId) {
this.bookName = bookName;
this.bookId = bookId;
}
private Book(Parcel in) {
bookName = in.readString ();
bookId = in.readInt ();
}
public static final Creator<Book> CREATOR = new Creator<Book> () {
@Override
public Book createFromParcel(Parcel in) {
return new Book (in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
public String getBookName() {
return bookName;
}
public int getBookId() {
return bookId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString (bookName);
dest.writeInt (bookId);
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", bookId=" + bookId +
'}';
}
}
通过writeToParcel(Parcel dest, int flags)
方法实现序列化过程,通过createFromParcel(Parcel in)
实现反序列化,通过describeContents()
返回内容描述,一般返回0。Parcelabe
可作为Intent
携带的数据在组件和进程间传递。
3. aidl
aidl是常用的跨进程通信方式,底层基于Binder,类似于客户端和服务端的架构,客户端根据自身需求规定接口,服务端则实现接口中的方法,并将结果通过Binder返回个客户端。其特殊之处在于接口定义需要定义为.aidl文件,使用到的自定义类需要实现Parcelabe
接口,即可序列化。下面是一个例子:
假设客户端是读者,服务端是图书馆,读者需要知道图书馆的所有藏书,并且也可以向其中添加书,因此客户端要定义一个接口IBookManager
,这个接口是一个aidl接口,新建IBookManager.aidl
文件如下:
// IBookManager.aidl
package com.github.xiaogegechen.ipctest;
import com.github.xiaogegechen.ipctest.Book;
import com.github.xiaogegechen.ipctest.IOnNewBookArrivedListener;
// Declare any non-default types here with import statements
interface IBookManager {
void addBook(in Book book);
List<Book> getBookList();
}
Book
类实现了Parcelabe
接口,并且新建Book.aidl
文件并声明,如下:
package com.github.xiaogegechen.ipctest;
parcelable Book;
接着需要服务端实现客户端的需求,将新建的Book.aidl
、Book.java
、IBookManager.aidl
文件拷贝到服务端app工程中,build工程,可以发现生成了一个新的类IBookManager.java
,如下:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: F:\\program\\Android\\IPCTestClient\\app\\src\\main\\aidl\\com\\github\\xiaogegechen\\ipctest\\IBookManager.aidl
*/
package com.github.xiaogegechen.ipctest;
// Declare any non-default types here with import statements
// android.os.IInterface接口中定义了asBinder()方法,
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
* 服务端的binder
*/
public static abstract class Stub extends android.os.Binder implements com.github.xiaogegechen.ipctest.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.github.xiaogegechen.ipctest.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface (this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.github.xiaogegechen.ipctest.IBookManager interface,
* generating a proxy if needed.
* 将服务端的binder对象封装成客户端需要的binder对象
*/
public static com.github.xiaogegechen.ipctest.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
if (((iin != null) && (iin instanceof com.github.xiaogegechen.ipctest.IBookManager))) {
return ((com.github.xiaogegechen.ipctest.IBookManager) iin);
}
return new com.github.xiaogegechen.ipctest.IBookManager.Stub.Proxy (obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
/**
* 从data中取出参数,从code中取出方法标志,从而调用服务端相应的方法,并将结果写进reply中
*/
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString (descriptor);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface (descriptor);
com.github.xiaogegechen.ipctest.Book _arg0;
if ((0 != data.readInt ())) {
_arg0 = com.github.xiaogegechen.ipctest.Book.CREATOR.createFromParcel (data);
} else {
_arg0 = null;
}
this.addBook (_arg0);
reply.writeNoException ();
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface (descriptor);
java.util.List<com.github.xiaogegechen.ipctest.Book> _result = this.getBookList ();
reply.writeNoException ();
reply.writeTypedList (_result);
return true;
}
default: {
return super.onTransact (code, data, reply, flags);
}
}
}
/**
* 客户端的binder
*/
private static class Proxy implements com.github.xiaogegechen.ipctest.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
// 将客户端的binder对象封装成服务端的binder对象
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
// 客户端会调用这个方法,此方法将调用服务端的transact()方法,transact()方法调用onTransact()方法,onTransact()方法
// 内部会调用服务端的addBook()方法,从而完成从客户端到服务端的调用
@Override
public void addBook(com.github.xiaogegechen.ipctest.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain ();
android.os.Parcel _reply = android.os.Parcel.obtain ();
try {
_data.writeInterfaceToken (DESCRIPTOR);
if ((book != null)) {
_data.writeInt (1);
book.writeToParcel (_data, 0);
} else {
_data.writeInt (0);
}
// 调用服务端的addBook方法,参数在_data中,结果在_reply中,只是这里没有返回值
mRemote.transact (Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException ();
} finally {
_reply.recycle ();
_data.recycle ();
}
}
// 和addBook()方法类似
@Override
public java.util.List<com.github.xiaogegechen.ipctest.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain ();
android.os.Parcel _reply = android.os.Parcel.obtain ();
java.util.List<com.github.xiaogegechen.ipctest.Book> _result;
try {
_data.writeInterfaceToken (DESCRIPTOR);
// 调用服务端的addBook方法,参数在_data中,结果在_reply中
mRemote.transact (Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException ();
_result = _reply.createTypedArrayList (com.github.xiaogegechen.ipctest.Book.CREATOR);
} finally {
_reply.recycle ();
_data.recycle ();
}
return _result;
}
}
// 方法标记符号,用于区分接口方法
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void addBook(com.github.xiaogegechen.ipctest.Book book) throws android.os.RemoteException;
public java.util.List<com.github.xiaogegechen.ipctest.Book> getBookList() throws android.os.RemoteException;
}
服务端需要继承IBookManager.Stub
类并实现客户端规定的接口中的方法,然后将这个对象binder对象放回个客户端,而客户端需要调用IBookManager.Stub.asInterface()
方法将这个binder封装成客户端需要的接口,从而实现在客户端调用服务端的服务。服务端:
public class MyService extends Service {
private static final String TAG = "MyService";
private CopyOnWriteArrayList<Book> mBooks;
private IBookManager.Stub mStub = new IBookManager.Stub () {
@Override
public void addBook(Book book) throws RemoteException {
mBooks.add (book);
}
@Override
public List<Book> getBookList() throws RemoteException {
return mBooks;
}
};
@Override
public void onCreate() {
super.onCreate ();
mBooks = new CopyOnWriteArrayList<> ();
mBooks.add (new Book ("Book1", 1));
mBooks.add (new Book ("Book2", 2));
}
@Override
public IBinder onBind(Intent intent) {
return mStub;
}
@Override
public void onDestroy() {
super.onDestroy ();
}
}
客户端:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTextView;
private IBookManager mBookManager;
private ServiceConnection mConnection = new ServiceConnection () {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = IBookManager.Stub.asInterface (service);
try {
mBookManager.registerListener (mOnNewBookArrivedListener);
List<Book> bookList = mBookManager.getBookList ();
Log.d (TAG, "onServiceConnected: " + bookList);
mBookManager.addBook (new Book ("Book3", 3));
List<Book> newBookList = mBookManager.getBookList ();
Log.d (TAG, "onServiceConnected: " + newBookList);
} catch (RemoteException e) {
e.printStackTrace ();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
mTextView = findViewById (R.id.text_view);
Intent intent = new Intent ();
intent.setAction ("com.github.xiaogegechen.ipctest.MyService");
intent.setPackage ("com.github.xiaogegechen.ipctest");
bindService (intent, mConnection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy ();
unbindService (mConnection);
}
}
三、IPC方式
1. Bundle
Bundle
实现了Parcelable
接口,所以支持在四大组件中通过Intent
携带Bundle
数据从而实现进程间通信,使用比较简单。
2. 文件共享
通过共享文件实现进程通信,也比较简单,但是要注意并发写的问题
3. 使用aidl
上述已经介绍过。
4. 使用ContentProvider
Android四大组件之一,专门为进程间共享数据服务。
5. 使用Socket
socket工作原理
很显然,socket也是可以完成进程间通信的。
服务端:
public class SocketService extends Service {
private static final String TAG = "SocketService";
private boolean mIsServiceDestroyed = false;
private static final String[] CHAT_CONTENT = new String[]{
"你好啊!哈哈",
"请问你叫什么名字啊",
"今天沈阳天气不错啊,shy",
"你知道吗?我只和你聊天的哦!",
"给你讲个笑话吧,你比我帅,哈哈!"
};
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate ();
new Thread (new Runnable (){
@Override
public void run() {
ServerSocket serverSocket = null;
try {
// 监听5000端口
serverSocket = new ServerSocket (5000);
} catch (IOException e) {
Log.e (TAG, "open socket failed");
e.printStackTrace ();
return;
}
Log.d (TAG, "open socket successfully");
while(!mIsServiceDestroyed){
try {
// 等待有客户端连接进来,当没有客户端连接时会阻塞
final Socket socket = serverSocket.accept ();
Log.d (TAG, "a client connect, it is: " + socket);
replyClient (socket);
} catch (IOException e) {
e.printStackTrace ();
}
}
}
}).start ();
}
private void replyClient(Socket socket) throws IOException{
BufferedReader reader = new BufferedReader (new InputStreamReader (socket.getInputStream ()));
PrintWriter writer = new PrintWriter (new BufferedWriter (new OutputStreamWriter (socket.getOutputStream ())));
while(!mIsServiceDestroyed){
// 读取客户端发来的信息,以"\n"为参考,当客户端没有发送信息时会阻塞
// 读到输入流的尾部时返回null
String line = reader.readLine ();
Log.d (TAG, "receive message from client, the message is: " + line);
if(line == null){
// 客户端断开链接,结束
break;
}
// 随机进行回复
int idx = new Random ().nextInt (CHAT_CONTENT.length);
String reply = CHAT_CONTENT[idx];
writer.println (reply);
writer.flush ();
}
Log.d (TAG, "client quit!");
// 断开读取客户端的连接
reader.close ();
// 断开向客户端发消息的连接
writer.close ();
// 关闭socket
socket.close ();
}
@Override
public void onDestroy() {
mIsServiceDestroyed = true;
super.onDestroy ();
}
}
客户端:
public class SocketActivity extends AppCompatActivity {
private static final String TAG = "SocketActivity";
private static final int CONNECT_OK = 0;
private static final int NEW_MESSAGE_ARRIVED = 1;
private static final int SEND_MESSAGE = 2;
private Button mButton;
private TextView mTextView;
private EditText mEditText;
private PrintWriter mPrintWriter;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler (){
@Override
public void handleMessage(Message msg) {
super.handleMessage (msg);
switch (msg.what){
case CONNECT_OK:
mButton.setEnabled (true);
break;
case NEW_MESSAGE_ARRIVED:
mTextView.append ("from server:\n");
String reply = (String) msg.obj;
mTextView.append (reply);
mTextView.append ("\n");
break;
case SEND_MESSAGE:
String str = (String) msg.obj;
mTextView.append (str);
break;
default:
break;
}
}
};
private Socket mSocket;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_socket);
mButton = findViewById (R.id.send);
mTextView = findViewById (R.id.text_chat);
mEditText = findViewById (R.id.input);
mButton.setEnabled (false);
mButton.setOnClickListener (new View.OnClickListener () {
@Override
public void onClick(View v) {
final String str = mEditText.getText ().toString ();
mTextView.append ("from client:\n" + str + "\n");
new Thread (new Runnable (){
@Override
public void run() {
mPrintWriter.println (str);
mPrintWriter.flush ();
}
}).start ();
}
});
// 开启远程服务
Intent intent = new Intent ();
intent.setAction ("com.github.xiaogegechen.ipctest.SocketService");
intent.setPackage ("com.github.xiaogegechen.ipctest");
ComponentName componentName = startService (intent);
Log.d (TAG, "startService, componentName is: " + componentName);
new Thread (new Runnable (){
@Override
public void run() {
Socket socket = null;
boolean success = false;
while(!success){
try {
// 建立链接,三次握手会在这里完成
socket = new Socket ("localhost", 5000);
mSocket = socket;
// 建立向服务端发送数据的连接
mPrintWriter = new PrintWriter (new BufferedWriter (new OutputStreamWriter (mSocket.getOutputStream ())));
mPrintWriter.println ("hello");
mPrintWriter.flush ();
success = true;
mHandler.sendEmptyMessage (CONNECT_OK);
} catch (IOException e) {
e.printStackTrace ();
Log.e (TAG, "connect tcp server failed! retry ...");
// 暂停一秒继续重试
SystemClock.sleep (1000);
}
}
Log.d (TAG, "connection ok!");
try {
// 链接成功,开始通信
BufferedReader reader = new BufferedReader (new InputStreamReader (socket.getInputStream ()));
while(!SocketActivity.this.isFinishing ()){
// 读取服务端传回的信息
String reply = reader.readLine ();
Log.d (TAG, "receive: " + reply);
if(!TextUtils.isEmpty (reply)){
Message message = mHandler.obtainMessage ();
message.what = NEW_MESSAGE_ARRIVED;
message.obj = reply;
message.sendToTarget ();
}
}
Log.d (TAG, "quit...");
// 关闭向服务端发送数据的连接
mPrintWriter.close ();
// 关闭从服务端读数据的链接
reader.close ();
// 关闭socket
socket.close ();
} catch (IOException e) {
e.printStackTrace ();
}
}
}).start ();
}
@Override
protected void onDestroy() {
try {
if (mSocket != null) {
mSocket.shutdownInput ();
mSocket.close ();
}
} catch (IOException e) {
e.printStackTrace ();
}
super.onDestroy ();
}
}
四、Binder连接池
当客户端有大量的服务需要服务端完成时,如果是普通的aidl方法就需要创建镀铬服务,非常不方便管理,这时可以使用binder连接池。这时服务端仅仅需要开启一个服务即可,当客户端需要服务的时候,通过一个queryCode向该服务发送请求,拿到自己需要的Binder,利用这个binder拿到结果。
需要在客户端添加一个aidl接口,接收一个queryCode并返回binder,服务端开启服务并实现这个接口,根据不同的queryCode返回不同的binder,如下:
// IBinderPool.aidl
package com.github.xiaogegechen.ipctest;
import android.os.IBinder;
interface IBinderPool {
// 根据queryCode返回相应的binder
IBinder queryBinder(int queryCode);
}
服务端:
// BinderPoolService.java
public class BinderPoolService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new BinderPoolImpl ();
}
}
// BinderPoolImpl.java
public class BinderPoolImpl extends IBinderPool.Stub{
@Override
public IBinder queryBinder(int queryCode) throws RemoteException {
IBinder binder = null;
switch(queryCode){
case QueryCode.CODE_ICOMPUTE:
binder = new CopmuteImpl ();
break;
case QueryCode.CODE_ISECURIT_CENTER:
binder = new SecurityCenterImpl ();
break;
default:
break;
}
return binder;
}
}
当客户端有IPC需求的时候,就需要开启这个服务,将返回的binder
封装成客户端定义的IBinderPool
,通过向IBinderPool
提供queryCode
拿到对应的binder
,在将此binder
封装成对应的客户端接口即可使用:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ServiceConnection mConnection = new ServiceConnection () {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 封装成客户端需要的binderPool
IBinderPool binderPool = IBinderPool.Stub.asInterface (service);
try {
// 拿到服务端ICompute的IBinder
IBinder computeBinder = binderPool.queryBinder (QueryCode.CODE_ICOMPUTE);
// 封装成客户端需要的ICompute
ICompute compute = ICompute.Stub.asInterface (computeBinder);
// 计算
int result = compute.add (10, 20);
Log.d (TAG, "result is: " + result);
IBinder securityBinder = binderPool.queryBinder (QueryCode.CODE_ISECURIT_CENTER);
ISecurityCenter securityCenter = ISecurityCenter.Stub.asInterface (securityBinder);
String decode = securityCenter.decode ("hello world");
Log.d (TAG, decode);
} catch (RemoteException e) {
e.printStackTrace ();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
Intent intent = new Intent ();
intent.setPackage ("com.github.xiaogegechen.ipctest");
intent.setAction ("com.github.xiaogegechen.ipctest.binder_pool.BinderPoolService");
bindService (intent, mConnection, BIND_AUTO_CREATE);
}
}
binder连接池避免了service的重复创建,使用非常方便,因此应该尽量在项目中使用binder连接池管理IPC请求。