详解Android跨进程IPC通信AIDL机制原理

简介

AIDL:Android Interface Definition Language,即Android接口定义语言,用于生成Android不同进程间进行进程通信(IPC)的代码,一般情况下一个进程是无法访问另一个进程的内存的。如果某些情况下仍然需要跨进程访问内存数据,这时候Android系统就要将其对象分解成能够识别的原数据,编写这一组操作的代码是一项繁琐的工作,但是AIDL对底层进行了抽象的封装,简化了跨进程操作。

AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

在Android中跨进程操作的方式不止一种,四大组件中ContentProvider天生就是为跨进程操作而存在的,但是ContentProvider所谓的跨进程操作数据,这些数据不一定是存放在内存中的,如通讯录数据时存放在Sqlite数据库中的。AIDL支持的跨进程操作的数据是要存放在内存中的,AIDL底层实际上也是使用的Binder进行的跨进程操作,后续另起一篇博文继续介绍Binder的跨进程机制。

使用场景

只有不同应用之间需要进行IPC,并且想要在Service中处理多线程时,这种场景才有必要使用AIDL。如果仅仅需要跨进程但是不是跨应用,这时候应该通过Binder进行数据交互;另外如果仅仅是需要跨进程IPC,但是不需要处理多线程,这时候应该通过Messenger类进行数据交互。

定义AIDL接口

在Android Studio中使用AIDL的项目的目录结构跟eclipse中有很大差异,下图是使用AIDL的项目的目录结构。

在Android Studio中只需要在某个Module中使用右键菜单中new就会显示创建AIDL文件的菜单,当新建成功后AIDL文件位于工程的同java同一级的aidl目录文件夹下面。在 .aidl 文件中存放的就是AIDL接口。

定义.aidl文件

.aidl文件名称必须同接口名称保持一致,必须使用Java语言的语法定义AIDL文件。AIDL使用简单语法,通过可带参数和返回值的一个或多个方法来声明接口。参数和返回值可以是任意类型,甚至可以是其他 AIDL 生成的接口。每个.aidl文件都必须定义单个接口,并且只需包含接口声明和方法签名,也意味着在.aidl文件中接口名称和方法名称都不可以使用权限修饰符。

默认情况下,AIDL 支持下列数据类型:

  1. Java语言中所有的基本数据类型,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double;
  2. String和CharSequence类型;
  3. 集合List类型,List中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。可选择将 List用作泛型类型(例如,List )。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是List接口, 在AIDL中不可以使用ArrayList类型进行定义,只能使用List接口定义 ,否则会报unknown type编译错误。
  4. 集合Map类型,中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。不同于集合List接口, 在AIDL中不支持泛型Map(如 Map 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。类似List接口, 在.aidl文件中不能使用HashMap,只能使用Map接口 。
  5. 自定义类型必须实现Parcelable接口,并且在aidl文件夹下有对应类型的aidl文件;
  6. 非JDK中定义的类型,类似于Java语法,必须使用import进行引入。

定义AIDL接口时需要注意如下:

  1. 方法可带零个或多个参数,返回值或空值,但是方法名称不能相同;
  2. 所有非基本数据类型参数都需要指示数据走向的方向标记。可以是 in、out 或 inout。基本数据类型默认为 in,不能是其他方向;
  3. .aidl 文件中包括的所有代码注释都包含在生成的 IBinder 接口中(import 和 package 语句之前的注释除外);
  4. 只支持方法,不应在AIDL中定义静态字段。

如下是定义是IRemoteService.aidl:

package com.sunny.server;
import com.sunny.server.bean.User;
interface IRemoteService {
  void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
      double aDouble, String aString);
   String getName();
   void setListData(in List<String> inList,out List<String> outList);
   void setMapData(in Map map);
   void setUser(in User user);
}

下面是自定义的JavaBean类型User.aidl

package com.sunny.server.bean;
parcelable User;

User类在使用的时候必须实现Parcelable接口,代码这里就不再贴出来了。

Service实现AIDL接口

在定义的AIDL接口编译后实际上会生成一个跟.aidl同名的Java类文件,里面包含了所有的AIDL文件中声明的方法,并且包含了一个默认的实现类Stub,该类是抽象类,继承了Binder类实现了AIDL接口。在Stub类中有两个方法一个是asInterface()方法,该方法返回的是AIDL文件生成的接口,另外一个方法是asBinder(),该方法返回的是一个IBinder类型的实例。

asInterface()和asBinder()方法非常有用,asInterface()方法可以用于客户端的IPC方法调用,另外一个方法可以用于在服务端返回Binder实例,并在服务端实现响应的接口方法。

上面介绍过在定义非基本数据类型的时候必须定义数据走向,声明in或out或者inout,在AIDL生成的Java文件中就可以看出来究竟了,这里可以参看setListData()方法的生成实现。

