跨进程通讯

一、Android 中的多进程

1、定义

首先,进程一般指一个执行单元,在移动设备上就是一个程序或应用,我们在Android中所说的多进程(IPC)一般指一个应用包含多个进程。之所以要使用多进程有两方面原因:某些模块由于特殊的需求要运行在单独的进程;增加应用可用的内存空间。

2、开启多进程

Android中开启多线程只有一种方法,就是在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性,例如:

1
<service     android:name= ".MyService"      android:process= ":remote" ></service><activity     android:name= ".MyActivity"      android:process= "com.shh.ipctest.remote2" ></activity>

我们为MyService和MyActivity指定的android:process属性值有所不同,它们的区别如下:

  • :remote:以:开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.shh.ipctest:remote,同时以:开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。

  • com.shh.ipctest.remote2:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。

3、多进程引发的问题

开启多进程虽简单,但会引发如下问题,必须引起注意。

  • 静态成员和单例模式失效

  • 线程同步机制失效

  • SharedPreferences 可靠性降低

  • Application 被多次创建

对于前两个问题,可以这么理解,在Android中,系统会为每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间,所以同一个类的对象会产生不同的副本,导致共享数据失败,必然也不能实现线程的同步。

由于SharedPreferences底层采用读写XML的文件的方式实现,多进程并发的的读写很可能导致数据异常。

Application被多次创建和前两个问题类似,系统在分配多个虚拟机时相当于把同一个应用重新启动多次,必然会导致 Application 多次被创建,为了防止在 Application
中出现无用的重复初始化,可使用进程名来做过滤,只让指定进程的才进行全局初始:

1
2
3
4
5
6
public  class  MyApplication  extends  Application{    @Override
     public  void  onCreate() {         super .onCreate();
         String  processName =  "com.shh.ipctest" ;         if  (getPackageName().equals(processName)){             // do some init
         }
     }
}

4、Android中的多进程通信方式

Android中支持的多进程通信方式主要有以下几种,它们之间各有优缺点,可根据使用场景选择选择:

  • AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现 RPC (远程过程调用)。

  • Messenger:支持一对多的串行实时通信, AIDL 的简化版本。

  • Bundle:四大组件的进程通信方式,只能传输 Bundle 支持的数据类型。

  • ContentProvider:强大的数据源访问支持,主要支持 CRUD 操作,一对多的进程间数据共享,例如我们的应用访问系统的通讯录数据。

  • BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息。

  • 文件共享:在非高并发情况下共享简单的数据。

  • Socket:通过网络传输数据。

这里我们主要讨论四大组件中Service在多进程通信中的使用,这就涉及到了 AIDL、Messenger这两种多进程通信方式,接下来重点看这两种 IPC 方式。

二、AIDL

AIDL 的意思是 Android 接口定义语言,使用AIDL进行进程间通信需要定义服务端和客户端,其中客户端和服务端可以在同一应用也可以在不同应用。这里我们服务端可以看做是图书馆,为客户端提供近期新书查询、图书捐赠、新书通知的服务。

1、服务端实现

先创建一个 AIDL 文件,声明服务端要暴露给客户端的接口,然后创建一个 Service 监听客户端的连接请求,并在其中实现 AIDL 文件中的接口。

注意,为了方便开发,我们一般把 AIDL 相关的文件放在同一包中,这样当客户端是另一个应用时可方便的把整个包复制到客户端工程中。最终的AIDL文件包如下:

aidl

首先了解下 AIDL 文件支持的几种数据类型:

  • 基本数据类型

  • String、CharSequence

  • ArrayList、HashMap,其内部元素也需要被AIDL支持

  • 实现了 Parcelable 接口的对象

  • AIDL 类型的接口,非普通接口

Book是实现了Parcelable的图书类,只定义了图书名name字段,按照规定如果 AIDL 文件用到了自定义Parcelable对象,同时需要提供一个Book.aidl文件:

1
2
package  com.shh.ipctest;
parcelable Book;

ILibraryManager.aidl定义了服务端要暴露给客户端的接口:

