在 APK 上实现一个 AIDL 跨进程通信

2019-08-19

关键字:AIDL、跨进程通信、Service与AIDL


 

Linux 操作系统为了安全性的原因,将不同应用的活动范围,或者说权限范围限定在一块专有的内存空间中。每个应用都有属于自己的专属内存领域,且无法访问其它应用的专属领域。但需求永远是丰富多变的,跨进程访问,或者说宏观一点,跨应用访问这个需求在日常项目开发中是常有的事。为了打破这种应用无法访问其它应用的专属内存空间的限制,就衍生出了各种“跨进程通讯”技术。AIDL 也算是一种跨进程通讯方式。

 

1、AIDL是什么?

 

AIDL(Android Interface Definition Language),Android接口定义语言,它是 Android 提供的用于进程间通讯的机制。

 

其实,要做进程间通信还是挺复杂的。但 Android 为了让开发更简单高效,将复杂、重复的工作都封装好了,对上层就开放了一些配置信息的接口而已,这样一来,开发者就可以仅仅通过配置自己专有的信息,比如定义接口名称、参数列表与返回值类型等等,再经由编译转换以后就自动实现了原本复杂难懂的跨进程通信功能了。这种开发模式就是 AIDL。

 

AIDL 的模式既可以应用在 APK 开发中,也可以应用在 framework 层的开发中。它们的使用方式大同小异,都是编写相关 AIDL 接口信息文件并执行编译即可。AIDL 的进程间通信与其它进程间通信机制一样,都是使用 C/S 架构的。

 

2、AIDL的语法规则

 

AIDL 的语法规则还是比较简单的,同时也正是因为简单,它功能也不会有多丰富,不过作为一款跨进程通讯的模块,已经够了。

 

所有 AIDL 相关的信息都得定义在后缀为 .aidl 的文件中。对于文件名并没有太多的要求,只不过它要求与 Java 的类定义一样,接口类名称与文件名称要一致。并且我们还约定俗成地将 AIDL 类名称以大写字母 I 开头。

 

跨进程通信在微观上是要将一个应用内的数据由对象序列化为字节流传送出去,并接收来自外部的对象字节流来实现的。这就意味着 AIDL 不可能支持所有数据类型的使用。不过所幸,受 AIDL 支持的类型也已经足够满足各种需求了。

 

AIDL 语法支持 8 种基本类型:

1、byte

2、char

3、short

4、int

5、long

6、float

7、double

8、boolean

 

支持的字符串类型有两种:

1、String

2、CharSequence

 

支持的集合类型也有两种:

1、List

2、Map

关于集合类型的存储类型,也必须得是受 AIDL 支持的类型才可。

 

同时,AIDL 的语法还支持所有实现了 Parcelable 接口的类型。

 

 

AIDL 的书写规则与定义普通的 Java 接口没有什么区别。定义包名,再定义接口类名,接口内部再定义抽象方法。就这么简单。不过一定要注意,AIDL 文件的存储路径一定要与所定义的包名一致。

 

3、AIDL开发实例

 

AIDL 最广泛与最简单的应用是与四大组件之一 Serivce 的配合使用了。我们都知道,启动一个 Serivce 有两种方式:1、通过 startService 的方式;2、通过 bindService 的方式。 通过 binService 方式启动的 Service 所返回的对象类型,就可以理解为是 AIDL 跨进程通信类型。

 public abstract IBinder onBind(Intent intent);

 

下面我们来演示一下最简单的 APK 开发 AIDL 的方式。

 

首先是服务端的定义。我们在 Android Studio 上演示。创建一个 APK 工程。在开始的时候它什么也没有,笔者这边甚至连 MainActivity 都没有,它的结构如下图所示:

然后我们直接来创建 AIDL 文件,具体操作如下图所示:

然后便会自动多出一些文件:

再然后便可以去编写自己需要的接口方法了。笔者这边定义了两个示例方法:

 

再接下来,便是创建 Service 去实现这些抽象方法了。Service 中的任务也算单一,将前面我们定义的 ITest 的实例在 onBind 方法中返回即可。那这里就涉及到要去实现 ITest 接口。我们可以另外创建一个代码文件来实现它,也可以直接在 Service 类内部来实现它。笔者这里为了方便,就直接在 Service 类内部以匿名内部类的形式来实现它了。

 

在 Serivce 中要导入正确的 AIDL 接口类:

 

其次是创建匿名内部类的实现。注意这里实现的不是 ITest 接口,而是 ITest.Stub 抽象类。如果不懂就不用纠结它,记住这种写法就行,直接在自己定义的接口后加上 .Stub 即可:

 

完整的 Service 代码如下所示:

package com.tst.aidlservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.tst.aidlservice.ITest;

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return its;
    }


    private ITest.Stub its = new ITest.Stub() {
        @Override
        public boolean isSingular(int value) throws RemoteException {
            return value % 2 != 0;
        }

        @Override
        public String whatTime() throws RemoteException {
            return "I do not know!";
        }
    };
}

 

至此,服务端就算是完成了。接下来是客户端 APK 的开发。同样是新建一个 APK 工程。然后将工程视图切换成“Project”模式,并在 main 目录下右键,新建名称为 aidl 的目录,如下图所示:

 

aidl 目录创建以后则是要再创建一个与 AIDL 文件中的包名一致的目录层级,如下图所示: 

 

再然后,就是将服务端中的 AIDL 文件拷贝进这个目录中去。注意,是原封不动地拷贝,千万不能修改 AIDL 文件的什么信息。

 

然后要执行一下 Make Project 命令以创建 AIDL 的索引。

 

在 Make Project 完成以后,便可以开始去绑定 Service 了。笔者这里为了方便,选择在 Activity 的 onResume 方法中绑定,并在绑定成功后直接调用演示接口。整个代码不长,如下所示:

import com.tst.aidlservice.ITest;

public class MainActivity extends AppCompatActivity {

    private ITest its;

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

    @Override
    protected void onResume() {
        super.onResume();

        Intent it = new Intent();
        it.setClassName("com.tst.aidlservice", "com.tst.aidlservice.MyService");
        bindService(it, sc, Context.BIND_AUTO_CREATE);

    }

    private ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            its = ITest.Stub.asInterface(iBinder);

            try {
                boolean ret = its.isSingular(33);
                Log.d("Tst", "is 33 singluar? " + ret);

                String ret2 = its.whatTime();
                Log.d("Tst", "What time now? " + ret2);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };
}

 

然后编译服务端与客户端,将它们安装进手机中去测试。发现运行一切正常。

 

这就是最最简单的 AIDL 通讯实例。还有个更高级一点的用法,就是 AIDL 中要用到实现了 Parcelable 接口的类的形式。但这种实例,等以后有需要的时候再去实现吧。

 


 

posted @ 2019-08-19 17:54  大窟窿  阅读(797)  评论(0编辑  收藏  举报