每一个APP都独立运行在自己的进程中,拥有独立的地址空间。因而进程之间的资源是不能共享的,所以每个系统都有自己的IPC(Inter-Process Communication,进程间通信)机制。
Android是基于Linux内核的移动操作系统,但它并没有继承Linux的IPC机制,而是有着自己的一套IPC机制,这个IPC机制就是使用AIDL来实现的,而android中的Binder就是Android中最具特色的IPC方式,AIDL就是通过Binder实现的。
那么,一个APP,什么情况下会开启多进程呢?
- 应用由于自身需要采用多进程模式来实现。比如播放器之类,由于service与Activity一样,都是运行在UI主线程中的,如果播放器后台播放功能仅仅在service中运行会影响主线程的响应速度,很可能会造成ANR,一般情况下不会这么写;如果仅仅在子线程中运行,一旦开启该线程的Activity被杀死后,线程也被杀死,无法实现后台运行效果,更加不合理。而如果在另外一个进程中使用service后台运行,就显得十分恰当了。
- 由于android对单个应用所使用的最大内存做了限制,为了加大一个应用可使用的内存,所以通过多进程来获取多份内存空间。
开启多线程的方法
android开启多线程的方法只有这一种:在manifest.xml文件中,为android四大组件配置android:process属性,如:
<service
android:name=".services.TestAIDLService"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.TestAIDLService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
开启新的进程之后,会在APP的进程列表中看到多了一个:remote的进程:
从Manifest以及进程名称中,我们可以看到,新开的进程的名称与APP主进程的名称前半部分相同,但多了:remote。由此可以知道新进程的名称已”:”开头,且属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中;而新进程名称又可以不带这个“:”,表示全局进程,其他应用可以通过某些方式和它跑在同一个进程中。当然,这个“remote”名字可以随意取。
接下来,就一步步看下android是如何实现自己的IPC的。
我创建两个APP来实现跨进程(APP)的通信。
第一个APP,我直接在我用来学习的工程中创建了,我们定义这个APP为服务端:
因为这个是服务端,所以提供跨进程的服务就设计在这个APP中;
第二个APP,我新建一个测试工程,定义这个APP为client:
从两张图中,可以看到,我们有相同包名,相同类名的,两个后缀名为aidl的文件,这个文件就是我们实现Android IPC的关键。
首先,我们在java的同级目录下创建如图的aidl目录,并在该目录下新建一个AIDL文件:
// IMyAidlInterface.aidl
package com.shangxiaom.commonlist;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
String getTestStr(String extra);
void testAidl();
double getTestNum();
}
创建该接口之后,build一下工程,studio会自动帮我们创建对应的Stub(下边会介绍)。
接下来,我们提供服务:
public class TestAIDLService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
@Override
public String getTestStr(String extra) throws RemoteException {
return "这是测试RPC代码!" + extra;
}
@Override
public void testAidl() throws RemoteException {
}
@Override
public double getTestNum() throws RemoteException {
return 1;
}
};
}
从服务的onBind方法,我们返回了一个IMyAidlInterface.Stub对象,打开Stub的源码:
public interface IMyAidlInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.shangxiaom.commonlist.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.shangxiaom.commonlist.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.shangxiaom.commonlist.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.shangxiaom.commonlist.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.shangxiaom.commonlist.IMyAidlInterface))) {
return ((com.shangxiaom.commonlist.IMyAidlInterface)iin);
}
return new com.shangxiaom.commonlist.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
从源码中看出,Stub属于IMyAidlInterface的内部静态类,并实现了我们定义的AIDL接口:
可以看到Stub中的常量,其中两个int常量是用来标识我们在接口中定义的方法的,DESCRIPTOR常量是 Binder的唯一标识。
asInterface 方法用于将服务端的Binder对象转换为客户端所需要的接口对象,该过程区分进程,如果进程一样,就返回服务端Stub对象本身,否则呢就返回封装后的Stub.Proxy对象。
adBinder()方法用于在onBind方法中返回Binder对象,但一般我们直接用Stub对象即可,因为它本身继承自Binder。
onTransact 方法是运行在服务端的Binder线程中的,当客户端发起远程请求后,在底层封装后会交由此方法来处理。通过code来区分客户端请求的方法,注意一点的是,如果该方法返回false的换,客户端的请求就会失败。一般可以用来做权限控制。
最后,看一下这个Proxy代理
private static class Proxy implements com.shangxiaom.commonlist.IMyAidlInterface {
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.lang.String getTestStr(java.lang.String extra) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(extra);
mRemote.transact(Stub.TRANSACTION_getTestStr, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void testAidl() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_testAidl, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public double getTestNum() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
double _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getTestNum, _data, _reply, 0);
_reply.readException();
_result = _reply.readDouble();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
Proxy类实现了我们定义的IMyAidlInterface接口方法。
代理类中的方法都是运行在客户端,当客户端发起远程请求时,_data会写入参数,然后执行mRemote.transact(Stub.TRANSACTION_getTestStr, _data, _reply, 0);
这个方法会发起RPC(远程过程调用)请求,并挂起当前线程,同时服务端的onTransact方法就会被 调起,直到RPC过程返回后,当前线程继续执行,并从_reply取出返回值(如果有的话),并返回结果。
最后在对应的Activity中,启动该服务
Intent startIntent = new Intent(this, TestAIDLService.class);
startService(startIntent);
别忘了在manifest.xml中配置服务,以及对应的action
<service
android:name=".services.TestAIDLService"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.TestAIDLService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
接下来,来实现第二个APP:
因为要保证两个APP中,AIDL接口的包名以及方法都相同,所以我们直接把第一个APP中的aidl目录整个拷贝到第二个APP的main目录下;
然后,在MainActivity中,获取AIDL接口对象,并绑定远程的服务:
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface mIMyAidlInterface;
private TextView mTextView;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
mTextView.setText(mIMyAidlInterface.getTestStr("com.shangxiaom.testaidlclient"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.test_aidl_textview);
Intent intent = new Intent();
intent.setAction("android.intent.action.TestAIDLService");
Intent eintent = new Intent(getTempIntent(this, intent));
bindService(eintent, mConnection, Context.BIND_AUTO_CREATE);
}
}
关于getTempIntent方法,请见
到此就实现了跨进程(APP)之间的通信,先运行第一个APP,启动对应的服务之后,再运行第二个APP,得到如下的结果:
完整的服务端代码见github:
client代码自己实现吧。。。