1
2
3
4
package  com.shh.ipctest; import  com.shh.ipctest.Book; interface  ILibraryManager{     // 近期新书查询
     List<book> getNewBookList();     // 图书捐赠
     void  donateBook( in  Book book);
}</book>

注意,尽管ILibraryManager.aidl和Book在同一包中,还是需要显示的导入Book类。除了基本类型数据外,其它类型的参数需要标注方向,可选的方向标识有:

  • in:输入类型参数

  • out:输出类型参数

  • inout:输入输出类型参数

接下来就是LibraryManagerService这个服务类了,在编写服务类前要先编译项目,这样在服务类里使用 AIDL 生成的java类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  class  LibraryManagerService  extends  Service {     private  static  final  String  TAG =  "LibraryManagerService" ;     // CopyOnWriteArrayList 支持并发读写
     private  CopyOnWriteArrayList<book> mBookList =  new  CopyOnWriteArrayList<>();   
     private  Binder mBinder =  new  ILibraryManager.Stub() {        @Override
         public  List<book> getNewBookList() throws RemoteException {             return  mBookList;
         }        @Override
         public  void  donateBook(Book book) throws RemoteException {
             mBookList.add(book);
         }
     };     public  LibraryManagerService() {
     }    @Override
     public  IBinder onBind(Intent intent) {         return  mBinder;
     }    @Override
     public  void  onCreate() {         super .onCreate();
         mBookList.add( new  Book( "book0" ));
         mBookList.add( new  Book( "book1" ));
     }
}</book></book>

首先通过ILibraryManager.Stub()创建一个mBinder对象,并实现了ILibraryManager.aidl中定义的接口方法,在onBind()方法中返回创建的mBinder,并在服务onCreate()时添加两本书。
最后在 AndroidManifest.xml 注册服务:

1
<service     android:name= ".LibraryManagerService"      android:process= ":aidl_remote" ></service>

到这里服务端的基本功能就完成了。

2、客户端实现

这里先把客户端和服务端放在同一个应用,客户端的实现相对简单些,首先在项目 app 的build.gradle指定 AIDL 文件路径:

1
2
3
4
5
6
7
8
android {
     ......     // 指定 aidl 路径
     sourceSets {
         main {
             java.srcDirs = [ 'src/main/java' 'src/main/aidl' ]
         }
     }
}

之后就是绑定服务了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public  class  AIDLActivity  extends  AppCompatActivity {     private  static  final  String  TAG =  "AIDLActivity" ;     private  ServiceConnection mServiceConnection =  new  ServiceConnection() {        @Override
         public  void  onServiceConnected(ComponentName name, IBinder service) {
             ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service);             try  {                 // 近期新书查询
                 List<book> books = libraryManager.getNewBookList();
                 Log.e(TAG,  "books:"  + books.toString());                 // 捐赠一本书
                 libraryManager.donateBook( new  Book( "book"  + books.size()));
                 List<book> books2 = libraryManager.getNewBookList();
                 Log.e(TAG,  "books:"  + books2.toString());
             catch  (RemoteException e) {
                 e.printStackTrace();
             }
         }        @Override
         public  void  onServiceDisconnected(ComponentName name) {
         }
     };    @Override
     protected  void  onCreate(Bundle savedInstanceState) {         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_aidl);
         bindNewService();
     }     private  void  bindNewService() {
         Intent intent =  new  Intent(AIDLActivity. this , LibraryManagerService. class );
         bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }    @Override
     protected  void  onDestroy() {
         unbindService(mServiceConnection);         super .onDestroy();
     }
}</book></book>

先实现ServiceConnection接口,在onServiceConnected()方法中将IBinder对象转换成ILibraryManager对象,通过该对象就能调用ILibraryManager.aidl中声明的方法了。
接下来绑定服务:

1
2
Intent intent =  new  Intent(AIDLActivity. this , LibraryManagerService. class );
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

运行项目后观察log:

log

