IPC 简介
IPC 是 Inter-Process Communication 的缩写,意思是跨进程通信,是指两个进程之间进行数据交换的过程。
在 Android 中,默认情况下一个 App 只有一个进程,但是可以通过在 AndroidMenifest.xml 中给四大组件(Activity、Service、Receiver、ContentProvider)指定 android:process 属性来开启多进程。
<activity
android:name="com.example.test.MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.example.test.SecondActivity"
android:configChanges="screenLayout"
android:process=":remote" />
<activity
android:name="com.example.test.ThirdActivity"
android:configChanges="screenLayout"
android:process="com.example.test.remote"/>
上面的示例分别为 SecondActivity 和 ThirdActivity 指定了 process 属性,并且它们的属性值不同,这意味着当前应用又增加了两个新进程。假设当前应用的包名为 “com.example.test”,当 SecondActivity 启动时,系统会为它创建一个单独的进程,进程名为 “com.example.test:remote”;当 ThirdActivity 启动时,系统也会为它创建一个单独的进程,进程名为“com.example.test.remote”。同时入口 Activity 是 MainActivity,没有为它指定 process 属性,那么它运行在默认进程中,默认进程的进程名是包名。
在 Android Studio 中选择 Attach Debugger to Android Process 可以看到这3个进程:
你还可以用 shell 命令来查看进程信息,命令为:ps -A | grep com.example.test,如下图:
你有没有注意到,SecondActivity 和 ThirdActivity 的 android:process 属性分别为 “:remote” 和 “com.example.test.remote”,那么这两种方式有区别吗?其实是有区别的,区别有两方面:首先,“:” 的含义是指要在当前的进程名前面附加上当前的包名,“:” 是简写,对于SecondActivity 来说,它完整的进程名为 “com.example.test:remote”,这一点通过上图中的进程信息也能看出来,而对于 ThirdActivity 中的声明方式,它是一种完整的命名方式,不会附加包名信息;其次,进程名以 “:” 开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而不以 “:” 开头的进程属于全局进程,其他应用通过 ShareUID 方式可以和它跑在同一个进程中。
我们知道 Android 系统会为每个应用分配一个唯一的 UID,具有相同 UID 的应用才能共享数据。这里要说明的是,两个应用通过 ShareUID 跑在同一个进程中是有要求的,需要这两个应用有相同的 ShareUID 并且签名相同才可以。在这种情况下,它们可以互相访问对方的私有数据,比如 data 目录、组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享 data 目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。
多进程导致的问题
这样看来开启多进程是不是很简单,直接指定 android:process 属性就行了,但是多进程会有一些其他的问题,比如新建了一个名为 UserManager 的类,里面有一个静态成员变量 sUserId ,如下所示:
public class UserManager{
public static int sUserId = 1;
}
如果我们在 MainActivity 中把 sUserId 赋值为 2,然后在 SecondActivity 中打印 sUserId 的值,发现还是 1。上述问题出现的原因是 SecondActivity 运行在一个单独的进程中,我们知道 Android 为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。也就是说两个进程中都存在 UserManager 类,并且这两个类互不干扰。
一般来说,使用多进程会造成如下几方面的问题:
- 静态成员和单例模式完全失效。
- 线程同步机制完全失效。
- SharedPreferences 的可靠性下降。
- Application 会多次创建。
第 1 和第 2 个问题很好理解,第 3 个问题是因为 SharedPreferences 不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为 SharedPreferences 底层是通过读/写 XML 文件来实现的,并发写显然是可能出问题的,甚至并发读/写都有可能出问题。第4个问题也是显而易见的,当一个组件跑在一个新的进程中的时候,由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统又把这个应用重新启动了一遍,既然重新启动了,那么自然会创建新的 Application。这个问题其实可以这么理解,运行在同一个进程中的组件是属于同一个虚拟机和同一个 Application 的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和 Application 的。为了更加清晰地展示这一点,下面我们来做一个测试,在 Application 的 onCreate() 方法中打印出当前进程的名字,代码如下:
public class MyApplication extends Application {
private static final String TAG ="MyApplication";
@Override
public void onCreate(){
super.onCreate();
String processName = MyUtils.getProcessName(getApplicationContext(), android.os.Process.myPid());
Log.d(TAG, "application start, process name:"+ processName);
}
}
public class MyUtils {
public static String getProcessName(Context cotext, int pid){
ActivityManager activityManager = (ActivityManager) cotext.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()) {
if (appProcessInfo.pid == pid) {
return appProcessInfo.processName;
}
}
return "";
}
}
顺序启动 MainActivity、SecondActivity、ThirdActivity,Log 打印如下:
MyApplication com.example.test D application start, process name:com.example.test
MyApplication com.example.test D application start, process name:com.example.test:remote
MyApplication com.example.test D application start, process name:com.example.test.remote
可以看到,Application的 onCreate() 方法执行了三次并打印出了三次进程名,这也就证实了在多进程模式中,不同进程的组件的确会拥有独立的虚拟机、Application 以及内存空间,这会给实际的开发带来很多困扰,是尤其需要注意的。
Binder
从 IPC 的角度来说,Binder 是 Android 中的一种跨进程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在 Linux 中没有;从 Android Framework 的角度来说,Binder 是 ServiceManager 连接各种 Manager (ActivityManager、WindowManager 等等)和相应 ManagerService 的桥梁;从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 的服务。
为了分析 Binder 的工作机制,我们新建一个 AIDL 示例。
在 main 目录右键 New > AIDL > AIDL Folder,操作完后 main 目录下会自动新建一个 aidl 目录,包名为 com.example.binderclient。在这里添加 2 个文件:Apple.aidl 和 IAppleManager.aidl,代码如下:
// Apple.aidl
package com.example.binderclient;
parcelable Apple;
// IAppleManager.aidl
package com.example.binderclient;
import com.example.binderclient.Apple;
interface IAppleManager {
List<Apple> getAppleList();
void addApple(in Apple apple);
}
在普通目录下添加 Apple.java 类,代码如下:
// Apple.java
package com.example.binderclient;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class Apple implements Parcelable {
private String name;
private String loc;
public Apple(String name, String loc) {
this.name = name;
this.loc = loc;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(loc);
}
public static final Creator<Apple> CREATOR = new Creator<Apple>() {
@Override
public Apple createFromParcel(Parcel in) {
return new Apple(in);
}
@Override
public Apple[] newArray(int size) {
return new Apple[size];
}
};
protected Apple(Parcel in) {
name = in.readString();
loc = in.readString();
}
}
上面三个文件中,Apple.java 是一个表示苹果信息的类,它实现了 Parcelable 接口。Apple.aidl 是 Apple 类在 AIDL 中的声明。IAppleManager.aidl 是我们定义的一个接口,里面有两个方法:getAppleList() 和 addApple(),其中 getAppleList() 用于从远程服务端获取苹果列表,而 addApple() 用于往苹果列表中添加一个苹果。可以看到,尽管 Apple 类已经和 IAppleManager 位于相同的包中,但是在 IAppleManager 中仍然要导入 Apple 类,这就是 AIDL 的特殊之处。下面我们先看一下系统为 IAppleManager.aidl 生产的 Binder 类,make 项目后,在 build 目录下可以找到该类: IAppleManager.java:
下面我们根据这个系统生成的 Binder 类来分析 Binder 的工作原理。
在 Binder 服务端,我们需要新建 IAppleManager.Stub 实例,实现里面的方法,并在 onBind() 方法中返回该实例,代码如下:
public class AppleService extends Service {
...
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IAppleManager.Stub mBinder = new IAppleManager.Stub() {
@Override
public List<Apple> getAppleList() throws RemoteException {
return mAppleList;
}
@Override
public void addApple(Apple apple) throws RemoteException {
if(!mAppleList.contains(apple)){
mAppleList.add(apple);
}
}
};
}
其中的 Stub 是 IAppleManager 的一个抽象静态内部类,继承自 Binder 并实现了 IAppleManager 接口,代码如下:
public interface IAppleManager extends android.os.IInterface
{
public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IAppleManager
{
// 构造函数,调用 Binder 的 attachInterface() 方法
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
// 将 IBinder 对象转成 com.example.binderserver.IAppleManager 接口,
// 如果需要会生成一个代理对象
public static com.example.binderserver.IAppleManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
// 使用 queryLocalInterface() 方法获取 IBinder 接口的本地实现
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.binderserver.IAppleManager))) {
return ((com.example.binderserver.IAppleManager)iin);
}
// 返回 IAppleManager.Stub.Proxy 对象
return new com.example.binderserver.IAppleManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder()
{
// 此方法用于返回当前 Binder 对象
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
// 这里后面再分析
...
}
static final int TRANSACTION_getAppleList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addApple = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
// Binder 的唯一标识,一般用当前 Binder 的类名表示
public static final java.lang.String DESCRIPTOR = "com.example.binderserver.IAppleManager";
public java.util.List<com.example.binderserver.Apple> getAppleList() throws android.os.RemoteException;
public void addApple(com.example.binderserver.Apple apple) throws android.os.RemoteException;
}
客户端需要调用 bindService() 方法绑定服务端的 Service,代码如下:
public class MainActivity extends AppCompatActivity {
private IAppleManager mAppleManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("client", "onServiceConnected");
mAppleManager = IAppleManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("client", "onServiceDisconnected");
mAppleManager = null;
}
};
@Override
public void onClick(View v) {
if(v.getId() == R.id.btn_bind){
...
// 绑定服务
boolean result = bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
...
}else if (v.getId() == R.id.btn_add) {
// 添加苹果
...
if(mAppleManager != null){
mAppleManager.addApple(new Apple("红星", "山东"));
}
...
}else if(v.getId() == R.id.btn_show) {
// 获取苹果列表
if(mAppleManager != null){
List<Apple> appleList = mAppleManager.getAppleList();
...
}
}
}
}
在 ServiceConnection 的 onServiceConnected() 方法中会拿到服务端的 Binder 对象,并调用 IAppleManager.Stub 的 asInterface() 方法将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象。这个转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 IAppleManager.Stub.Proxy 对象。这里我们只分析跨进程的调用过程。
其中 Proxy 是 IAppleManager.Stub 的内部类,代码如下:
public interface IAppleManager extends android.os.IInterface
{
public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IAppleManager
{
private static class Proxy implements com.example.binderserver.IAppleManager
{
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.example.binderserver.Apple> getAppleList() throws android.os.RemoteException
{
// 创建输入型 Parcel 对象 _data
android.os.Parcel _data = android.os.Parcel.obtain();
// 创建输出型 Parcel 对象 _reply
android.os.Parcel _reply = android.os.Parcel.obtain();
// 返回值
java.util.List<com.example.binderserver.Apple> _result;
try {
// 将 IBinder Interface Token写入 _data
_data.writeInterfaceToken(DESCRIPTOR);
// 调用 transact() 方法来发起 RPC 请求,同时当前线程会挂起,
// 然后服务端的 onTransact() 方法会被调用,直到 RPC 过程返回后,当前线程继续执行
boolean _status = mRemote.transact(Stub.TRANSACTION_getAppleList, _data, _reply, 0);
_reply.readException();
// 从 _reply 中读取 RPC 过程的返回结果,赋值给 _result
_result = _reply.createTypedArrayList(com.example.binderserver.Apple.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addApple(com.example.binderserver.Apple apple) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_Parcel.writeTypedObject(_data, apple, 0);
boolean _status = mRemote.transact(Stub.TRANSACTION_addApple, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
}
}
Proxy 也实现了 IAppleManager 接口,并且 Proxy 继承了 Binder 类,在客户端 获取苹果列表 和 添加苹果 会分别调用 Proxy 的 getAppleList() 和 addApple() 方法。
getAppleList() 方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型 Parcel 对象 _data,输出型 Parcel 对象 _reply 和返回值 _result,然后把 IBinder Interface Token 写入 _data;接着调用 IBinder 的 transact() 方法来发起 RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的 onTransact() 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果,赋值给 _result;最后返回 _result 中的数据。addApple() 方法的执行过程和 getAppleList() 方法类似,就不分析了。
onTransact() 方法运行在服务端中的 Binder 线程池中,代码如下:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
data.enforceInterface(descriptor);
}
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
}
switch (code)
{
case TRANSACTION_getAppleList:
{
// 调用服务端的方法
java.util.List<com.example.binderserver.Apple> _result = this.getAppleList();
reply.writeNoException();
reply.writeTypedList(_result);
break;
}
case TRANSACTION_addApple:
{
com.example.binderserver.Apple _arg0;
_arg0 = _Parcel.readTypedObject(data, com.example.binderserver.Apple.CREATOR);
this.addApple(_arg0);
reply.writeNoException();
break;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
return true;
}
当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。仔细观察可以发现,服务端的 onTransact() 方法与客户端的 transact() 方法的参数是对应的。服务端通过 code 可以确定客户端所请求的目标方法是什么,然后执行目标方法。data 是客户端传递过来的参数,当目标方法执行完毕后,就向 reply 中写入返回值(如果目标方法有返回值的话)。flags 标明是否有返回值,0为有(双向),1为没有(单向)。需要注意的是,如果此方法返回 false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
有两点还是需要额外说明一下:
- 当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在 UI 线程中发起此远程请求。
- 由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
自己写一个Binder
其实我们完全可以不使用 AIDL 文件,自己写一个 Binder 出来,AIDL 文件的本质是系统为我们提供的一种快速实现 Binder 的工具而已。
我们先编写客户端的代码,把 IAppleManager 接口单独提取出来,代码如下:
public interface IAppleManager extends android.os.IInterface {
java.lang.String DESCRIPTOR = "com.example.binderclient.IAppleManager";
int TRANSACTION_getAppleList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
int TRANSACTION_addApple = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
List<Apple> getAppleList() throws android.os.RemoteException;
void addApple(Apple apple) throws android.os.RemoteException;
}
Stub 类的 asInterface() 方法只是根据是否在同一个进程中返回不同的对象,我们可以确定是跨进程通信,那么这里可以直接去掉。我们把 Proxy 类单独提取出来作为一个单独的类,并改名为:AppleManagerImpl,代码如下:
public class AppleManagerImpl implements IAppleManager{
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.example.binderclient.Apple> getAppleList() throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.binderclient.Apple> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(TRANSACTION_getAppleList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.binderclient.Apple.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addApple(com.example.binderclient.Apple apple) throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_Parcel.writeTypedObject(_data, apple, 0);
boolean _status = mRemote.transact(TRANSACTION_addApple, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
static class _Parcel {
static private <T> T readTypedObject(
android.os.Parcel parcel,
android.os.Parcelable.Creator<T> c) {
if (parcel.readInt() != 0) {
return c.createFromParcel(parcel);
} else {
return null;
}
}
static private <T extends android.os.Parcelable> void writeTypedObject(
android.os.Parcel parcel, T value, int parcelableFlags) {
if (value != null) {
parcel.writeInt(1);
value.writeToParcel(parcel, parcelableFlags);
} else {
parcel.writeInt(0);
}
}
}
}
这样在客户端的 onServiceConnected() 方法中直接 new AppleManagerImpl(service) 就可以了,代码如下:
ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAppleManager = new AppleManagerImpl(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mAppleManager = null;
}
};
然后一样可以在客户端调用其 addApple() 和 getAppleList() 方法。
在服务端,新建一个与客户端相同的 IAppleManager 接口,其实现类 AppleManagerImpl 与客户端不同,服务端的 AppleManagerImpl 需要继承 Binder 类,代码如下:
public abstract class AppleManagerImpl extends Binder implements IAppleManager {
@Override
public IBinder asBinder() {
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
data.enforceInterface(descriptor);
}
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
}
switch (code)
{
case TRANSACTION_getAppleList:
{
java.util.List<com.example.binderserver.Apple> _result = this.getAppleList();
reply.writeNoException();
reply.writeTypedList(_result);
break;
}
case TRANSACTION_addApple:
{
com.example.binderserver.Apple _arg0;
_arg0 = _Parcel.readTypedObject(data, com.example.binderserver.Apple.CREATOR);
this.addApple(_arg0);
reply.writeNoException();
break;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
return true;
}
static class _Parcel {
static private <T> T readTypedObject(
android.os.Parcel parcel,
android.os.Parcelable.Creator<T> c) {
if (parcel.readInt() != 0) {
return c.createFromParcel(parcel);
} else {
return null;
}
}
static private <T extends android.os.Parcelable> void writeTypedObject(
android.os.Parcel parcel, T value, int parcelableFlags) {
if (value != null) {
parcel.writeInt(1);
value.writeToParcel(parcel, parcelableFlags);
} else {
parcel.writeInt(0);
}
}
}
}
在服务端 new AppleManagerImpl() 就可以拿到 Binder 实例了:
private final AppleManagerImpl mBinder = new AppleManagerImpl() {
@Override
public List<Apple> getAppleList() throws RemoteException {
return mAppleList;
}
@Override
public void addApple(Apple apple) throws RemoteException {
if(!mAppleList.contains(apple)){
mAppleList.add(apple);
}
}
};
这样,不使用 AIDL 文件就实现了 Binder,手动去写的意义在于可以让我们更加理解 Binder 的工作原理。
Demo地址:github.com/EnzoXRay/Cu…