AIDL

主讲人:梅静

前言

如何解决跨进程间的通信(两个应用之间进行数据通信)?

Binder是神马?

说明:

AIDL的定义

特点

优点:实现多应用之间进行跨进程通信  

缺点:耗内存

开发流程

语法规则

特别注意

## 如何实现AIDL?下面我们根据几个例子来看一下(基于Android Studio开发)

#### 实例1:有两个应用应用A(客户端),应用B(服务端)。应用B用于当获取到客户端传过来的数据后进行数据计算并将结果返回给客户端,提供远程计算功能;应用A用于输入数据,并将数据传给应用B,获取返回值进行界面更新,提供原始数据,并显示功能。

1.创建一个项目,之后右键项目的main文件夹,出现菜单选择Aidl选项,创建Aidl文件(类名要和文件名相同)。

说明:在新建的文件中,会有自动生成的一个接口方法,这个就是向我们展示了他支持基本数据类型,当然它也支持其他的类型,将在后续说明。

	    /**
	     * 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);

2.我们将默认提供的方法删除,实现我们自己的接口方法

     //计算两个数据的和
    int   add(int num1,int num2);

3.因为Android Studio对Aidl不能实现自动编译功能,所以写好之后aidl接口之后,需要将项目重新编译一下。编译之后我可以在项目的app\build\generated\source\aidl文件夹下查看到aidl文件所对应的java文件,它类似与R文件,我们无需创建,系统将自动为我们创建,从而使我们可以调用。

4.当我们写好aidl之后,我们需要实现其接口。那么如何让客户端去访问呢?那我们就需要Service的IBinder,因为当客户端绑定这个服务之后IBinder才会被执行,此时客户端则可以访问服务端了。

5.我们需要将实现aidl接口的服务在清单文件注册。

        <service android:name=".ICalculateService"
            android:enabled="true"
            android:exported="true">
        </service>

说明:注册该服务一定要设置这两个属性,否则客户端运行时将绑定不到服务,从而可能出现异常。

    android:enabled="true"  设置该服务能否被实例化 默认值为  true  true:可以被实例化  false:不允许被实例化

    android:exported="true" 设置该服务能否被其他应用组件调用或进行交互,其默认值依赖于该服务的包含的过滤器。若没有过滤器,则该服务只能被应用程序内部调用 此时默认值为false;若包含过滤器,则该服务可以被外部程序所调用。

1.创建一个新的项目,实现其界面展示。

2.将服务端的aidl包拷贝到客户端中来,注意包名,类名,文件名一定要与服务端的完全一致。

3.因为Android Studio对Aidl不能实现自动编译功能,所以写好之后aidl接口之后,需要将项目重新编译一下。编译之后我可以在项目的app\build\generated\source\aidl文件夹下查看到aidl文件所对应的java文件,它类似与R文件,我们无需创建,系统将自动为我们创建,从而使我们可以调用。(与服务端一样)

4.客户端绑定服务端的服务。

### 注意: Android5.0以后,android不允许通过隐示视图启动服务,必须通过显示意图才可启动服务。

		    private String TAG="AidlCalculate";
		    private CalculateAidl mCalculateAidl;
		    private ServiceConnection connectionm=new ServiceConnection() {
		
		        /**
		         * 当服务连接后调用
		         * @param componentName
		         * @param iBinder
		         */
		        @Override
		        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
		            Toast.makeText(AidlCommunicateToCalculateActivity.this,"连接到远程服务",Toast.LENGTH_SHORT);
		            Log.i(TAG,"连接到远程服务");
		            mCalculateAidl= CalculateAidl.Stub.asInterface(iBinder);
		            //在客户端绑定远程服务之后,给Binder设置死亡代理:
		            try {
		                iBinder.linkToDeath(mDeathRecipient, 0);
		            } catch (RemoteException e) {
		                e.printStackTrace();
		                Log.i(TAG,"给Binder设置死亡代理失败:"+e.getMessage());
		            }
		        }
		
		        /**
		         * 当服务端开后实现
		         * @param componentName
		         */
		        @Override
		        public void onServiceDisconnected(ComponentName componentName) {
		            Toast.makeText(AidlCommunicateToCalculateActivity.this,"与远程服务断开连接",Toast.LENGTH_SHORT);
		            Log.i(TAG,"与远程服务断开连接");
		            //资源回收
		            mCalculateAidl=null;
		
		        }
		    };
		
		   /*
		   * 当远程服务挂掉后调用
		   *
		   */
		    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
		        @Override
		        public void binderDied() {
		            // TODO: 这里重新绑定远程Service。
		            if(mCalculateAidl == null)
		                return;
		            mCalculateAidl.asBinder().unlinkToDeath(mDeathRecipient, 0);
		            mCalculateAidl = null;
		
		        }
		    };


		    /**
		     * 绑定服务
		     * 采用显示意图调用   需要  包名    包名+类名
		     */
		    private void bindService() {
		        Intent intent=new Intent();
		        intent.setComponent(new ComponentName("com.project.aidltocalculate","com.project.aidltocalculate.ICalculateService"));
		        bindService(intent,connectionm, Service.BIND_AUTO_CREATE);
		        Log.i(TAG,"绑定服务");
		    }
		
		
		    @Override
		    protected void onDestroy() {
		        super.onDestroy();
		        unbindService(connectionm);
		    }