出现了两个进程,并在客户端进程打印了对应的图书名,到这里我们一个简单的 AIDL 多进程通信就实现了。

如果客户端和服务端在不同的应用怎么实现呢?首先将服务端的 AIDL 包复制到客户端项目的main目录下,在客户端项目 app 的build.gradle指定 AIDL 文件路径,最后就是绑定服务了,由于客户端需要隐式绑定服务,所以要先修改服务端LibraryManagerService的注册方式为:

1
2
3
4
<service     android:name= ".LibraryManagerService"      android:process= ":aidl_remote" >
     <intent-filter>
         <action android:name= "android.intent.action.librarymanagerservice"  >
     </action android:name= "android.intent.action.librarymanagerservice"  ></intent-filter></service>

然后就是在客户端隐式启动服务:

1
2
3
4
Intent intent =  new  Intent();
intent.setAction( "android.intent.action.LibraryManagerService" );
intent.setPackage( "com.shh.ipctest" );
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

3、通知

如果要添加一个新书提醒功能,即图书馆每采购一本新书后需要通知订阅了新书提醒功能的用户,要怎么修改服务端和客户端呢?

首先创建一个服务端通知客户端的 IOnNewBookArrivedListener.aidl 接口:

1
2
package  com.shh.ipctest; import  com.shh.ipctest.Book; interface  IOnNewBookArrivedListener {     void  onNewBookArrived( in  Book book);
}

我们约定服务端要先注册后才能收到通知,同时也可以取消注册,所以要给之前的ILibraryManager.aidl添加连个方法了:

1
2
3
4
5
package  com.shh.ipctest; import  com.shh.ipctest.Book; import  com.shh.ipctest.IOnNewBookArrivedListener; interface  ILibraryManager{
     ......     // 注册通知
     void  register(IOnNewBookArrivedListener listener);     // 取消注册
     void  unregister(IOnNewBookArrivedListener listener);
}

