AIDL基础介绍

前言

在android一个应用通常对应一个进程,通常为了保护数据的安全性,2个不同的进程间是无非直接通信(互相传递数据)的,但是android就为了保证2个进程间的通信找了个第三个做桥梁——AIDL。
__
AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似。 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。 在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。
:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不**需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。__

以上是Google官方文档给出的介绍,地址请点击这里
总结上面提到的AIDL,Binder和Messenger的IPC方式如下:
AIDL:IPC方式,多个应用之间通信,而且服务中需要处理多线程。
Binder:IPC方式,多个应用之间通信,但是不需要处理多线程。
Messenger:IPC方式,单个应用,且不需要处理多线程。

AIDL的使用步骤

为了方便模拟AIDL,我们假设我们有一个应用需要计算两个数相加之和,那么假设有2个进程(或者2个应用),一个叫服务端(Server),它里面已经实现两个整数相加的方法,那么在客户端中我们不需要再重新做这个方法了,而且调用服务端的方法,通过AIDL可以实现上述的过程,下图是过程的描述。

图1、实例程序图
1,创建aidl文件
图2、AndroidStudio中新建AIDL

首先在Android Studio中创建一个module作为服务端,然后在src的目录下右键-->选择New-->选择Folder-->AIDL Folder;然后会弹出一个选择aidl文件是否放在默认的目录下的对话框,我们直接点击Finish即可,我们会发现AndroidStudio下,AIDL的目录会放在java和res同级的目录下:


图3、AIDL的目录位置
2,定义aidl,并且编译成java

在AIDL目录上右键-->New-->AIDL-->AIDL File;然后在aidl目录会看到多了一个以包名命名的子目录,并且多了一个IMathAidl.aidl的文件,我们可以在这个文件下定义上述所说的两个整数相加的方法:

// IMathAidl.aidl
package com.example.server;

// Declare any non-default types here with import statements

interface IMathAidl {
   /**
   *  计算两个整数相加之和
   */
   int add(int params1,int params2);
   /**
    * Demonstrates some basic types that you can use as parameters
    * and return values in AIDL.
    */
   void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
           double aDouble, String aString);
}

上面的IMathAidl中add方法就是我定义的方法,下面的basicTypes是系统自动生成的,先不管它。在aidl文件定义方法就跟在Java中定义方法差不多一样的,但是很值得注意的是aidl中的方法返回值和参数值的类型有如下规定:
1,在默认情况下,aidl参数只支持基本数据类型,如 int、long、char、boolean 等等,还有String、CharSequence。
2,使用对象时,必须继承Parcelable接口。但是使用时,必须在文件中声明import。
3,使用List和Map时,内部支持的数据类型也必须满足基本数据类型或者其包装类、对象等。
4,在使用非基本数据类型时,如Person、List<Person>等,都需要指示数据走向的方向标记,用 in、out 或 inout修饰,分别输入、输出、输入输出;都需要指示数据走向的方向标记。
定义好上述的aidl的文件后,别忘记编译一下,因为默认情况下,aidl不会自动编译的,手动编译后我们会在build相关目录下发现了一个aidl.java的文件

图4、aidl生成的java文件
3,创建远程服务,实现aidl的接口

我们已经创建好了aidl文件并且也编译成了Java类,那么我们知道aidl远程调用其实是通过绑定服务的方式做到的,那么我们这里就需要在服务端里建立一个服务类RemoteService ,在这个服务里实现aidl的stub类,完成功能代码的编写,如下所示:

public class RemoteService extends Service {
   @Nullable
   @Override
   public IBinder onBind(Intent intent) {
       return mBinder;
   }

   private IBinder mBinder = new IMathAidl.Stub() {
       @Override
       public int add(int params1, int params2) throws RemoteException {
           //计算
           Log.i("AIDL", "params1 = " + params1 + " , params2 = " + params2);
           return params1 + params2;
       }

       @Override
       public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

       }
   };
}

通过上述代码的分析,我们首先new了一个IMathAidl.Stub类的对象,其实它是一个IBinder,此时在onBind的生命回调里返回值也是IBinder,那么将这个mBinder通过绑定的时候返回,这样就能在客户端与服务端建立连接的时候,客户端能得到服务端的IBinder对象,从而能调用aidl中方法。

4,客户端的建立
图5,客户端UI