5.传递数据给服务端,并获取返回结果,更新界面。


	   @Override
	    public void onClick(View view) {
	        switch (view.getId()) {
	            case R.id.btn_add:
	                 String result1=etNum1.getText().toString();
	                 String result2=etNum2.getText().toString();
	                if(TextUtils.isEmpty(result1)){
	                    result1="0";
	                }
	                if(TextUtils.isEmpty(result2)){
	                    result2="0";
	                }
	                 int num1=Integer.valueOf(result1);
	                 int num2=Integer.valueOf(result2);
	                try {
	                   if(mCalculateAidl!=null) {
	                       int data = mCalculateAidl.add(num1, num2);
	                       tvData.setText("数据之和:" + data);
	                   }else {
	                       Toast.makeText(MainActivity.this," 与远程服务断开连接",Toast.LENGTH_SHORT);
	                   }
	                } catch (RemoteException e) {
	                    Log.i(TAG,"没有连接到服务:"+e.getMessage());
	                    Toast.makeText(MainActivity.this,"没找到该服务",Toast.LENGTH_SHORT);
	                }

6.运行服务端和客户端,进行测试

服务端:

客户端

说明:

1.客户端访问服务端的前提是服务端应用要在客户端应用之前启用,否则将无法连接到服务;同时客户端能够调用服务端的方法,是因为客户端和服务端带运行中,一旦服务端被系统kill后,客户端将与其失去连接,从而无法服务访问器数据;

2.Binder运行在服务端进程,如果服务端进程由于某些原因异常终止,这个时候我们到服务端的Binder连接断裂,会导致我们的远程调用失败。Binder提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

### 说明: 1.Aidl只支持方法,不能定义静态成员;对于其他数据类型需要导包</br>            2.对于基本数据类型,aidl不支持short类型。</br>            3.对于集合支持List的集合,不支持Map集合,必须要指明是输入端(in输入型参数 )/输出端(ou输出型参数)/inout:输入输出型参数</br>            4.支持实体数据,但是该实体必须实现Parcelable接口,不支持Serializable,必须要指明是输入端(in输入型参数 )/输出端(ou输出型参数)/inout:输入输出型参数</br>           5. 对于List集合,必须要指明其实输入端还是输出端,因为当客户端将集合数据传递给服务端的过程实际上传递给系统底层,系统底层对于数据操作是最基本的,所以需要将数据拆分,这个过程叫打包,当系统根据客户端端的请求找到服务端将数据给服务端是,需要将数据组合成原始数据,这个过程叫拆包、为了保证不过多小号内存,我们必须指明数据的是输入端(in输入型参数 )/输出端(ou输出型参数)/inout:输入输出型参数,否则会报错。

错误写法:

			// BaseDataAidlInterface.aidl
			package com.project.aidltocalculate;
			
			// Declare any non-default types here with import statements
			
			interface BaseDataAidlInterface {
			    /**
			     * 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,List<String> list);
			}

报错异常如下图:

正确写法:in List list 标识这个是输入端

		// BaseDataAidlInterface.aidl
			package com.project.aidltocalculate;
			
			// Declare any non-default types here with import statements
			
			interface BaseDataAidlInterface {
			    /**
			     * 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,in List<String> list);

			}

#### 实例2:有两个应用应用A(客户端),应用B(服务端)。应用B用于当获取到客户端传过来的数据后进行组合后将结果返回给客户端,提供数据重新组合功能;应用A用于将数据传给应用B,获取返回值进行界面更新,提供原始数据,并显示功能。(基于实例1)

2.在Aidl文件加载下新建一个与实体类同名的aidl文件,用于向aidl描述该对象是实体类并实现了Parcelable接口

3.在Aidl文件夹下新建一个aidl的接口文件,注意对于list集合和实体数据必须指明参数输入输出类型,否则无法编译通过

4.对项目进行编译,之后实现其接口实现服务。

5.在清单文件中注册该服务

        <service android:name=".IDataTypeService"
            android:enabled="true"
            android:exported="true">
        </service>

1.创建一个新的项目,实现其界面展示。

2.将服务端的aidl包拷贝到客户端中来,注意包名,类名,文件名一定要与服务端的完全一致。

3.因为Android Studio对Aidl不能实现自动编译功能,所以写好之后aidl接口之后,需要将项目重新编译一下。编译之后我可以在项目的app\build\generated\source\aidl文件夹下查看到aidl文件所对应的java文件,它类似与R文件,我们无需创建,系统将自动为我们创建,从而使我们可以调用。(与服务端一样)

4.客户端绑定服务端的服务。


			     private String TAG="DataType";
			    private BaseDataAidlInterface mCalculateAidl;
			    private ServiceConnection connectionm=new ServiceConnection() {
			
			        /**
			         * 当服务连接后调用
			         * @param componentName
			         * @param iBinder
			         */
			        @Override
			        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
			            Toast.makeText(AidlCommunicateToDataTypeActivity.this,"连接到远程服务",Toast.LENGTH_SHORT);
			            Log.i(TAG,"连接到远程服务");
			            mCalculateAidl= BaseDataAidlInterface.Stub.asInterface(iBinder);
			            //在客户端绑定远程服务之后,给Binder设置死亡代理:
			            try {
			                iBinder.linkToDeath(mDeathRecipient, 0);
			            } catch (RemoteException e) {
			                e.printStackTrace();
			                Log.i(TAG,"给Binder设置死亡代理失败:"+e.getMessage());
			            }
			
			        }
			
			        /**
			         * 当服务端开后实现
			         * @param componentName
			         */
			        @Override
			        public void onServiceDisconnected(ComponentName componentName) {
			            Toast.makeText(AidlCommunicateToDataTypeActivity.this,"与远程服务断开连接",Toast.LENGTH_SHORT);
			            Log.i(TAG,"与远程服务断开连接");
			            //资源回收
			            mCalculateAidl=null;
			
			        }
			    };
			
			    /*
			     * 当远程服务挂掉后调用
			     *
			      */
			    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
			        @Override
			        public void binderDied() {
			            // TODO: 这里重新绑定远程Service。
			            if(mCalculateAidl == null)
			                return;
			            mCalculateAidl.asBinder().unlinkToDeath(mDeathRecipient, 0);
			            mCalculateAidl = null;
			
			        }
			    };

		
		    /**
		     * 绑定服务
		     * 采用显示意图调用   需要  包名    包名+类名
		     */
		    private void bindService() {
		        Intent intent=new Intent();
		        intent.setComponent(new ComponentName("com.project.aidltocalculate","com.project.aidltocalculate.IDataTypeService"));
		        bindService(intent,connectionm, Service.BIND_AUTO_CREATE);
		        Log.i(TAG,"绑定服务");
		    }
		
		
		    @Override
		    protected void onDestroy() {
		        super.onDestroy();
		        unbindService(connectionm);
		    }

