二、IPC机制续(IPC方式)

时间:2022-08-23 16:14:39

IPC机制

具体方式有很多,比如可以在Intent中附加Extra来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,Content Provider天生就是支持跨进程访问的,因此,我们也可以使用它来进行IPC,另外通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。

1.使用Bundle

由于Bundle实现了Parcelable接口,所以它可以方便地在不同进程间传输。

除了直接传递数据这种典型的使用场景,他还有一种特殊的使用场景,如A进程正在进行计算,计算完成之后需要把结果传递给B进程,但是这个结果不支持放入Bundle中,那么可以这样考虑,A中,通过Intent启动B进程的一个Service组件(如IntentService),让Service进行后台计算,计算完毕之后,再启动B进程中真正想要启动的组件由于Service也在B进程中,所以目标组件就可以直接获取结果。

        findViewById(R.id. button).setOnClickListener( new OnClickListener() {

@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity. this, SecondActivity.class);
User user = new User(0, "jake", true);
user. book = new Book();
intent.putExtra( "extra_user", (Serializable) user);
startActivity( intent);
}
});

2.使用文件共享

两个进程通过读写同一个文化夹来交换数据,比如A进程把数据写进文件,B进程通过读取这个文件来获取数据。Linux使得并发读写文件可以没有限制,甚至两个线程同时对一个文件进行读写都是运行的。

希望在ManActivity中的onResume中序列化一个User对象到SDk卡上面的一个文件里面,在SecondActivity的onResume中去反序列化。

MainActivity:onResume执行下面的方法
private void persistToFile() {
new Thread( new Runnable() {
@Override
public void run() {
User user = new User(1, "hello world", false);
File dir = new File(MyConstants. CHAPTER_2_PATH);
if (! dir.exists()) {
dir.mkdirs();
}
File cachedFile = new File(MyConstants. CACHE_FILE_PATH );
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(
new FileOutputStream(cachedFile));
objectOutputStream.writeObject( user);
Log. d(TAG, "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
MyUtils. close(objectOutputStream);
}
}
}).start();
}
SecondActivity中取:
private void recoverFromFile() {
new Thread(new Runnable() {

@Override
public void run() {
User user = null;
File cachedFile = new File(MyConstants. CACHE_FILE_PATH);
if ( cachedFile.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(
new FileInputStream( cachedFile));
user = (User) objectInputStream.readObject();
Log. d(TAG, "recover user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
MyUtils. close(objectInputStream);
}
}
}
}).start();
}

当然这种不支持并发,如果想要并发,需要使用线程同步机制来解决。SharePreferences是个特例,通过键值对来存储数据,底层采用xml来存储键值对,位置在/data/data/packagename/shared_prefs目录下面,从本质来说SharePreferences也属于文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存里面有一份SharePreferences文件的缓存,因此在多进程模式下,系统对他的读写变得不可靠,当面对高并发的读写访问就有很大几率丢失数据,因此不建议进程间通信使用SP。

3.使用Messenger

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。从构造方法可以很明显的看出AIDL的痕迹。

public Messenger(Handler target) {
mTarget = target.getIMessenger();
}

public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}

Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简单地进行线程间通信,同时由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这个是因为服务端不存在并发执行的情况。

步骤:
1.服务端进程,首先我们要创建一个Service来处理客户端的请求,同时创建一个Handler并通过它来创建一个Messenger对象,在Service的onBind里面返回这个Messenger对象底层的Binder即可。
2.客户端进程,首先要绑定服务端的Service,绑定成功之后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了发送消息类型为Message对象。

如果要服务端能够回应客户端,就和服务端一样,需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

服务端:
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();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand( intent, flags, startId);
}
}
客户端:
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();
}
}
Mainfest里面:
<activity
android:name= ".messenger.MessengerActivity"
android:label= "@string/title_activity_messenger" >

<intent-filter >
<action android:name ="android.intent.action.MAIN" />
</intent-filter >
</activity >

<service
android:name= ".messenger.MessengerService"
android:process= ":remote" >

<intent-filter >
<action android:name ="com.ryg.MessengerService.launch" />
</intent-filter >
</service >

注意:
通过Messenger来传递Message,Message中能用的载体只有what,arg1,arg2,Bundle以及replyTo。Message中的另外一个字段object在同一个进程中是很实用的,但是在跨进程间通信的时候,在Android2.2以前object字段不支持跨进程传输,
即使2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的。

Messenger的工作原理图:
二、IPC机制续(IPC方式)

4.使用AIDL

由于Messenger的主要作用还是传递消息,有时候可能需要跨进程调用服务端的方法,那么Messenger就不行了。
使用AIDL进行跨进程通信也分为客户端和服务端两个方面:
(1)服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口咋这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

(2)客户端
首先绑定服务端的Service,绑定成功之后,将服务端返回的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

(3)AIDL接口的创建

package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}