好,客户端的界面如上所示很简单,不贴代码了。我们知道,两个进程(应用)通信,需要建立一种规则,必须同时遵守才能进行下去,那么上面我们在服务端通过aidl定义好了这种规则,如果客户端需要通信,最好的方式就是直接拿服务端定义好的通信协议,换句话说,我们保证客户端能够调用服务端的方法,就应该新建一个跟服务端一样的aidl文件
,所以我将服务端中的IMathAidl直接拿到客户端中使用,使用步骤都是一样的,新建AIDL目录,新建.aidl文件,编译等。
注意:客户端新建的AIDL下面的包名一定要跟服务端的包名是一样的,譬如上述的服务端aidl的包名是com.example.server,那么客户端的包名也得是这个,否则会报错:

java.lang.SecurityException: Binder invocation to an incorrect interface...
5,客户端与服务端建立连接

首先我们需要绑定远程的服务,通过Intent,然后通过ServiceConnection对象获取到远程服务的IBinder对象,最后调用IBinder里的方法:

public class MainActivity extends AppCompatActivity {

   private EditText mEditText1;
   private EditText mEditText2;
   private EditText mEditText3;
   private Button mButton;

   private IMathAidl mService;

   private ServiceConnection mConn = new ServiceConnection() {
       /**
        * 服务连接上了调用
        * @param name
        * @param service
        */
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           //获取远程服务
           mService = IMathAidl.Stub.asInterface(service);
       }

       /**
        * 服务断开了调用
        * @param name
        */
       @Override
       public void onServiceDisconnected(ComponentName name) {
           //回收资源
           mService = null;
       }
   };

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

       mEditText1 = (EditText) findViewById(R.id.editText1);
       mEditText2 = (EditText) findViewById(R.id.editText2);
       mEditText3 = (EditText) findViewById(R.id.editText3);
       mButton = (Button) findViewById(R.id.button);

       //进入客户端就绑定远程服务
       bindService();

       mButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               int params1 = Integer.parseInt(mEditText1.getText().toString().trim());
               int params2 = Integer.parseInt(mEditText2.getText().toString().trim());

               try {
                   //调用远程服务的add方法
                   int result = mService.add(params1, params2);
                   mEditText3.setText(result + "");
               } catch (RemoteException e) {
                   e.printStackTrace();
                   mEditText3.setText("出错了");
               }
           }
       });
   }

   /**
    * 绑定远程服务
    */
   private void bindService() {
       Intent intent = new Intent();
       intent.setComponent(new ComponentName("com.example.server", "com.example.server.RemoteService"));
       bindService(intent, mConn, Context.BIND_AUTO_CREATE);
   }


}

值得注意的是,我们必须要在服务端的AndroidMainifest.xml里为服务设置如下属性:

<service
     android:name=".RemoteService"
     android:exported="true"
     android:process=":remote" />

否则在客户端绑定远程服务建立连接时,获取到的远程AIDL的binder为空,就无法调用远程服务中的方法了,报错信息如下:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.aidlapplication/com.example.server.MainActivity}: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.example.server/.RemoteService }
     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
     at android.app.ActivityThread.-wrap11(ActivityThread.java)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
     at android.os.Handler.dispatchMessage(Handler.java:102)
     at android.os.Looper.loop(Looper.java:148)
     at android.app.ActivityThread.main(ActivityThread.java:5417)
     at java.lang.reflect.Method.invoke(Native Method)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.example.server/.RemoteService }
     at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1322)
     at android.app.ContextImpl.bindService(ContextImpl.java:1286)
     at android.content.ContextWrapper.bindService(ContextWrapper.java:604)
     at com.example.server.MainActivity.bindService(MainActivity.java:84)
     at com.example.server.MainActivity.onCreate(MainActivity.java:58)
     at android.app.Activity.performCreate(Activity.java:6237)
     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) 
     at android.app.ActivityThread.-wrap11(ActivityThread.java) 
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
     at android.os.Handler.dispatchMessage(Handler.java:102) 
     at android.os.Looper.loop(Looper.java:148) 
     at android.app.ActivityThread.main(ActivityThread.java:5417) 
     at java.lang.reflect.Method.invoke(Native Method) 
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

下面是实例程序的结果:

图6、计算结果

并且在服务端的服务日志里打印如下:


图7、服务日志

说明该计算过程确实是调用了服务端的add方法完成的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容