接下来就是修改服务端的LibraryManagerService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 只保留了相关核心代码public class LibraryManagerService extends Service {
     ......     // 系统提供的专门用于保存、删除跨进程 listener 的类
     private  RemoteCallbackList<ionnewbookarrivedlistener> mListenerList =  new  RemoteCallbackList<>();     // AtomicBoolean 支持并发读写
     private  AtomicBoolean mIsServiceDestroy =  new  AtomicBoolean( false );     private  Binder mBinder =  new  ILibraryManager.Stub() {
         ......        @Override
         public  void  register(IOnNewBookArrivedListener listener) throws RemoteException {
             mListenerList.register(listener);
             Log.e(TAG,  "register success" );
         }        @Override
         public  void  unregister(IOnNewBookArrivedListener listener) throws RemoteException {
             mListenerList.unregister(listener);
             Log.e(TAG,  "unregister success" );
         }
     };
    .......    @Override
     public  void  onCreate() {         super .onCreate();
         ......         // 在子线程中每隔3秒创建一本新书,并通知所有已注册的客户端
         new  Thread( new  Runnable() {            @Override
             public  void  run() {                 // 如果服务还没终止
                 while  (!mIsServiceDestroy. get ()) {                     try  {
                         Thread.sleep( 3  1000 );
                     catch  (InterruptedException e) {
                         e.printStackTrace();
                     }
                     Book book =  new  Book( "book"  + mBookList.size());
                     mBookList.add(book);
                     bookArrivedNotify(book);
                 }
             }
         }).start();
     }    
     private  void  bookArrivedNotify(Book book) {         int  n = mListenerList.beginBroadcast();         for  ( int  i =  0 ; i < n; i++) {
             IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);             if  (listener !=  null ) {                 try  {
                     listener.onNewBookArrived(book);
                 catch  (RemoteException e) {
                     e.printStackTrace();
                 }
             }
         }
         mListenerList.finishBroadcast();
     }    @Override
     public  void  onDestroy() {         super .onDestroy();
         mIsServiceDestroy. set ( true );
     }
}</ionnewbookarrivedlistener>

注意这里用到了RemoteCallbackList类,它是系统提供的专门用于删除跨进程 listener 的类,用普通的集合难以保证客户端注册的 listener 和服务端存储的 listener 是同一个,会取消注册失败。在的register()、unregister()中实现了通知接口的绑定和解绑操作。在onCreate()周期性的通知客户端有新书了。

在客户端中需要完成通知接口的注册和取消注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 只保留了相关核心代码public class AIDLActivity extends AppCompatActivity {
     ......     private  ILibraryManager mLibraryManager;     private  Handler mHandler =  new  Handler( new  Handler.Callback() {        @Override
         public  boolean handleMessage(Message msg) {             switch  (msg.what) {                 case  MESSAGE_NEW_BOOK_ARRIVED:
                     Log.e(TAG,  "new book:"  + msg.obj);                     break ;
             }             return  true ;
         }
     });     private  IOnNewBookArrivedListener listener =  new  IOnNewBookArrivedListener.Stub() {        @Override
         public  void  onNewBookArrived(Book book) throws RemoteException {             // 由于 onNewBookArrived 方法在子线程被调用,所以通过Handler切换到UI线程,方便UI操作
             mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget();
         }
     };     private  ServiceConnection mServiceConnection =  new  ServiceConnection() {        @Override
         public  void  onServiceConnected(ComponentName name, IBinder service) {
             ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service);
             mLibraryManager = libraryManager;             try  {
                 ......                 // 注册通知
                 libraryManager.register(listener);
             catch  (RemoteException e) {
                 e.printStackTrace();
             }
         }
         ......
     };    @Override
     protected  void  onCreate(Bundle savedInstanceState) {         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_aidl);
         bindNewService();
     }    @Override
     protected  void  onDestroy() {
         unbindService(mServiceConnection);         if  (mLibraryManager !=  null  && mLibraryManager.asBinder().isBinderAlive()) {             try  {                 // 取消注册
                 mLibraryManager.unregister(listener);
             catch  (RemoteException e) {
                 e.printStackTrace();
             }
         }         super .onDestroy();
     }
}

首先是创建IOnNewBookArrivedListener接口的对象,为了方便 UI 操作可以在回调中用Handler将服务端通知的结果切换到 UI 线程,并打印新书名,在onServiceConnected()中注册接口,在onDestroy()中取消注册。运行项目后看下客户端、服务端的log:

cilent

sevice

4、权限校验

目前我们的服务端任何客户端都可以连接,项目开发中为了安全起见,我们需要在服务端做权限校验,只有客户端配置了指定的权限,才能调用服务端的方法。

服务端可以使用permission+包名的方式来验证,首先在服务端的AndroidManifest.xml声明一个permission:

1
<permission         android:name= "com.shh.ipctest.permission.ACCESS_LIBRARY_SERVICE"          android:protectionlevel= "normal"   = "" ></permission>

接下来就是在LibraryManagerService中校验要连接的客户端是配置了该permission,以及包名是否符合指定规则,可以做校验的地方有两个,第一个是在onBind()中校验:

1
2
3
4
5
6
7
8
9
@Overridepublic IBinder onBind(Intent intent) {     if  (!passBindCheck()) {
         Log.e(TAG,  "bind denied" );         return  null ;
     }     return  mBinder;
} private  boolean permissionCheck() {     // 客户端是否已申请了指定权限
     int  check = checkCallingOrSelfPermission( "com.shh.ipctest.permission.ACCESS_LIBRARY_SERVICE" );     if  (check == PackageManager.PERMISSION_DENIED) {         return  false ;
     }     // 检验客户端包名会否以com.shh开头
     String [] packages = getPackageManager().getPackagesForUid(getCallingUid());     if  (packages !=  null  && packages.length >  0  && !packages[ 0 ].startsWith( "com.shh" )) {         return  false ;
     }     return  true ;
}

注意,onBind()是在客户端连接服务端时调用,如果客户端不能在此处通过校验则无发连接到服务。如果客户端和服务端是两个应用,则无法在onBind()实现校验的功能!

第二个地方是在ILibraryManager.Stub类的onTransact()方法中,在该方法里校验解决了客户端和服务端是两个应用时无法在onBind()实现校验的问题,代码如下:

1
2
3
4
5
6
7
private  Binder mBinder =  new  ILibraryManager.Stub() {
     ......    @Override
     public  boolean onTransact( int  code, Parcel data, Parcel reply,  int  flags) throws RemoteException {         if  (!permissionCheck()) {
             Log.e(TAG,  "bind denied" );             return  false ;
         }         return  super .onTransact(code, data, reply, flags);
     }
};

注意,该方法在onBind()之后执行,意味着客户端和服务端已经连接,当客户端调用服务端的方法时会走onTransact()方法。

最后需要在客户端的AndroidManifest.xml配置permission权限:

1
<uses-permission android:name= "com.shh.ipctest.permission.access_library_service"  ></uses-permission android:name= "com.shh.ipctest.permission.access_library_service"  >

5、重新连接

服务端进程可能会由于内存不足等原因意外终止而导致服务被杀死,所以有必要在这种情况下重新连接到服务。连接的方式有两种:

第一种相对简单,是在ServiceConnection接口的onServiceDisconnected()方法中重新连接服务,注意该方法在UI线程执行。

第二种是给客户端得到的Binder对象注册一个DeathRecipient监听,首先来创建该监听:

1
2
3
4
5
6
7
8
private  IBinder.DeathRecipient mDeathRecipient =  new  IBinder.DeathRecipient() {    @Override
     public  void  binderDied() {         if  (mLibraryManager !=  null ) {
             mLibraryManager.asBinder().unlinkToDeath(mDeathRecipient,  0 );
             mLibraryManager =  null ;             // 重新连接服务
             bindNewService();
         }
     }
}

当服务被杀死时binderDied()方法会被调用,接下来就是在服务连接成功后设置死亡监听:

1
2
3
4
5
6
7
8
9
10
@Override
     public  void  onServiceConnected(ComponentName name, IBinder service) {
         ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service);
         mLibraryManager = libraryManager;         try  {             // 将mLibraryManager转成Binder对象然后注册死亡监听
             mLibraryManager.asBinder().linkToDeath(mDeathRecipient,  0 );
         catch  (RemoteException e) {
             e.printStackTrace();
         }
         ......
     }

三、Binder

在 AIDL 的例子中,客户端之所以能和服务端通信,主要依靠系统提供的Binder类实现,它实现了IBinder接口,我们在前边的例子已经用到了Binder类,例如在服务端的 Service 中创建了一个Binder对象并在onBind()中返回:

1
2
3
Binder mBinder =  new  ILibraryManager.Stub(){
......
}

当在客户端绑定服务成功后,会得到服务端返回的Binder对象,并将其转换成可调用服务端暴露的方法的ILibraryManager对象:

1
2
3
4
5
6
7
private  ServiceConnection mServiceConnection =  new  ServiceConnection() {        @Override
         public  void  onServiceConnected(ComponentName name, IBinder service) {
             ILibraryManager libraryManager = ILibraryManager.Stub.asInterface(service);
             .......
         }
         ......
     };

可以发现不管是客户端还是服务端都用到了ILibraryManager.java类,回想一下之前我们创建过一个ILibraryManager.aidl文件,所以ILibraryManager.java应该是ILibraryManager.aidl在编译后生成的,果然在如下目录有这个类:

ILibraryManager

ILibraryManager.java的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public  interface  ILibraryManager  extends  android.os.IInterface {    
     public  static  abstract  class  Stub  extends  android.os.Binder  implements  com.shh.ipctest.ILibraryManager {         // Binder 的唯一标识
         private  static  final  java.lang. String  DESCRIPTOR =  "com.shh.ipctest.ILibraryManager" ;         public  Stub() {             this .attachInterface( this , DESCRIPTOR);
         }         // 将服务端的 Binder 对象转换成客户端需要的接口对象
         // 如果客户端和服务端在同一进程则返回 Stub 对象本身,否则返回Stub.Proxy这个代理对象
         public  static  com.shh.ipctest.ILibraryManager asInterface(android.os.IBinder obj) {             if  ((obj ==  null )) {                 return  null ;
             }
             android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);             if  (((iin !=  null ) && (iin  instanceof  com.shh.ipctest.ILibraryManager))) {                 return  ((com.shh.ipctest.ILibraryManager) iin);
             }             return  new  com.shh.ipctest.ILibraryManager.Stub.Proxy(obj);
         }         // 返回当前 Binder 对象
         @Override
         public  android.os.IBinder asBinder() {             return  this ;
         }         /**
          * 运行在服务端的 Binder 线程池,客户端发起的跨进程方法调用最终会交给该方法处理
          * @param code 确定客户端调用的是哪个方法
          * @param data  保存要调用的方法需要的参数
          * @param reply 保存要调用的方法最后的返回值
          * @param flags 
          * @return 返回false调用
          * @throws android.os.RemoteException
          */
         @Override
         public  boolean onTransact( int  code, android.os.Parcel data, android.os.Parcel reply,  int  flags) throws android.os.RemoteException {             switch  (code) {                 case  INTERFACE_TRANSACTION: {
                     reply.writeString(DESCRIPTOR);                     return  true ;
                 }                 case  TRANSACTION_getNewBookList: {
                     data.enforceInterface(DESCRIPTOR);
                     java.util.List<com.shh.ipctest.book> _result =  this .getNewBookList();                     // 封装返回的数据
                     reply.writeNoException();
                     reply.writeTypedList(_result);                     return  true ;
                 }                 case  TRANSACTION_donateBook: {
                     data.enforceInterface(DESCRIPTOR);
                     com.shh.ipctest.Book _arg0;                     if  (( 0  != data.readInt())) {                         // 反序列化出具体的请求参数
                         _arg0 = com.shh.ipctest.Book.CREATOR.createFromParcel(data);
                     else  {
                         _arg0 =  null ;
                     }                     this .donateBook(_arg0);
                     reply.writeNoException();                     return  true ;
                 }                 case  TRANSACTION_register: {
                     data.enforceInterface(DESCRIPTOR);
                     com.shh.ipctest.IOnNewBookArrivedListener _arg0;
                     _arg0 = com.shh.ipctest.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());                     this .register(_arg0);
                     reply.writeNoException();                     return  true ;
                 }                 case  TRANSACTION_unregister: {
                     data.enforceInterface(DESCRIPTOR);
                     com.shh.ipctest.IOnNewBookArrivedListener _arg0;
                     _arg0 = com.shh.ipctest.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());                     this .unregister(_arg0);
                     reply.writeNoException();                     return  true ;
                 }
             }             return  super .onTransact(code, data, reply, flags);
         }         // 客户端跨进程调用服务端方法时,会先在客户端执行该类里的对应方法,
         // 调用mRemote.transact()发起远程过程调用(RPC)请求,同时客户端对应的线程会挂起,
         // 进而执行服务端的onTransact()方法,直到 RPC 请求结束,客户端线程继续执行,
         // 这可能是一个耗时的过程,所以要避免客户端出现 ANR 的问题。
         private  static  class  Proxy  implements  com.shh.ipctest.ILibraryManager {             private  android.os.IBinder mRemote;
             Proxy(android.os.IBinder remote) {
                 mRemote = remote;
             }            @Override
             public  android.os.IBinder asBinder() {                 return  mRemote;
             }             public  java.lang. String  getInterfaceDescriptor() {                 return  DESCRIPTOR;
             }            @Override
             public  java.util.List<com.shh.ipctest.book> getNewBookList() throws android.os.RemoteException {                 // 保存请求参数的 Parcel 对象
                 android.os.Parcel _data = android.os.Parcel.obtain();                 // 保存返回结果的 Parcel 对象
                 android.os.Parcel _reply = android.os.Parcel.obtain();
                 java.util.List<com.shh.ipctest.book> _result;                 try  {
                     _data.writeInterfaceToken(DESCRIPTOR);                     // 发起 RPC 请求
                     mRemote.transact(Stub.TRANSACTION_getNewBookList, _data, _reply,  0 );
                     _reply.readException();                     // 从 RPC 请求的结果中取出最终要返回的数据
                     _result = _reply.createTypedArrayList(com.shh.ipctest.Book.CREATOR);
                 finally  {
                     _reply.recycle();
                     _data.recycle();
                 }                 return  _result;
             }            @Override
             public  void  donateBook(com.shh.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 );
                     }
                     mRemote.transact(Stub.TRANSACTION_donateBook, _data, _reply,  0 );
                     _reply.readException();
                 finally  {
                     _reply.recycle();
                     _data.recycle();
                 }
             }            @Override
             public  void  register(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                 android.os.Parcel _data = android.os.Parcel.obtain();
                 android.os.Parcel _reply = android.os.Parcel.obtain();                 try  {
                     _data.writeInterfaceToken(DESCRIPTOR);
                     _data.writeStrongBinder((((listener !=  null )) ? (listener.asBinder()) : ( null )));
                     mRemote.transact(Stub.TRANSACTION_register, _data, _reply,  0 );
                     _reply.readException();
                 finally  {
                     _reply.recycle();
                     _data.recycle();
                 }
             }            @Override
             public  void  unregister(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                 android.os.Parcel _data = android.os.Parcel.obtain();
                 android.os.Parcel _reply = android.os.Parcel.obtain();                 try  {
                     _data.writeInterfaceToken(DESCRIPTOR);
                     _data.writeStrongBinder((((listener !=  null )) ? (listener.asBinder()) : ( null )));
                     mRemote.transact(Stub.TRANSACTION_unregister, _data, _reply,  0 );
                     _reply.readException();
                 finally  {
                     _reply.recycle();
                     _data.recycle();
                 }
             }
         }         // 客户端可调用的方法的对应 code
         static  final  int  TRANSACTION_getNewBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION +  0 );         static  final  int  TRANSACTION_donateBook = (android.os.IBinder.FIRST_CALL_TRANSACTION +  1 );         static  final  int  TRANSACTION_register = (android.os.IBinder.FIRST_CALL_TRANSACTION +  2 );         static  final  int  TRANSACTION_unregister = (android.os.IBinder.FIRST_CALL_TRANSACTION +  3 );
     }     public  java.util.List<com.shh.ipctest.book> getNewBookList() throws android.os.RemoteException;     public  void  donateBook(com.shh.ipctest.Book book) throws android.os.RemoteException;     public  void  register(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException;     public  void  unregister(com.shh.ipctest.IOnNewBookArrivedListener listener) throws android.os.RemoteException;
}</com.shh.ipctest.book></com.shh.ipctest.book></com.shh.ipctest.book></com.shh.ipctest.book>