** 5.传递数据给服务端,并获取返回结果,更新界面。


	    @Override
	    public void onClick(View view) {
	        switch (view.getId()){
	            case R.id.btn_get_remote_data_way1:
	                try {
	                    if(mCalculateAidl!=null) {
	                        List<String>  list=new ArrayList<>();
	                        list.add("list的集合");
	                        String data = mCalculateAidl.basicTypes(1,99999,true,3.14f,0.9999999,"字符串数据",list,"aa");
	                        tvWay1.setText("数据之和:" + data);
	                    }else {
	                        Toast.makeText(AidlCommunicateToDataTypeActivity.this," 与远程服务断开连接",Toast.LENGTH_SHORT);
	                    }
	                } catch (RemoteException e) {
	                    Log.i(TAG,"没有连接到服务:"+e.getMessage());
	                    Toast.makeText(AidlCommunicateToDataTypeActivity.this,"没找到该服务",Toast.LENGTH_SHORT);
	                }
	
	                break;
	            case R.id.btn_get_remote_data_way2:
	                try {
	                if(mCalculateAidl!=null) {
	                    Person person=new Person();
	                    person.setAge(21);
	                    person.setName("张三");
	                    List<Person> result = mCalculateAidl.addPersonData(person);
	                    tvWay2.setText("数据之和:" + result.toString());
	                }else {
	                    Toast.makeText(AidlCommunicateToDataTypeActivity.this," 与远程服务断开连接",Toast.LENGTH_SHORT);
	                }
	        } catch (RemoteException e) {
	            Log.i(TAG,"没有连接到服务:"+e.getMessage());
	            Toast.makeText(AidlCommunicateToDataTypeActivity.this,"没找到该服务",Toast.LENGTH_SHORT);
	        }
	
	        break;
	
	        }
	    }