AIDL文件支持的数据类型:
1)基本数据类型(int,long,char,boolean,double等)
2)String和CharSquence
3)List,只支持ArrayList,里面的每个元素都要必须能被AIDL支持
4)Map,只支持HashMap,里面的每个元素都必须被AIDL支持,
包括Key和Value
5)Parcelable,所有实现了Parcelable接口的对象
6)AIDL,所有的AIDL接口本身也可以在AIDL文件中使用
以上的6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管是否和当前的AIDL文件位于同一个包里面。
另外,如果AIDL文件里面用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在里面声明它为Parcelable类型。
在IBookManager.aidl文件中使用到了自定义的Book对象,所以必须创建Book.aidl在里面添加:

package com.ryg.chapter_2.aidl;
parcelable Book;

AIDL中每个实现了Parcelable接口的类型的类,都需要像上面那样去声明,创建对应的AIDL文件,并声明那个类为parcelable。除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向,in,out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。AIDL接口中只支持方法,不支持静态常量。

in,out,inout区别:
http://hold-on.iteye.com/blog/2026138
二、IPC机制续(IPC方式)

二、IPC机制续(IPC方式)

为了方便开发,建议把所有的和AIDL相关的类和文件全部放入同一个包中,这样的好处是,当客户端是另外的应用时,我们可以直接把整个包复制放入到客户端工程中。
AIDL包结构,在客户端和服务端要一致,否则会运行出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类。

(4)远程服务端的Service实现

public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList =
new CopyOnWriteArrayList<Book>();
private Binder mBinder = new 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 onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

}
        <service
android:name= ".aidl.BookManagerService"
android:process= ":remote" >
</service >

采用CopyOnWriteArrayList,它支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的问题,所以要在AIDL方法中处理线程同步,这里使用它来进行自动的线程同步。

服务端可以使用CopyOnWriteArrayList和ConcurrentHashMap来进行自动线程同步,客户端拿到的依然是ArrayList和HashMap。

(5)客户端的实现
客户端首先绑定远程服务,绑定成功之后将服务端返回的Binder对象转换成为AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。

public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log. i(TAG, "query book list, list type:"
+ list.getClass().getCanonicalName());
Log. i(TAG, "query book list:" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}

public void onServiceDisconnected(ComponentName className) {
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState);
setContentView(R.layout. activity_book_manager);
Intent intent = new Intent( this, BookManagerService. class);
bindService( intent, mConnection, Context. BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
unbindService( mConnection);
super.onDestroy();
}

}

(6)监听
比如现在想服务端有新书的时候通知客户端,那么必须要监听了需要使用RemoteCallbackList,存储我们自定义的监听器,它是一个泛型,支持管理任意的AIDL接口。它的内部是Map结构,key是IBinder类型,value是Callback类型。

注意:
服务端和客户端之间做监听器,服务端需要使用RemoteCallbackList,否则客户端的监听器无法收到通知(因为服务端实质还是一份新的序列化后的监听器实例,并不是客户端那份)

RemoteCallbackList的beginBroadcast和finishBroadcast必须配对使用,哪怕我们仅仅需要获取RemoteCallbackList中的元素个数。

(7)不要在客户端的ui线程里面调用服务端的耗时方法
客户端调用远程服务方法时,因为远程方法运行在服务端的binder线程池中(服务端方法可以执行大量耗时操作,不需要开线程执行异步任务的);
同时客户端线程会被挂起,所以如果该方法过于耗时,而客户端又是UI线程,会导致ANR,所以当确认该远程方法是耗时操作时,应避免客户端在UI线程中调用该方法。
同理,当服务器调用客户端的listener方法时,该方法也运行在客户端的binder线程池中,所以如果该方法也是耗时操作,请确认运行在服务端的非UI线程中。
另外,因为客户端的回调listener运行在binder线程池中,所以更新UI需要用到handler。

(8)服务端进程意外终止
客户端通过IBinder.DeathRecipient来监听Binder死亡,也可以在onServiceDisconnected中监听并重连服务端。区别在于前者是在binder线程池中,访问UI需要用Handler,后者则是UI线程。

(9)权限验证
可通过自定义权限在onBind或者onTransact中进行权限验证。

onBind中验证,验证不通过返回null,验证方式可以使用permission验证,首先在manifest里面注册。

<permission
android:name= "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel= "normal" />

就可以在onBind里面验证了。

      @Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" );
Log. d(TAG, "onbind check=" + check);
if ( check == PackageManager. PERMISSION_DENIED) {
return null;
}
return mBinder;
}

一个应用来绑定我们的服务的时候,会验证这个应用的权限,没有权限就返回null。这个方法同样适用于Messenger中。

我们自己内部的应用想要绑定我们的服务,只需要在Manifest采用如下方式使用permission即可。

<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />

服务端的onTransact方法中进行权限验证,验证失败就会返回false,服务端的方法就不会执行,验证方式可以采用permission验证,也可以使用Uid和Pid来验证。通过getCallingUid和getCallingPid可以拿到客户端所属的应用的Uid和Pid,通过这两个参数可以做一些验证工作,比如验证包名。

下面即验证了权限又需要包名以com.rgy开始。

             public boolean onTransact( int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission( "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" );
Log. d(TAG, "check=" + check);
if ( check == PackageManager. PERMISSION_DENIED) {
return false;
}

String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if ( packages != null && packages. length > 0) {
packageName = packages[0];
}
Log. d(TAG, "onTransact: " + packageName);
if (! packageName.startsWith( "com.ryg")) {
return false;
}

return super.onTransact( code, data, reply, flags);
}

5.使用ContentProvider

底层实现是AIDL,使用比AIDL简单许多。自定义ContentProvider需要继承ContentProvider并实现里面的方法即可,onCreate,query,update,insert,delete,getType,getType用来返回Uri请求所对应的MimeType。如果应用不关系这个,只需要返回null或者”/“,根据Binder,我们知道这6个方法均运行在ContentProvider的进程里面,除了onCreate方法由系统回调并运行在主线程里面,其余的5个均由外界调用并运行在Binder线程池中。

虽然ContentProvider的底层数据看起来很像一个SQLite数据库,但是它对底层的数据的存储方式没有任何要求,我们即可以使用SQLite,也可以使用普通文件,甚至可以采用内存中的一个对象来进行数据的存储。

注册android:authorities是它的唯一标识,建议命名的时候加上包名前缀,如果声明了权限,那么外界应用也需要相应的权限。

ContentProvider(有的手机上会出现不加uses-permission依然可以访问BookProvider的问题)

6.使用Socket
Socket 一般用于网络通信,AIDL用这种方式会过于繁琐,不建议。

7.Binder连接池
比如100个地方需要用到AIDL那么不可能创建100个Service,需要减少Service的数量,将AIDL放在同一个Service里面去管理。

Binder连接池的作用就是将每个业务模块的Binder请求统一转发到远程的Service中去。

二、IPC机制续(IPC方式)

Binder连接池,通过BinderPool的方式将Binder的控制与Service本身解耦,同时只需要维护一份Service即可。这里用到了CountDownLatch,大概解释下用意:线程在await后等待,直到CountDownLatch的计数为0,BinderPool里使用它的目的是为了保证Activity获取BinderPool的时候Service已确定bind完成~

例子:
两个AIDL:

ISecurityCenter.aidl
package com.ryg.chapter_2.binderpool;
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
ICompute.aidl
package com.ryg.chapter_2.binderpool;
interface ICompute {
int add( int a, int b);
}

实现:

public class SecurityCenterImpl extends ISecurityCenter.Stub {
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[ i] ^= SECRET_CODE;
}
return new String(chars);
}

@Override
public String decrypt (String password ) throws RemoteException {
return encrypt( password);
}

}

以及:

public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}

为Binder连接池创建AIDL接口IBinderPool.aidl

interface IBinderPool {
/**
* @param binderCode, the unique token of specific Binder<br/>
* @return specific Binder who's token is binderCode.
*/

IBinder queryBinder( int binderCode);
}

为Binder连接池创建远程Service并实现IBinderPool,
下面是queryBinder的实现:

        @Override
public IBinder queryBinder( int binderCode) throws RemoteException {
IBinder binder = null;
switch ( binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}

远程Service的实现比较简单了:

public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPool.BinderPoolImpl();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent ) {
Log.d(TAG, "onBind");
return mBinderPool;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}

下面还有Binder连接池的具体实现,首先绑定远程服务,成功之后,客户端就可以通过它的queryBinder方法获取各自对应的Binder,拿到所需要的Binder之后,不同的业务模块之间就可以进行各自的操作了。

public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_SECURITY_CENTER = 1;

private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;

private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}

public static BinderPool getInsance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}

private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

/**
* query binder by binderCode from binder pool
*
* @param binderCode
* the unique token of binder
* @return binder who's token is binderCode<br>
* return null when not found or BinderPoolService died.
*/

public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}

private ServiceConnection mBinderPoolConnection = new ServiceConnection() {

@Override
public void onServiceDisconnected(ComponentName name) {
// ignored.
}

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
};

private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};

public static class BinderPoolImpl extends IBinderPool.Stub {

public BinderPoolImpl() {
super();
}

@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}

return binder;
}
}

}

使用:

public class BinderPoolActivity extends Activity {
private static final String TAG = "BinderPoolActivity";
private ISecurityCenter mSecurityCenter;
private ICompute mCompute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState);
setContentView(R.layout. activity_binder_pool);
new Thread(new Runnable() {
@Override
public void run() {
doWork();
}
}).start();
}
private void doWork() {
BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity. this);
IBinder securityBinder = binderPool
.queryBinder(BinderPool. BINDER_SECURITY_CENTER);
mSecurityCenter = (ISecurityCenter) SecurityCenterImpl
. asInterface(securityBinder);
Log.d(TAG, "visit ISecurityCenter");
String msg = "helloworld-安卓";
System. out.println( "content:" + msg);
try {
String password = mSecurityCenter.encrypt( msg);
System. out.println( "encrypt:" + password);
System. out.println( "decrypt:" + mSecurityCenter.decrypt(password ));
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG, "visit ICompute");
IBinder computeBinder = binderPool
.queryBinder(BinderPool. BINDER_COMPUTE);
mCompute = ComputeImpl.asInterface(computeBinder );
try {
System. out.println( "3+5=" + mCompute.add(3, 5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

8.选择适合的IPC方式

二、IPC机制续(IPC方式)