今天这篇博客,我将会深入学习android中的IPC多进程之间的通信机制。
Android中的多进程模式
在android中,我们可以通过给四大组件指定”android:process”属性,就可以开启多进程模式了。
开启多进程模式
在android中开启多进程,可以给android中的四大组件在AndroidManifest.xml中指定”android:process”属性。
上面的代码,我们分别为SecondActivity和ThirdActivity指定了不同的进程。当SecondActivity和ThirdActivity启动的时候,系统会为他们分别创建一个不同的进程,我们可以通过下面的命令来查看当前的进程:
adb shell ps | grep -n “相关进程的包名”
可以看到上面我们分别使用了”:remote”和”com.example.myapplication.remote”来指定当前的进程名字,其实这两种指定方式还有有区别的:
- “:”这种方式,系统会自动在其前面加上当前的包名。
- 进程名称以”:”开头的属于当前应用的私有进程,其他应用组件不能和它运行在同一个进程中。而不以”:”开头的进程,属于全局进程,其他应用可以通过SharedUID和它运行在同一个进程中。
android多进程需要注意的地方
可以看到,我们可以通过在AndroidManifest.xml中指定”android:process”属性就可以轻松的开启多进程模式,但是,多进程还有很多我们预料不到的结果,先看看下面的栗子:
- 我们先创建一个MyStaticData类,包含一个静态变量。
- 在MainActivity中改变MyStaticData类中静态变量的值。
- 在SecondActivity中获取该静态变量的值。
public class MyStaticData {
public static int count = 0;
}
可以看到,虽然静态变量是可以内存共享的,可是由于MainActivity和SecondActivity是运行在不同的进程中的,所以是运行在不同的虚拟机的。
一般情况下,我们使用多进程,可能会遇到下面问题:
- 静态成员和単例模式失效
- 线程同步机制失效
- SharedPreference不是很可靠
- Application会多次创建
下面我们创建一个 MyApplication类,来验证这样的问题
public class MyApplication extends Application {
private String TAG = MyApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
String processName = "";
int pid = android.os.Process.myPid();
ActivityManager manager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo process: manager.getRunningAppProcesses()) {
if(process.pid == pid)
{
processName = process.processName;
}
}
Log.d(TAG,"CREATE MyApplication, processName is :"+processName);
}
}
另外需要配置我们自定义的application
<application
android:name=".MyApplication">
</application
可以看到,当三个Activity都启动的时候,会分别调用 的oncreate方法。
IPC基础学习
Binder
Binder是android中的一个类,它实现了IBinder接口,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。
下面为了更深入了解Binder的学习,我们新建一个demo。
Person.java
public class Person implements Parcelable {
private int personId;
private String personName;
private String personPass;
private int personAge;
public int getPersonId() {
return personId;
}
public void setPersonId(int personId) {
this.personId = personId;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public String getPersonPass() {
return personPass;
}
public void setPersonPass(String personPass) {
this.personPass = personPass;
}
public int getPersonAge() {
return personAge;
}
public void setPersonAge(int personAge) {
this.personAge = personAge;
}
public Person(int personId, String personName, String personPass,
int personAge) {
this.personId = personId;
this.personName = personName;
this.personPass = personPass;
this.personAge = personAge;
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int arg1) {
// TODO Auto-generated method stub
dest.writeInt(personId);
dest.writeString(personName);
dest.writeString(personPass);
dest.writeInt(personAge);
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel arg0) {
// TODO Auto-generated method stub
Person person = new Person(arg0.readInt(),arg0.readString(),arg0.readString(),arg0.readInt());
return person;
}
@Override
public Person[] newArray(int arg0) {
// TODO Auto-generated method stub
return new Person[arg0];
}
};
}
Person.aidl
parcelable Person;
IBookManager.aidl
package com.example.aidlservice;
import java.util.List;
import com.example.aidlservice.Person;
interface IPerson
{
List<Person> getAllPerson();
Person getPersonById(in int personId);
}
此时系统会自动为我们生成IPerson.java
package com.example.aidlservice;
public interface IPerson extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.aidlservice.IPerson
{
private static final java.lang.String DESCRIPTOR = "com.example.aidlservice.IPerson";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidlservice.IPerson interface,
* generating a proxy if needed.
*/
public static com.example.aidlservice.IPerson asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.aidlservice.IPerson))) {
return ((com.example.aidlservice.IPerson)iin);
}
return new com.example.aidlservice.IPerson.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@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_getAllPerson:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<Person> _result = this.getAllPerson();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_getPersonById:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
Person _result = this.getPersonById(_arg0);
reply.writeNoException();
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.aidlservice.IPerson
{
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<Person> getAllPerson() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getAllPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(Person.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public Person getPersonById(int personId) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
Person _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(personId);
mRemote.transact(Stub.TRANSACTION_getPersonById, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
_result = Person.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getAllPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getPersonById = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<Person> getAllPerson() throws android.os.RemoteException;
public Person getPersonById(int personId) throws android.os.RemoteException;
}
系统自动为我们生成了Iperson.java,下面我们对其内部属性做必要解释:
- DESCRIPTOR
当前Binder的唯一标识,一般是当前的全类名 asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,如果客户端和服务端是在同一个进程中,则此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。asBinder()
返回当前Binder对象- onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
这个方法运行咋服务端的Binder线程池中,当客户端发起跨进程请求时,最终会有该方法来进行处理,需要注意的是,如果该方法返回的是false,那么客户端的请求就会失败,因此我们可以使用这个特性来做权限验证。
需要注意的是:
1.如果需要传递自定义的类型:需要实现Parcelable接口
2.服务端定义的service必须在Manifest.xml文件中声明
3.如果需要传递自定义类型,还需要将自定义类型声明为aidl文件
4.在服务端声明的aidl文件,在客户端必须要有一份相同的文件存在,并且,包名必须相同。这是因为aidl文件就相当于调用的接口,包名必须相同,通过ServiceConnection绑定该接口,然后,通过该aidl生成的接口调用对应的服务端的方法。
可以看到系统根据IBookManager.aidl为我们生成了IBookManager.java类,它继承自IInterface接口,同时它还声明了两个整型id用于标识不同的方法,接着还有一个Stub的内部类,这个Stub也是一个Binder类,当客户端和服务端都位于同一个进程的时候,会返回一个本地的Binder对象,当两者位于不同进程的时候,服务端最终调用的方法有Stub类的内部代理类Proxy来完成。
Binder有两个很重要的方法:
public void linkToDeath(DeathRecipient recipient, int flags) {
}
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return true;
}
由于Binder是运行在服务端进程的,如果服务端进程由于某种原因被异常终止,此时称之为Binder死亡,此时客户端的调用则会收到影响,好在Binder中提供了两个配对的linkToDeath和unlinkToDeath方法,通过linkToDeath方法,我们可以给Binder设置一个死亡代理,当Binder死亡的时候,我们就会收到通知,这个时候就可以重新发起连接请求从而恢复连接。
设置死亡代理
- 声明一个 对象, 是一个接口,其内部有一个 方法,我们需要实现这个方法,当Binder死亡的时候,系统就会回调 方法,我们可以在该方法中移除之前的绑定的binder,并重新绑定到远程服务。
其次,需要在客户端绑定成功以后,给Binder设置死亡代理:
其中 的第二个参数是一个标记为。另外我们也可以通过Binder的isBinderAlive来判断当前Binder是否死亡,我们可以在客户端里设置该代理:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (iPerson == null) {
return;
}
iPerson.asBinder().unlinkToDeath(mDeathRecipient, 0);
iPerson = null;
//重新绑定远程服务
//......
Log.d(TAG,"haha remote service has died");
Intent intent2 = new Intent();
intent2.setAction("com.action.action.myPerson");
bindService(intent2, conn2, Service.BIND_AUTO_CREATE);
}
};
private ServiceConnection conn2 = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
Log.d(TAG,"haha onServiceDisconnected");
iPerson = null;
}
@Override
public void onServiceConnected(ComponentName arg0, IBinder service) {
Log.d(TAG,"haha onServiceConnected");
iPerson = IPerson.Stub.asInterface(service);
try {
service.linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
此时,如果我们手动将远程服务进程杀掉,会看到在客户端可以监听到服务端死亡的行为,并且可以重新绑定服务。
另外我们还可以在onServiceDisconnected方法中重新连接远程服务,区别在于onServiceDisconnected是在客户端的UI线程中被回调,而binderDied是在客户端的线程池中被回调的。即在binderDied方法中不能访问UI.
不能再服务端执行耗时操作
客户端调用远程服务端方法,被调用的方法运行在服务端的Binder线程池中,同时客户端也会被挂起,这个时候如果服务端执行比较耗时的话,就会导致客户端长时间的阻塞,如果此时是客户端的UI线程,则很有可能会出现ANR
我们修改服务端的方法,为耗时方法
public List<Person> getAllPerson() throws RemoteException {
SystemClock.sleep(90000);
return lists;
}
在客户端不断调用该方法,则会出现ANR
为了避免这种ANR出现,我们可以开启一个新的线程,将调用耗时的操作放到该线程中去执行即可。
AIDL权限验证
默认情况下,我们的远程服务是所有人都可以连接的, 但这绝不是我们想要看到的。所以需要添加权限验证的功能,验证失败则无法调用,常用的有两种方法:
- 在onBind方法中进行验证,验证失败,则直接返回null。比如添加permission来验证
<permission android:name="com.test.permission.aidl"
android:protectionLevel="normal"
>
</permission>
@Override
public IBinder onBind(Intent arg0) {
int check = checkCallingOrSelfPermission("com.test.permission.aidl");
Log.d("testaidl","check is :"+check+" PERMISSION_DENIED is :"+PackageManager.PERMISSION_DENIED);
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return personBinder;
}
此时一个应用来绑定我们的服务时候,会验证这个应用的权限,如果它没有使用这个权限,在onBind方法中会直接返回null。如果客户端需要绑定到该权限,则需要使用上面定义的权限
<uses-permission android:name="com.test.permission.aidl"/>
2.我们可以在服务端的onTransact方法中进行验证,如果验证失败直接返回false,这样服务端就不会执行AIDL中的方法。
使用Messenger实现进程间通信
Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以方便的进行进程间通信。实现Messenger通信:
服务端进程
我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind方法中返回这个Messenger对象底层的Binder即可。客户端进程
客户端进程需要首先绑定服务端的service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息,类型为Messenger对象。
服务端
在服务端创建一个service用来处理客户端的连接请求。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private Messenger mMessenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:// from client
Log.d(TAG,"receive msg from client :"+msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
});
@Override
public IBinder onBind(Intent arg0) {
return mMessenger.getBinder();
}
}
注册该service
<service android:name="com.example.aidlservice.MessengerService">
<intent-filter>
<action android:name="com.action.action.messenger"/>
</intent-filter>
</service>
客户端
private Messenger mService;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
mService = null;
}
@Override
public void onServiceConnected(ComponentName arg0, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","hello i am client");
msg.setData(data);// 发送数据到服务端
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
// 绑定服务端service
Intent intent3 = new Intent();
intent3.setAction("com.action.action.messenger");
bindService(intent3, conn, Service.BIND_AUTO_CREATE);
此时,先运行我们的服务端进程,然后运行客户端进程,在客户端进程中,当绑定服务成功以后,则客户端会发送消息给到服务端。
上面代码虽然实现了客户端发送消息给到服务端,可是如果服务端想要回复消息给到客户端,其实代码和客户端发送时一样的。
messenger双向通信服务端
messenger双向通信客户端
为了接受服务端的消息,客户端同样需要准备一个接收消息的Messenger和Handler
需要注意的是,当客户端发送消息的时候,需要把接收服务端回复的Messenger通过message.replyTo参数传递给到服务端。
此时先运行服务端,在运行客户端,可以看到,当客户端绑定服务成功以后,会发送一条消息给到服务端,当服务端接收到这条消息,会返回一条消息在到客户端。
Binder连接池
到现在为止,我们已经能够很熟练的使用AIDL来实现进程之间的通信了,可是如果业务模块很多的话,如果按照这种方式,就需要很多个AIDL文件,创建很多个service,我们需要减少service的数量,将所有的AIDL放在同一个Service中去管理。
从上图可以看出,在这种模式下,每个业务模块创建自己的AIDL接口并实现此接口,此时不同业务模块之间不能有耦合性,所有的实现细节都要单独开来,然后想服务端提供自己的唯一标示和其对应的Binder对象。
对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder给他们,不同业务模块拿到锁需要的Binder对象后就可以进行远程方法调用了。
Binder线程池的实现
下面我们创建两个业务模块,即创建两个不同的AIDL文件。
ICat.aidl
package com.example.aidlservice;
interface ICat
{
String getColor();
double getWeight();
}
IPerson.aidl
package com.example.aidlservice;
import java.util.List;
import com.example.aidlservice.Person;
interface IPerson
{
List<Person> getAllPerson();
Person getPersonById(in int personId);
}
为了演示问题,我们的服务端这两个业务模块的实现也比较简单
public class IpersonImpl extends com.example.aidlservice.IPerson.Stub{
private List<Person>lists = new ArrayList<Person>();
public IpersonImpl() {
for (int i = 0; i < 4; i++) {
lists.add(new Person(i,"names","pass",i));
}
}
@Override
public List<Person> getAllPerson() throws RemoteException {
return lists;
}
@Override
public Person getPersonById(int personId) throws RemoteException {
for (Person per : lists) {
if (per.getPersonId() == personId) {
return per;
}
}
return null;
}
}
public class IcatImpl extends com.example.aidlservice.ICat.Stub {
@Override
public String getColor() throws RemoteException {
return "red";
}
@Override
public double getWeight() throws RemoteException {
return Math.random() * 10;
}
}
到现在为止,我们各个业务模块的AIDL接口定义和实现都已经完成了,注意这里并没有为每个模块的AIDL单独创建Service
为Binder连接池创建AIDL接口IBinderPool.aidl
package com.example.aidlservice;
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
为Binder连接池创建远程Service并实现IBinderPool,下面是queryBinder的具体实现。
public class BinderPoolService extends Service {
private Binder mBinder = new BinderPoolManager();
public static class BinderPoolManager extends IBinderPool.Stub {
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BinderPool.BINDER_CAT:
binder = new IcatImpl();
break;
case BinderPool.BINDER_PERSON:
binder = new IpersonImpl();
break;
}
return binder;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
可以看到这里我们绑定服务的时候是根据传入的参数来返回不同的服务端代理的。注意在AndroidManifest.xml中声明该服务
<service android:name="com.example.aidlservice.BinderPoolService"
android:process=":remote">
</service>
这里我们创建一个BinderPool类,用来绑定和监听当前连接的状态:
public class BinderPool {
public static final int BINDER_CAT = 0;
public static final int BINDER_PERSON = 1;
private Context context = null;
private IBinderPool mBinderPool = null;
/**
* CountDownLatch
* 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。
* 由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
* 这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
*/
private CountDownLatch mConnectBinderPoolCountDownLatch = null;
private static BinderPool sInstance = null;
private BinderPool(Context ctx) {
this.context = ctx.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInstance(Context ctx) {
synchronized (BinderPool.class) {
if(sInstance == null) {
sInstance = new BinderPool(ctx);
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent intent = new Intent(context, BinderPoolService.class);
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);//这里为mBinderPool赋值为BinderPoolService
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IBinder.DeathRecipient mBinderPoolRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
mBinderPool.asBinder().unlinkToDeath(mBinderPoolRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
public IBinder queryBinder(int binderCode) {
if(mBinderPool != null && mBinderPool.asBinder().isBinderAlive()) {
try {
return mBinderPool.queryBinder(binderCode);
} catch (RemoteException e) {
e.printStackTrace();
}
}
return null;
}
}
这里我们通过CountDownLatch将bindService异步操作转换成了同步操作,这就意味着它可能是好事的,binder方法调用也可能是耗时的,因此不建议放在主线程中去执行。
客户端调用
private ICat catService;
private IPerson iPerson;
new Thread(new Runnable() {
@Override
public void run() {
IBinder binder = BinderPool.getInstance(MainActivity.this).queryBinder(BinderPool.BINDER_CAT);
ICat cat= ICat.Stub.asInterface(binder);
catService = ICat.Stub.asInterface(binder);
try {
catService.getColor();
} catch (RemoteException e) {
e.printStackTrace();
}
binder = BinderPool.getInstance(MainActivity.this).queryBinder(BinderPool.BINDER_PERSON);
IPerson person = IPerson.Stub.asInterface(binder);
iPerson = IPerson.Stub.asInterface(binder);
try {
iPerson.getAllPerson();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
好了,关于android中的AIDL和Messenger进程间通信就先到这里了,算是自己的一个学习记录吧。