Android进程间通信的方式有很多种,例如:文件通信、共享内存、Binder、Socket、管道、信号等等
作为Android开发人员要想深入的了解Android的通信机制,那就不得不了解Binder。在Android中Binder又有多种表现形式,例如:ContentProvider、Messenger、AIDL、Token等,今天的重点是AIDL。AIDL也是Android C\S架构中做常用的进程间通信方式。
内容概要:
- 应用间使用AIDL通信
- 同一个应用中多进程间的AIDL通信
- AIDL原理分析(Android的AMS与客户端使用AIDL通信的应用场景)
- 遇到的异常
在看本文前,对于AIDL传递数据类型不熟悉的同学可以先看看这两篇文章(两篇文章都差不多)
https://www.cnblogs.com/0616--ataozhijia/p/4952441.html
https://www.cnblogs.com/android-er/p/5477071.html
应用间使用AIDL通信
在示例中来认识AIDL的工作原理:
- 新建一个Android工程
- 新建一个本地服务LocalService和一个远程服务RemoteService
- 本地服务与远程服务间通过AIDL通信
先写一个使用AIDL在两个不同的应用之间通信的demo:
- 先创建两个Android Module,分别为PluginApp(作为服务端)和ClientApp(作为客户端)
- 在PluginApp中新建RemoteService,然后再新建aidl文件IAidlInterface.aidl,并且在同一个目录下新建自定义类型User和User.aidl类型申明文件
- 在ClientApp中将PluginApp下的aidl目录拷贝到ClientApp下
目录结构如下:
RemoteApp.png
示例代码链接: https://pan.baidu.com/s/185Ocv5NgMDlAyYhT7Xu5dg 密码: kn64
同一个应用中多进程间的AIDL通信
同一个应用中的多进程通信实际上也是采用AIDL的方式,跟上边Demo的使用方式差不多
-
应用开启多进程的方式
很简单,在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性。
<service
android:name=".RemoteService"
android:process=":remote">
</service>
<activity
android:name=".RemoteActivity"
android:process="com.chenxf.ipc.remote">
</activity>
有两种声明方式,一个加冒号,一个完整的名字,区别如下:
-
:remote: 以冒号开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.chenxf.ipc:remote,同时以冒号开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。
-
com.chenxf.ipc.remote:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。(一般极少这样用,除非是同一公司开发的app,且2个app关联很大,才会签名也一样)
-
多进程的优缺点
优点
- 增加内存。
- 业务隔离。一些子业务,放子进程,如果崩溃了,不会影响主app退出。
缺点
- 静态成员和单例模式失效
- 线程同步机制失效
- SharedPreferences 可靠性降低
- Application 被多次创建
1, 2 很容易理解,每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间。可以认为,每个进程,都有独立的静态成员和单例模式的对象,所以进程之间,千万不能通过这些通信,因为它们属于不同的时空喔。
3嘛,如果一个读,一个写,还好,要是同时去写,就可能出问题了,A进程刚写1,B进程又写2,A一脸懵逼,为啥变成2了,你说可靠不可靠。
4很重要,指的是,Application会被重复创建。比如,如果有3个进程,Application会初始化3次。如果希望不同进程做不同的初始化,则可以参考如下的实现:
package com.chenxf.processtest;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
public class MyApplication extends Application {
private static final String TAG = "MyApplication";
private static final String DOWNLOADER_PROCESS = ":downloader";
private static final String PLUGIN_PROCESS = ":plugin";
private BaseApplication mProxy;
@Override
public void onCreate() {
super.onCreate();
String processName = getMyProcessName();
Log.i(TAG, "onCreate " + processName);
initProxyApplication(processName);
}
private void initProxyApplication(String processName) {
String mPackageName = getPackageName();
if (TextUtils.equals(mPackageName, processName)) {
//主进程
Log.i(TAG, "init process " + mPackageName);
mProxy = new MainApplication(processName);
} else if (TextUtils.equals(processName, mPackageName + PLUGIN_PROCESS)) {
//插件安装进程
Log.i(TAG, "init process " + PLUGIN_PROCESS);
mProxy = new PluginApplication(processName);
} else if (TextUtils.equals(processName, mPackageName + DOWNLOADER_PROCESS)) {
//下载进程
Log.i(TAG, "init process " + DOWNLOADER_PROCESS);
mProxy = new DownloaderApplication(processName);
} else {
mProxy = new BaseApplication(processName);
}
}
/**
* 获取进程的名称
*
* @return
*/
public String getMyProcessName() {
if (mProxy != null) {
return mProxy.getProcessName();
} else {
return initCurrentProcessName(this);
}
}
private String initCurrentProcessName(Context context) {
int pid = android.os.Process.myPid();
ActivityManager manager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
if (process.pid == pid) {
return process.processName;
}
}
return null;
}
}
AIDL原理分析
先来看一下.aidl文件,这个文件是Android studio生成的:
// 这是本地IPC存根类,集成Binder类,实现本地IAidlInterface接口
public static abstract class Stub extends android.os.Binder implements com.pluginapp.IAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.pluginapp.IAidlInterface";
// 在关联接口时构造存根
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
// 将IBinder对象强制转换为com.pluginapp.IAidlInterface接口,
// 如果需要,生成代理。
public static com.pluginapp.IAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.pluginapp.IAidlInterface))) {
return ((com.pluginapp.IAidlInterface)iin);
}
return new com.pluginapp.IAidlInterface.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_setUser:
{
data.enforceInterface(descriptor);
com.pluginapp.User _arg0;
if ((0!=data.readInt())) {
_arg0 = com.pluginapp.User.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.setUser(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getUser:
{
data.enforceInterface(descriptor);
com.pluginapp.User _result = this.getUser();
reply.writeNoException();
if ((_result!=null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.pluginapp.IAidlInterface
{
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 void setUser(com.pluginapp.User user) 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 ((user!=null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_setUser, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setUser(user);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public com.pluginapp.User getUser() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.pluginapp.User _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getUser();
}
_reply.readException();
if ((0!=_reply.readInt())) {
_result = com.pluginapp.User.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
// AIDL支持的参数类型
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.pluginapp.IAidlInterface sDefaultImpl;
}
最重要的是四个部分,Proxy,Stub和asInterface,还有连接远程服务的接口实现ServiceConnection。
首先看看Proxy的实现,首先是将远程的Binder作为参数传入进来,远程Binder传入是在绑定链接远程服务的时候的IBinder对象分两步:
- 第一步: 链接远程服务,通过回调中获取IBinder对象
private IAidlInterface mIAidlInterface;
/**
* 链接服务的回调
*/
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtil.logE(true, TAG + "远程服务链接上了");
mIAidlInterface = IAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtil.logE(true, TAG + "远程服务断开了");
}
};
- 第二部:在获取本地服务接口对象的时候,将链接服务的回调接口中的IBinder作为参数传递给远程Binder服务在本地的代理对象Proxy,IAidlInterface.Stub.asInterface(service);
public static com.pluginapp.IAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
// 1. 根据AIDL唯一描述符查询本地的AIDL接口
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
// 2. 判断查询到的AIDL服务接口是不是本地进程的服务
if (((iin!=null)&&(iin instanceof com.pluginapp.IAidlInterface))) {
// 3. 如果是当前进程的本地服务,那么就返回服务本身
return ((com.pluginapp.IAidlInterface)iin);
}
// 4. 如果不是当前进程的本地服务,而是远程服务,那么就返回远程服务的本地代理对象
return new com.pluginapp.IAidlInterface.Stub.Proxy(obj);
}
Stub实际上是远程服务在本地进程的存根,是远程服务在本地服务操作远程服务的真正代理者,继承自Binder,而Binder实现IBinder接口,在Binder IPC通信中只要是实现了IBinder接口的对象都具备跨进程通信的能力。Proxy是本地操作远程服务的接口实现,持有外部传入的远程服务的mRemote对象(IBinder对象),通过mRemote对象调用transact方法,写入参数,阻塞等待读取结果。
boolean _status = mRemote.transact(Stub.TRANSACTION_setUser, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
_result = com.pluginapp.User.CREATOR.createFromParcel(_reply);
}
transact方法的实现在远程服务端,在远程服务端实现具体的逻辑,回调Stub中的onTransact方法,读取参数,写入结果返回结果。从这个过程来看,AIDL也是一个同步阻塞的耗时操作。
其实在Android系统中使用AIDL实现进程间通信的场景很多,例如:AMS与客户端进程的通信就是使用的AIDL的方式
遇到的异常
在使用自定义类型的时候可能会遇到类型包找不到的异常,如下:
Error.png
解决方式:在Module Gradle下添加如下代码,申明资源代码的位置,然后Sync Project即可:
sourceSets{
main{
jniLibs.srcDir(['libs'])
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/aidl']
resources.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl','src/main/java/com/pluginapp/bean']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
网友评论