使用Bundle
我们知道,四大组件中的三大组件(Activity,Service,Broadcast)都是支持在Intent中传输Bundle数据的,由于Bundle实现了Parcelable接口,所以他可以很方便的在进程间传输,通过Bundle我们不就可以传输基本类型,也可以传输实现了Parcelable接口或者Serializable接口的对象
使用文件共享
- 在Windows上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问,包括读和写
- 而Android由于是基于Linux系统,使得这个并发读写操作可以无限制的进行,尽管这可能出问题
- 但是这种方式却是解决了不同进程数据传输的问题
接下来简单的举个栗子示范一下在本进程中写数据:
Data data = new Data(10,"这是主进程写的数据");
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("/storage/emulated/0/1/sina/sdadas.txt"));
out.writeObject(data);
out.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "ssss: 序列化的时候失败" + e.toString());
}
在另一进程中读数据
Data data = null;
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("/storage/emulated/0/1/sina/sdadas.txt"));
data = (Data) in.readObject();
}catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(data != null){
Log.d(TAG, "onClick: 读取成功 , data.id = " + data.id + "data.name = " + data.name);
}else {
Log.d(TAG, "onClick: 错误");
}
这是正常并且操作成功的,说明这种方式是可行的,不过我们这里虽然说的是传输数据,只不过我们保证的是内容一样,他们实际上还是两个不同的对象,通过这种方式,我们应尽量控制读写时的时序问题
使用Messenger
Messenger可以翻译为信使,通过它可以在不同进程中传递Messenger对象,在Messenger中可以放入我们需要传递的对象,就可以轻松跨进程传输
-
Messenger是一种轻量级的IPC方案,他的底层实现是AIDL,为什么这么说呢,我们去看一下他的构造方法
public Messenger(Handler target) { mTarget = target.getIMessenger(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
从上构造方法我们可以很容易想到底层是AIDl的
Messenger的用法非常简单,他对AIDL做了封装,使得我们可以更简单的跨进程通信
同时,由于他一次处理一个请求,因此在服务端我们不同考虑线程同步的问题,这是因为服务端不承诺在并发执行的问题,下面我们来看看怎么实现一个Messenger
我们首先来看看服务端进程中怎么实现
只需要在服务端创建一个Service来处理客户端的连接请求,并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可
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 1:
Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
break;
}
super.handleMessage(msg);
}
}
private final Messenger mMessenger = new Messenger(new Messengerhandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
可以看到,因为我们在上面的Messenger构造器中发现Messenger的构造器需要传入一个handler 类型的参数,很明显这个Handler就是用来处理客户端发来的Message消息的
接下来看看客户端的代码
客户端首先要绑定Service,然后发送Message类型的消息
void bindMyMessengerService(){
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
sendData();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
void sendData(){
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","这是客户端的消息");
msg.setData(data);
try{
mService.send(msg);
}catch (RemoteException e) {
e.printStackTrace();
}
}
这段代码相信没什么难度,不过这里问题又来了?诶?我们只是发送了消息给服务端,可是通信,通信,不是应该能互相发的吗?客户端是怎么接收服务端的呢?
接下来我们看看服务端怎么给客户端发送消息,先来看看服务端怎么修改
private static class Messengerhandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message clientData = Message.obtain(null,1);
Bundle bundle = new Bundle();
bundle.putString("reply","嗯,我收到了你的消息,我是服务端");
clientData.setData(bundle);
try {
client.send(clientData);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msg);
}
}
这里我将服务端接收消息的handler的Hand可Message方法改变了一下,让他能在接收到消息之后直接再给客户端发送一个消息
这里可以看到,我们发送消息的Messenger是从客户端发送过来msg中得到的,能猜到吧,客户端将自己进程所在的Messenger通过Messenger发送给服务端,然后服务端就可以拿到客户端进程的Messenger句柄给客户端发送消息了
-
看一下客户端的代码
private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
private static class ClientMessenger extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.d(TAG, "handleMessage: 接收到服务端的消息 = " +msg.getData().getString("reply"));
break;
}super.handleMessage(msg); }
}
void sendData(){
Message msg = Message.obtain(null,1);
msg.replyTo = mCilentMessenger;
Bundle data = new Bundle();
data.putString("msg","这是客户端的消息");
msg.setData(data);
try{
mService.send(msg);
}catch (RemoteException e) {
e.printStackTrace();
}
}
- 这里我添加了一个Messenger对象负责作为发送消息时候的replyTo参数,然后将这个参数在发送消息时添加到replyTo参数上即可
- 具体代码其实也没啥难度,不太清楚的小伙伴认真分析一下即可
Messenger总结
- 我们发现Messenger的核心就是使用本进程的Messenger在另外一个线程发送(Message)消息就可
使用AIDL
Binder之间通信还是分为客户端和服务端
先来看服务端,还是创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可
-
先看看服务端AIDL接口的创建吧
// IBookManager.aidl package com.example.learnretrofit.LearnMoreProcess; import com.example.learnretrofit.LearnMoreProcess.Book; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
创建AIDL文件的方法上篇文章讲过,在文章最开始有链接
在AIDL文件中,并不是所有类型都可使用,他所能使用的数据类型如下
基本数据类型
- String和CharSequence
- List:只支持ArrayList,里面每个元素都必须能被AIDL支持
- Map:只支持HashMap,里面每个元素都必须能被AIDL支持
- Parcelable:所有实现了Parcelable接口的对象,使用时需要手动import
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用,使用时需要手动import
这里由于在AIDL接口中使用了Book类,所以必须在前面import,另外如果AIDL文件用到了自定义的Parcelable对象,必须新建一个和他同名的AIDL文件,并在其中声明他为Parcelable类型,因为在上面的接口定义中,我们使用了Book类,所以我们必须定义一个aidl文件去声明他
// Book.aidl
package com.example.learnretrofit.LearnMoreProcess;
parcelable Book;
不知道大家注意到接口定义的方法的参数声明前有个in,这个参数是什么意思呢?AIDL中除了基本数据类型之外,其他类型的参数都必须标上方向,in , out ,或者inout,in表示输入性参数,out表示输出型参数,inout表示输入输出型参数,因为这个参数代表着底层的操作,所以在标注的时候不能随便标
最后要注意的是AIDL接口中只支持方法,不支持静态变量,还有,服务端和客户端的包结构必须一致,因为客户端需要反序列话服务器中和AIDL接口相关的所有类,如果包路径不同就会无法反序列化成功
-
那么上面的接口定义好了,我们就来看看我们的服务端怎么实现
注意,在编写好aidl文件之后记得build-make projectpublic class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Apple> mAppleList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IAppleManager.Stub() {
@Override
public List<Apple> getAppleList() throws RemoteException {
return mAppleList;
}@Override public void addApple(Apple apple) throws RemoteException { mAppleList.add(apple); } }; @Override public void onCreate() { super.onCreate(); mAppleList.add(new Apple(10,"红富士")); mAppleList.add(new Apple(10,"早熟苹果")); } @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } }
这里的IAppleManager.Stub是系统给我们生成的东西,他实际上是一个Binder类,然后我们先在onCreate方法里面添加两个数据,注意到我们这里使用了CopyOnWriteArrayList,因为他在原理上是支持并发读写的,* * 而AIDL支持的是抽象的List这个接口,虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端,所以我们在这里使用CopyOnWriteArrayList是完全合理的,类似的还有ConcurrentHashMap,然后在注册文件中注册这个Service
接着来看看客户端的实现-
客户端的话我们就需要绑定服务,然后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调服务端的远程方法了,看看客户端的代码
void bindMyAppleService(){ ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IAppleManager appleManager = IAppleManager.Stub.asInterface(service); try { List<Apple> list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName()); for(Apple apple : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) {} }; bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE); }
在绑定成功之后直接跨进程获取数据,注意这里是为了方便才这么写,一般还是写在新线程中比较好,因为这个服务端响应时间是不定的,万一响应时间过长就会出现ANR
如果你对AIDL还不太清楚,可能看这些有点模糊,烦请移驾androidIPC机制探索先把这个看看
-
在启动程序后打印出来的log信息为
onServiceConnected: query apple list ,list type is java.util.ArrayList onServiceConnected: apple.color = 红富士,apple.size = 10 onServiceConnected: apple.color = 早熟苹果,apple.size = 10
可见客户端收到的确实是Arraylist类型,证实了我们上面的说法,同时下面的信息也证明了我们的跨进程操作成功
-
我们再试一下他的addApple方法,修改onServiceConnection方法
public void onServiceConnected(ComponentName name, IBinder service) { IAppleManager appleManager = IAppleManager.Stub.asInterface(service); try { List<Apple> list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName()); for(Apple apple : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size); } Apple apple = new Apple(10,"绿色的苹果"); appleManager.addApple(apple); list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: 重新接收"); for(Apple apple1 : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size); } } catch (RemoteException e) { e.printStackTrace(); } }
log信息
onServiceConnected: query apple list ,list type is java.util.ArrayL
onServiceConnected: apple.color = 红富士,apple.size = 10
onServiceConnected: apple.color = 早熟苹果,apple.size = 10
onServiceConnected: 重新接收
onServiceConnected: apple.color = 红富士,apple.size = 10
onServiceConnected: apple.color = 早熟苹果,apple.size = 10
onServiceConnected: apple.color = 绿色的苹果,apple.size = 10
没毛病,我们接下来看看AIDL更多的用法
AIDL深入探索
我们看到,前面我们的跨进程通信都是一问一答式,就是客户端是主导,服务器是被主导,那么会不会有一种需求,我们不想去实时的查询了,因为这样太耗费时间,每次还不一定都有信息更新,所以,能不能有一种操作,客户端去问服务器:当有信息更新的时候你能不能直接给我说呢?
以上需求是一种典型的观察者模式,服务器在有数据更新的时候通知每一位想知道这个消息的客户端,可是怎么用AIDL实现呢?
-
首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并对服务器申请数据更新的通知功能,当然用户也可以随时取消这种功能,这里我们创建一个
// InewDataListener.aidl package com.example.learnretrofit.LearnMoreProcess; import com.example.learnretrofit.LearnMoreProcess.Apple; interface InewDataListener { void DataUpdata(in Apple apple); }
当然不止增加这个接口,我们还需要在原来接口做改动
// IBookManager.aidl
package com.example.learnretrofit.LearnMoreProcess;
import com.example.learnretrofit.LearnMoreProcess.Apple;
import com.example.learnretrofit.LearnMoreProcess.InewDataListener;
interface IAppleManager {
List<Apple> getAppleList();
void addApple(in Apple apple);
void registerListener(InewDataListener listener);
void unRegisterListener(InewDataListener listener);
}
这两个方法很容易理解
然后我们build -> make project
-
然后服务端的Binder对象就会提示我们有两个接口方法没实现,手动实现,然后看看此时的服务端代码有哪些改动吧
private AtomicBoolean mAtomicBoolean = new AtomicBoolean(false); private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>(); private Binder mBinder = new IAppleManager.Stub() { @Override public List<Apple> getAppleList() throws RemoteException { return mAppleList; } @Override public void addApple(Apple apple) throws RemoteException { mAppleList.add(apple); } @Override public void registerListener(InewDataListener listener) throws RemoteException { if(!mListeners.contains(listener)){ mListeners.add(listener); } } @Override public void unRegisterListener(InewDataListener listener) throws RemoteException { if(mListeners.contains(listener)){ mListeners.remove(listener); } } }; public void onCreate() { super.onCreate(); mAppleList.add(new Apple(10,"红富士")); mAppleList.add(new Apple(10,"早熟苹果")); new Thread(new ServiceWorker()).start(); } private class ServiceWorker implements Runnable{ @Override public void run() { while (!mAtomicBoolean.get()){ try{ Thread.sleep(5000); int appleId = mAppleList.size() + 1; Apple apple = new Apple(appleId,"苹果 " + appleId + " 号"); addNewApple(apple); } catch (InterruptedException e) { e.printStackTrace(); } } } } private void addNewApple(Apple apple) { mAppleList.add(apple); for(int i = 0;i < mListeners.size() ; i ++) { InewDataListener listener = mListeners.get(i); try { listener.DataUpdata(apple); } catch (RemoteException e) { e.printStackTrace(); } } }
-
分析起来不难理解,这里就不再说了,我们再看看客户端的改动
private InewDataListener mInewDataListener = new InewDataListener.Stub() { @Override public void DataUpdata(Apple apple) throws RemoteException { mHandler.obtainMessage(10,apple).sendToTarget(); } }; void bindMyAppleService(){ ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IAppleManager appleManager = IAppleManager.Stub.asInterface(service); try { List<Apple> list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName()); for(Apple apple : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size); } Apple apple = new Apple(10,"绿色的苹果"); appleManager.addApple(apple); list = appleManager.getAppleList(); Log.d(TAG, "onServiceConnected: 重新接收"); for(Apple apple1 : list){ Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size); } appleManager.registerListener(mInewDataListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) {} }; bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE); } @SuppressLint("HandlerLeak") private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 10: Log.d(TAG, "handleMessage: receive The new Data " + msg.obj); } super.handleMessage(msg); } };
定义了接口,声明了当被回调时候的动作,接着在onServiceConnection中注册接口
这里要注意的是接口回调是在Binder线程池中执行的,这一点可以通过我的上篇博客看到,这里不再多说,因此我们要进行UI操作的话,还是尽量使用Handler将他弄到主线程使用
-
这里运行程序看到log信息为
onServiceConnected: query apple list ,list type is java.util.ArrayList onServiceConnected: apple.color = 红富士,apple.size = 10 onServiceConnected: apple.color = 早熟苹果,apple.size = 10 onServiceConnected: 重新接收 onServiceConnected: apple.color = 红富士,apple.size = 10 onServiceConnected: apple.color = 早熟苹果,apple.size = 10 onServiceConnected: apple.color = 绿色的苹果,apple.size = 10 handleMessage: receive The new Data 苹果 4 号 handleMessage: receive The new Data 苹果 5 号 handleMessage: receive The new Data 苹果 6 号
AIDL解决跨进程取消注册问题
-
Android系统为我们提供了一个专门用于删除跨进程listener的接口–RemoteCallbackList,他是一个泛型,支持管理任意的AIDL接口,这点从他的声明就可以看出来,因为所有的AIDL接口都继承自IInterface接口,这点如果是因为我们写的aidl接口都会由系统为我们自行生成一个java文件,而标准格式就是都会继承自IInterface接口,下面是这个RemoteCallbackList接口的声明
public class RemoteCallbackList<E extends IInterface>
它的工作原理很简单,在他的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
其中Callback中封装了真正的远程Listener,当客户端注册listener的时候,他会把这个listener信息存入mCallback中,其中key和value分别通过下面的方式获得
到这里的时候有没有点明悟了,还记得我们负责注册的过程是谁来完成的吗?是服务端给我们返回的一个Binder对象,而我们的跨进程通信都是基于这个对象的,而一般一个组件都是只有一个Binder的,所以?我们每个客户端与服务器之间的底层Binder是一一对应的,这点是不会变的,所以就有了上面的写法,我们存binder就行了,到时候你想取消注册,那就看看你的底层是哪个binder
RemoteCallbackList这个接口不光可以解决这个问题,他还有一个很有用的功能,那就是当客户端进程终止时,他会自动移除客户端所注册的listener
另外,RemoteCallbackList内部自动实现了线程同步的功能,所以我们用它的时候不需要做额外的线程同步工作
说了这么多,我们来看看他到底怎么用吧
服务端的改变
-
先用RemoteCallbackList创建新的集合代替原来的集合
private RemoteCallbackList<InewDataListener> mCallbackList = new RemoteCallbackList<>(); // private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>();
-
修改注册和取消注册两个接口的实现
public void registerListener(InewDataListener listener) throws RemoteException { // if(!mListeners.contains(listener)){ // mListeners.add(listener); // } mCallbackList.register(listener); } @Override public void unRegisterListener(InewDataListener listener) throws RemoteException { // if(mListeners.contains(listener)){ // mListeners.remove(listener); // } mCallbackList.unregister(listener); }
-
接下来修改通知的方法
private void addNewApple(Apple apple) { mAppleList.add(apple); // for(int i = 0;i < mListeners.size() ; i ++) { // InewDataListener listener = mListeners.get(i); // try { // listener.DataUpdata(apple); // } catch (RemoteException e) { // e.printStackTrace(); // } // } final int N = mCallbackList.beginBroadcast(); for (int i = 0;i < N;i++){ InewDataListener listener = mCallbackList.getBroadcastItem(i); if(listener != null){ try{ listener.DataUpdata(apple); } catch (RemoteException e) { e.printStackTrace(); } } } mCallbackList.finishBroadcast(); }
是不是非常简单呢,我们来看一下具体效果吧,我在客户端进程的activity中添加一个取消注册的按钮
-
运行之后的log
unRegisterListener: 取消之前的数量是 1 unRegisterListener: 取消之后的数量是 0
-
注意:使用RemoteCallbackList的时候,我们无法像操作List一样去操作它,他并不是一个List,而且,遍历RemoteCallbackList的时候,必须要将代码写在CallbackList.beginBroadcast()和CallbackList.finishBroadcast()这两个方法之间,无论是遍历,还是哪怕只是获取个元素个数
final int N = mCallbackList.beginBroadcast(); for (int i = 0;i < N;i++){ InewDataListener listener = mCallbackList.getBroadcastItem(i); if(listener != null){ try{ listener.DataUpdata(apple); } catch (RemoteException e) { e.printStackTrace(); } } } mCallbackList.finishBroadcast();
到这里关于AIDL的基本东西就说完了,这里说几点注意
- 我们必须清楚什么方法执行在主线程,什么方法执行在Binder线程池,一些耗时的或者不可知的操作尽可能放在多线程中解决
- 涉及到UI操作的,如果当前线程非主线程,要使用Handler切换到主线程再进行操作
还有,binder是有可能意外死亡的,为了我们程序的健壮性,我们有必要给Binder设置死亡监听
AIDL拓展
- 如果我们的服务想要更健全的话,我们有必要给他加上权限验证功能,因为在默认情况下,我们的远程服务是任何人都能连接的,这是我们不愿意看到的,所以我们必须给服务加入权限验证功能
怎么加这个功能呢?我们想一下,服务最早接触客户端的方法是哪个方法? - 当然是onBind方法了,我们在绑定服务的时候,会使用onBind方法返回的对象来拿到我们操作服务的句柄,那么我们的权限验证就可以在这里做
- 不过在做之前我们首先应该定义自己的权限
- 这里简单说一下自定义权限
自定义权限简单说明
-
在AndroidManifest.xml中,像这个样子
<permission android:description="string resource" android:icon="drawable resource" android:label="string resource" android:name="string" android:permissionGroup="string" android:protectionLevel=["normal" | "dangerous" |"signature" | "signatureOrSystem"] />
-
这里对各个属性做个说明
2.png -
protectionLevel权限等级的说明
3.png -
然后我们在这里在我们的AndroidManifest.xml声明权限
<permission android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE" android:protectionLevel="normal"/>
-
然后在我们的onBind方法添加权限验证
public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"); if(check == PackageManager.PERMISSION_DENIED){ return null; } return mBinder; }
-
如果我们自己的应用想要绑定我们的服务,只需要在他的AndroidManifest.xml中声明权限即可
<uses-permission android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"/>
当然,权限验证不仅仅局限于AIDL,他可以用在所有你想用的地方
第二种权限验证的方法
我们可以在服务端的Binder的onTransact方法中进行权限验证,如果验证失败,就直接返回false,这样服务端就不会去执行客户端想要的逻辑 ,也达到了权限验证的效果
我们甚至还可以采用UID和PID来做验证,通过getCallingUID和getCallingPid来拿到客户端所属进程的UID和Pid,这样我们就可以验证包名,等具体的操作大家具体去尝试吧
下面我们继续学习另外的IPC方式