ILibraryManager类继承android.os.IInterface接口,其实 aidl类型文件中定义的接口在编译后都会生成一个继承IInterface的java类。这个类看起来有点复杂,但结构还是很清晰的,主要有两部分,首先是继承了Binder的Stub类,还有ILibraryManager.aidl中接口声明的方法。Stub类中关键部分都做了注释,其实它的核心就是将客户端进程中发起的跨进程方法调用转换到服务端进程去执行,最终得到执行结果!所以直观的看,在 Service 中Binder是作为跨进程通信的桥梁存在的,在此基础上,我们才能更好的理解使用 AIDL 背后的原理!

四、Messenger

Messenger 是一种轻量级的多进程通信方式,它是在 AIDL 的基础上封装而成的,可以看做是 AIDL 的简化版,支持一对多的串行实时通信,一次只处理一个请求,不存在并发的问题。和 AIDL 的使用类似,但要简单的多,同样需要实现服务端和客户端。

首先来看服务端,功能就是接收客户端发送的消息,同时回复一条消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  MessengerService  extends  Service {     private  static  final  String  TAG =  "MessengerService" ;     // 将Messenger和Handler关联起来
     private  Messenger mServiceMessenger =  new  Messenger( new  MessengerHandler());     public  MessengerService() {
     }    @Override
     public  IBinder onBind(Intent intent) {         return  mServiceMessenger.getBinder();
     }     private  static  class  MessengerHandler  extends  Handler {        @Override
         public  void  handleMessage(Message msg) {             super .handleMessage(msg);             switch  (msg.what) {                 case  MessengerActivity.MESSAGE_FROM_CLIENT:                     // 打印接收到的客户端消息
                     Log.e(TAG,  "receive message from client:"  + msg.getData().getString( "msg" ));                     // 给客户端回复一条消息
                     Messenger clientMessenger = msg.replyTo;
                     Message message = Message.obtain();
                     message.what = MessengerActivity.MESSAGE_FROM_SERVICE;
                     Bundle bundle =  new  Bundle();
                     bundle.putString( "msg" "I am fine,thank you!" );
                     message.setData(bundle);                     try  {
                         clientMessenger.send(message);
                     catch  (RemoteException e) {
                         e.printStackTrace();
                     }                     break ;
             }
         }
     }
}

首先创建一个Handler对象,并在其handleMessage()中进行消息的接收和回复,注意回复消息是通过客户端传递过来的Messenger对象发送一个Message对象,有了Handler对象后,需要把它和服务端创建的Messenger关联起来:

1
Messenger mServiceMessenger =  new  Messenger( new  MessengerHandler());

并在onBind()中返回服务端Messenger包含的Binder对象。

下来看客户端的实现,和服务端连接成功后,给服务端发送一条消息,并接收服务端回复的消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public  class  MessengerActivity  extends  AppCompatActivity {     private  static  final  String  TAG =  "MessengerActivity" ;     public  static  final  int  MESSAGE_FROM_CLIENT =  1 ;     public  static  final  int  MESSAGE_FROM_SERVICE =  2 ;     private  Messenger mServiceMessenger;     private  Messenger mClientMessenger =  new  Messenger( new  MessengerHandler());     private  ServiceConnection mServiceConnection =  new  ServiceConnection() {        @Override
         public  void  onServiceConnected(ComponentName name, IBinder service) {
             mServiceMessenger =  new  Messenger(service);
             Message message = Message.obtain();
             message.what = MESSAGE_FROM_CLIENT;
             Bundle bundle =  new  Bundle();
             bundle.putString( "msg" "how are you?" );
             message.setData(bundle);             // 传递服务端回复客户端时需要使用的Messenger
             message.replyTo = mClientMessenger;             try  {
                 mServiceMessenger.send(message);
             catch  (RemoteException e) {
                 e.printStackTrace();
             }
         }        @Override
         public  void  onServiceDisconnected(ComponentName name) {
         }
     };    @Override
     protected  void  onCreate(Bundle savedInstanceState) {         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_messenger);
         Intent intent =  new  Intent(MessengerActivity. this , MessengerService. class );
         bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }    @Override
     protected  void  onDestroy() {
         unbindService(mServiceConnection);         super .onDestroy();
     }     private  static  class  MessengerHandler  extends  Handler {        @Override
         public  void  handleMessage(Message msg) {             super .handleMessage(msg);             switch  (msg.what) {                 case  MessengerActivity.MESSAGE_FROM_SERVICE:
                     Log.e(TAG,  "receive message from service:"  + msg.getData().getString( "msg" ));                     break ;
             }
         }
     }
}

在onServiceConnected()中,将服务端的Binder转换成服务端的Messenger对象,然后发送消息,由于服务端还需要给客服端回复消息,所以需要在客户端创建一个Messenger对象附加在消息上发送给服务端使用。

在Messenger中也可以进行权限校验、服务端终止重新连接的操作,实现了 AIDL 的类似。

最后看下效果:

服务端

客户端

五、小结

文中内容参考了《Android 开发艺术探索》,希望对你我有所帮助吧!
源码地址:https://github.com/Othershe/IPCTest

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值