@Override
public void setListData(java.util.List<java.lang.String> inList, java.util.List<java.lang.String> outList) 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.writeStringList(inList);
 mRemote.transact(Stub.TRANSACTION_setListData, _data, _reply, 0);
 _reply.readException();
 _reply.readStringList(outList);
 } finally {
 _reply.recycle();
 _data.recycle();
 }
}

如果声明数据时是in,在生成相对应的方法的时候调用的实际上是Parcel的writeXXX方法,如果声明的是out,在实现上面采用的是readXXX,所以在定义的时候一定要明确调用逻辑。

接下来看一下服务端中MyService类的实现。

public class MyService extends Service {
   private static final String TAG = "AIDL_Server";
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }

  private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
      Log.d(TAG, "anInt:" + anInt + " aLong:" + aLong + " aBoolean:" + aBoolean + " aFloat:" + aFloat + " aDouble:" + aDouble + " aString:" + aString);
    }

    @Override
    public String getName() throws RemoteException {
      return "admin";
    }

    @Override
    public void setListData(List<String> inList, List<String> outList) throws RemoteException {
      Log.d(TAG, "inList:" + inList.toString());
      setOutList(outList);
    }

    @Override
    public void setMapData(Map map) throws RemoteException {
      Log.d(TAG, "map:" + map.toString());
    }

    @Override
    public void setUser(User user) throws RemoteException {
      Log.d(TAG, "user:" + user.toString());
    }
  };

  private void setOutList(List<String> list) {
    list.add("out_01");
    list.add("out_02");
    list.add("out_03");
  }
}

在MyService类中,除了getName是一个有返回值的方法,其余的方法都是void类型的,另外在数据走向方面,除了setListData方法的第二个参数outList是输出类型的参数,其余的参数都是输入类型参数,所以这里将其它参数直接打印出来了。

调用IPC方法

在客户端想要调用Android的AIDL中定义的IPC方法,可以通过如下步骤实现:

  1. 首先需要定义一个相同包名相同目录的AIDL文件夹;
  2. 声明一个AIDL文件生成的接口实例;
  3. 实现ServiceConnection接口;
  4. 调用bindService绑定服务,传入生成的ServiceConnection实例;
  5. 在onServiceConnected()实现中,将收到的IBinder实例(名为 service)。调用 XXX.Stub.asInterface((IBinder)service),以将返回的参数转换为 AIDL生成的接口类型。
  6. 通过调用生成的AIDL接口实例中对应的方法就可以实现IPC调用了;
  7. 在不使用的时候解除服务的绑定Context.unbindService()。

如下是客户端Activity中代码的实现:

public class MainActivity extends AppCompatActivity {
  private static final String TAG = "AIDL_Client";
  private MyConnection conn;
  private IRemoteService service;
  private List<String> outList=new ArrayList<>();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  public void startBind(View v) {
    Intent intent = new Intent();
    conn=new MyConnection();
    intent.setAction("com.sunny.server.service.MyService");
    bindService(intent, conn, Context.BIND_AUTO_CREATE);
  }