6.运行和测试

AIDL的底层实现原理解析

说明:

1.服务端创建Aidl。编译生成所对应的java文件

2.生成的java文件其实是一个接口A,继承了android.os.Iinterface接口,里面声明实现了asBinder()方法,用于返回一个binder对象

3.接口A里面有一个内部抽象类(Stub)继承类android.os.Binder,同时实现了接口A。

4.当我们在service中 new 这个抽象类(Stub)时,其构造函数调用了this.attachInterface(this, DESCRIPTOR)方法,将该接口A的实例存储到底层中。

5.当客户端调用asinterface()时,会将服务端Binder的对象转换成客户端所需的AIDL接口类型的对象,但是转换的过程是区分进程的,若客户端和服务端处于同一进程,那么此方法将返回从obj.queryLocalInterface()中获取对象,否则返回是系统封装后的proxy代理对象

6.客户端获取实例后,调用方法进行数据传递时,该方法运行在客户端,首先会创建所需要的输入型Parcel对象_date,输出型Parcel对象_reply以及需要返回的结果对象,然后将客户端传入的数据写入到_data中,接着调用transact方法发起RPC(远程过程调用)请求,同时当前线程挂起(处于阻塞状态);这时候服务端的onTransact()方法会被调用,知道RPC过程返回,当前继续执行,并从_reply中取出RPC过程返回的结果给客户端

7.当服务端调用onTransact()时,该方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层分装好后交由此方法处理,服务端通过code可以确定客户端所应求的目标方法是什么,然后从_data中取出目标方法所需要的参数,之后执行目标方法,当目标执行完成后向_reply写入数据,并返回boolean确定这次请求是否成功。

参考链接: