使用Bundle
四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的。
所以我们在一个进程中启动了另外一个进程的Activity、Service、Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
Intent intent = new Intent(BundleFirstActivity.this, BundleSecondActivity.class);
intent.putExtra("User", new User("haha", 222));
startActivity(intent);
特殊场景:
当A进程计算出来了一个结果,需要传输给B进程,但是传输的数据不支持放入Bundle中,因此无法通过Intent来传输。这个时候我们可以通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台计算,计算完成后再启动B进程中真要需要的目标组件。
使用文件共享
共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。
需要注意的是并发情况下的读/写,可能造成数据错乱。
可能有人会想到SharedPreferences,但是SharedPreferences是个特例。从本质上来说,SharedPreferences也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一个SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,SharedPreferences有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences。
每个应用的SharedPreferences文件都可以在当前包所在的data目录下查看到。一般来说,它的目录位于/data/data/package name/share_prefs目录下。
使用Messenger
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。
由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。正是因为Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适 了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了。
使用AIDL(Android Interface Defined Language(安卓接口定义语言))
如上所说Messenger无法完成跨进程调用服务端的方法,而且不能处理并发请求。但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。
1.服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
2.客户端
首先需要绑定服务端的Service,然后将服务端返回的Binder对象转成AIDL接口所属类型,最后调用AIDL中的方法。
3.AIDL接口的创建
默认情况下,AIDL 支持下列数据类型:
Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
String
CharSequence
List
List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
Map
Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
Parcelable
所有实现了Parcelable接口的对象。
注意: 如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
// Book.aidl
package com.whyalwaysmea.ipc;
parcelable Book;
4.远程服务端Service的实现
public class BookManagerService extends Service {
// CopyOnWriteArrayList支持并发读/写
// 因为AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形
// 所以我们要在AIDL方法中处理线程同步
private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
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();
}}
5.客户端的实现
首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。
public class BookManagerActivity extends AppCompatActivity {
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List bookList = bookManager.getBookList();
System.out.println("book list type : " + bookList.getClass().getCanonicalName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@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();
}}
可能有人会发现,在Service中,我们返回的数据类型是CopyOnWriteArrayList,但是到了Activity中,接收到了却是List。这是为什么呢?
AIDL中能够使用的List只有ArrayList,但是我们这里却使用了CopyOnWriteArrayList(注意它并不是继承自ArrayList)。这是因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
RemoteCallbackList
RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。
注意
客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞,如果这个客户端线程是UI线程的话,就会导致客户端ANR。所以,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程的耗时方法。
同理,当远程服务端需要调用客户端的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。所以,我们同样不可以在服务端中调用客户端的耗时方法。
Binder是可能意外死亡的,由于服务端进程意外停止了,这时我们需要重新连接服务。
给Binder设备DeathRecipient监听
在onServiceDisconnected中重连远程服务。
两者的区别是:onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调。
权限验证
默认情况下,我们的远程服务任何人都可以连接,但这以你哥哥不是我们愿意看到的,所以我们必须加入权限验证功能。
1.在onBind中进行验证,验证不通过就直接返回null。比如使用permission验证
android:name="com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
public IBinder onbind(Intent intent) {
int check = checkCallingOrSelfPermission("com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
如果我们自己的应用想绑定到服务上,只需要在它的AndroidMenifest文件中采用如下方式使用permission
2.我们可以在服务端的onTransact方法中进行权限验证,如果验证失败就返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护客户端的效果。这里可以验证的方式很多,比如permission,Uid,Pid等验证。
使用ContentProviderContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,它的底层实现同样也是Binder。
系统预置了许多ContentProvider,比如通讯录、日程表等,要跨进程访问这些信息,只需要通过ContentProvider的query、update、insert和delete方法即可。
关于自定义ContentProvider的过程:
1.创建一个自定义的ContentProvider,名字就叫BookProvider,只需要继承ContentProvider类并实现六个抽象方法即可:onCreate、query、update、insert、delete和geType。除了onCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中。
2.注册ContentProvider。
// ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider
android:authorities="com.whyalwaysmea.ipc.provider.BookProvider"
android:name=".provider.BookProvider"
android:permission="com.whyalwaysmea.provider" // 权限
android:process=":provider"/>
3.通过外部应用访问BookProvider。
注意
ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;
ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;
ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;
要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者;
query,update,insert,delete四大方法是存在多线程并发访问的,因此方法内部要做好线程同步。
使用Socket
Binder连接池当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。
整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。
选择合适的IPC方式
选择合适的IPC方式
