  public void startExecute(View v) {
    try {
      service.basicTypes(1, 10000L, true, 1.5f, 300.3, "Hello World");

      Log.d(TAG, "getName:" + service.getName());
      List<String> inList = new ArrayList<String>();
      inList.add("inList01");
      inList.add("inList02");
      service.setListData(inList, outList);
      Log.d(TAG, "outList:" + outList.toString());
      Map<String, String> map = new HashMap<String, String>();
      map.put("key01", "value01");
      map.put("key02", "value02");
      service.setMapData(map);
      User user = new User();
      user.setId(1001);
      user.setName("admin");
      service.setUser(user);
    } catch (RemoteException e) {
      e.printStackTrace();
    }
  }
  private class MyConnection implements ServiceConnection {
    public void onServiceConnected(ComponentName name, IBinder binder) {
      service = IRemoteService.Stub.asInterface(binder);
    }
    public void onServiceDisconnected(ComponentName name) {
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    unbindService(conn);
  }
}

其它

上述示例只是为了介绍AIDL如何跨进程通信的,所以在客户端接收到的数据直接就在主线程中处理了。但是实际上客户端调用服务端的远程方法,被调用的方法运行在服务端的Binder线程池中的,同时客户端线程会被挂起,这时候如果服务端方法执行比较耗时,就会导致客户端长时间阻塞在这里,如果客户端方法位于UI线程中,可能会引起ANR。在实际开发的时候注意,客户端进行IPC通信的时候尽量放在子线程中。由于服务端的方法本身就是运行在服务端的Binder线程池中,所以即使服务端需要执行大量耗时的工作也不需要开启新的线程去执行。

另外一定要注意的就是安全性,默认情况下远程服务任何人都可以连接,这应该不是我们所需要的,所以还需要考虑一下权限验证。一般情况下有两种处理方法,第一种是通过自定义权限的方法,我们在服务端Service方法的onBinder()方法中添加权限验证,如果权限验证不通过直接返回null。另外一种就是在服务端的onTransact()方法中做验证,也是做权限验证,如果不通过直接返回false。除了上面讲的权限验证之外,可以通过getCallingPid()和getCallingUid()拿到客户端应用的Pid和Uid进行校验。

有关AIDL的介绍就先到这里了,后续继续介绍一下Binder有关内容。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android IPC进程间通信详解最新AndroidStudio的AIDL操作)

    前言 前面梳理了Android的线程间的通信<Thread.Handler和HandlerThread关系何在?> ,这些都是在同一个进程中,那进程间的通信,或者说不同的应用间的通信该如何实现呢?这个时候就要用到AIDL(Android Interface Definition LanguageAndroid接口定义语言 ). 使用方法(AndroidStudio) 我发现现在AIDL的教程基本上还是eclipse的,但是在AndroidStudio里面使用AIDL还是有一些不同的,来看看怎么

  • 详解Android跨进程IPC通信AIDL机制原理

    简介 AIDL:Android Interface Definition Language,即Android接口定义语言,用于生成Android不同进程间进行进程通信(IPC)的代码,一般情况下一个进程是无法访问另一个进程的内存的.如果某些情况下仍然需要跨进程访问内存数据,这时候Android系统就要将其对象分解成能够识别的原数据,编写这一组操作的代码是一项繁琐的工作,但是AIDL对底层进行了抽象的封装,简化了跨进程操作. AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量

  • 详解Android跨进程通信之AIDL

    需求描述 进程A调起第三方进程B进行第三方登录 – 实现双向通信 代码(进程A) 1.目录结构 2.LoginActivity.java public class LoginActivity extends AppCompatActivity { private ILoginInterface iLogin; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceSta

  • 详解Android Scroller与computeScroll的调用机制关系

    Android ViewGroup中的Scroller与computeScroll的有什么关系? 答:没有直接的关系 知道了答案,是不是意味着下文就没必要看了,如果说对ViewGroup自定义控件不感兴趣,可以不用看了. 1.Scroller到底是什么? 答:Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算. 无论从构造方法还是其他方法,以及Scroller的属性可知,其并不会持有View,辅助ViewGrou

  • 详解Android系统中的root权限获得原理

    前言 一直很好奇Android Root的原理,恰好最近碰到了一个跟Android默认带Root权限的问题,这里顺便记录一下Android系统root的原理. 原理 Android是基于Llinux内核的开源操作系统,与Ubuntu系统类似,所以在Android里获取root权限其实和在Linux系统下获取root权限是一回事.在Linux系统下获取root权限的方法是在命令行执行sudo或者su,接下来输入提权密码就可以获取root权限了.Android系统其实也是这样,例如应用层程序开发,在

  • Android 跨进程SharedPreferences异常详解

    Android 跨进程SharedPreferences异常详解 Context c = null; try { c = context.createPackageContext(PREFERENCE_PACKAGE, Context.CONTEXT_IGNORE_SECURITY); } catch (NameNotFoundException e) { e.printStackTrace(); } if (c != null) { SharedPreferences infoSp = c.g

  • 详解Android aidl的使用方法

    AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写(对于小白来说,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互.) AIDL只是Android中众多进程间通讯方式中的一种方式, AIDL和Messenger的区别: Messenger不适用大量并发的请求:Messenger以串行的方式来处

  • 详解Android中Service AIDL的使用

    目录 前言 Service基本用法--本地服务 远程服务 -- AIDL 服务端 客户端 前言 有些朋友可能是从事开发工作的时间不是特别的长,所以觉得Service相对与另外两个组件activity.broadcast receiver来说,使用可能并不是特别的多,所以对Service来说,理解不是特别的深入,只是有一个大概的概念,今天就和一块来走一下Service,希望能够帮助到大家对Service有更深入的理解. Service基本用法--本地服务 我们知道服务分为本地服务和远程服务,而本地

  • 详解Android 基于TCP和UDP协议的Socket通信

    本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知识点 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是"请求-响应方式",即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据. 而Socket通信中基于TCP/IP协议的通信则是在双方建立起连接后就可以直接进行数

  • 详解Android 进程

    多进程 如果需要的时候,app可以创建多进程. 在进程里面 各类组件元素的清单文件条目 . . 和 - 均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行. 默认进程就是主进程.其他进程一般来说都是子进程. 2个activity在不同的进程里面,可以刷新UI吗? <activity android:name=".androidsample.ActivityProgressB" android:process=":progressb&quo

  • 详解Android Ashmem匿名共享内存

    目录 1. 简述 2. 创建 MemoryFile 和 数据写入 3. 将文件描述符传递到其他进程 4. 在其他进程接收 FileDescriptor 并读取数据 1. 简述 Android 的 匿名共享内存(Ashmem) 基于 Linux 的共享内存,都是在临时文件系统(tmpfs)上创建虚拟文件,再映射到不同的进程.它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制.相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁.Java 层

随机推荐