轻松搞定AIDL

前言

为了防止遗忘这些知识点,写一篇博客加深自己的理解,方便忘记后再重新学习。

概述

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。
AIDL是用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。线程间通讯有多种方式,下面简单介绍下不同之间区别。

多种线程间通讯方式的不同:详情参考此博客
  • Bundle:四大组件间的进程间通信方式,简单易用,但传输的数据类型受限。
  • 文件共享: 不适合高并发场景,并且无法做到进程间的及时通信。
  • Messenger: 数据通过Message传输,只能传输Bundle支持的类型
  • ContentProvider:android 系统提供的。简单易用。但使用受限,只能根据特定规则访问数据。
  • AIDL:功能强大,支持实时通信,但使用稍微复杂。
  • Socket:网络数据交换的常用方式。不推荐使用。
关于AIDL语法

aidl的语法基本和java一样,仅有几点不同之处:

  • 文件类型:AIDL文件的后缀是 .aidl,而不是 .java。
  • 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下。(列:编写了两个文件,一个叫做 person.java ,另一个叫做PersonManager.aidl,它们都在 com.mumu.aidl包下 ,在 .aidl 文件里使用person对象我们就必须在 .aidl 文件里面写上 import com.mumu.aidl.person; )
  • 默认支持的数据类型包括:
    • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。(实测short不支持)
    • String 类型。
    • CharSequence类型。
    • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。
    • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。
    • parcelable序列化的数据类型:parcelable所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口;
  • 定向tag:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。注意,不要全都用 inout ,工程大了系统的开销就会大很多,排列整理参数的开销很大。详细Tag使用方式参考此博客

dome实例(使用的studio工具)

简单实现基本数据类型(如图一个远程相加运算)

这里写图片描述

服务端
  • 创建一个新的项目作为服务端
    1. 创建服务器端的aidl包
      这里写图片描述
    2. 创建的aidl包下aidl文件
    3. 实现aidl文件接口
package ready.mumu.service;

interface MyAidl {
     int addnum(int num1 , int num2);
}
  • 在java包下创建一个service并实现aidl接口
public class Myservice extends Service {

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

    private final MyAidl.Stub iBinder  = new MyAidl.Stub(){

        @Override
        public int addnum(int num1, int num2) throws RemoteException {
            Log.v("MUMU","收到输入的远程请求,收到的值是num1:"+num1+"  num2:"+num2);
            return num1 + num2;
        }
}
  • 注册表注册service
<service android:name=".Myservice" android:process=":remote">
            <intent-filter>
                <action android:name="ready.mumu.service.MyAidl"/>
            </intent-filter>
        </service>

这里说一下Android声明文件中的android:process属性可以为任意组件包括应用指定进程,如果我们需要让一个服务在一个远端进程中运行(而不是标准的它所在的apk的进程中运行),我们可以在声明文件中这个服务的标签中通过android:process属性为其指定一个进程。
“:remote”又是什么意思呢?“remote”不是关键,这个完全可以自己随意取名字,“:”冒号才是关键。
进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。而进程名不以“:”开头的进程属于全局进程,其他应用可以通过某些方式和它跑在同一个进程中。

客户端
  • 将服务端对应的aidl拷贝到客户端,要求aidl完全一致,所在的包名也完全一致
  • 创建客户端的activity运行界面,并实现按钮点击事件(xml布局文件就不写了,简单的三个输入框一个按钮)
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initUI();

        //软件启动就绑定服务
        bindService();
    }

    private void initUI() {
        et_num1 = (EditText) findViewById(R.id.et_num1);
        et_num2 = (EditText) findViewById(R.id.et_num2);
        et_res = (EditText) findViewById(R.id.et_res);
        bt_add = (Button) findViewById(R.id.bt_add);

        bt_add.setOnClickListener(this);
    }
  • 实现绑定服务方法
private void bindService() {
        //获取到服务端
        Intent intent = new Intent();
        //5.0之后必须显示intent启动 绑定服务 , ComponentName两个参数对应是服务包名和服务文件名(文件名必须是包名+文件名)
        intent.setComponent(new ComponentName("ready.mumu.service","ready.mumu.service.Myservice"));
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
    }
  • 实现ServiceConnection(conn)绑定回调
MyAidl myaidl;

private ServiceConnection conn = new ServiceConnection() {

        //绑定上服务的时候执行
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //拿到远程的服务
            myaidl = MyAidl.Stub.asInterface(iBinder);
        }

        //当服务断开的时候执行
        @Override
        public void onServiceDisconnected(ComponentName componentName) {

            //回收资源
            myaidl = null;
        }
    };
  • 实现“远程计算”按钮onclick方法
@Override
    public void onClick(View view) {

        if(view == bt_add){
            int num1 = Integer.parseInt(et_num1.getText().toString());
            int num2 = Integer.parseInt(et_num2.getText().toString());

            try {
                //调用远程服务
                int res = myaidl.addnum(num1 , num2);
                et_res.setText(res+"");
            } catch (RemoteException e) {
                e.printStackTrace();
                et_res.setText("报错了");
            }
        }
  • 在activity销毁的onDestroy方法中解绑服务
@Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }

如此简单的小dome就完成了。

相对复杂的序列化实现dome(如图)

这里写图片描述
这里在上边dome的基础上又添加了一个“序列化调用”按钮,点击之后输入传入的自定义的序列化数据,下面的步骤是在上个dome基础之上添加的。

服务端

方便看结构,先来一张服务端的代码结构图
这里写图片描述

  • 创建一个java类myParcelable,定义数据类型、构造方法、get/set方法,实现Parcelable序列化(这个类的包名和aidl的包名要一致)
public class myParcelable implements Parcelable{

    String name;
    int age;
    String sex;


    //参数是一个Parcel,用它来存储与传输数据
    protected myParcelable(Parcel in) {
        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
        name = in.readString();
        age = in.readInt();
        sex = in.readString();
    }

    public static final Creator<myParcelable> CREATOR = new Creator<myParcelable>() {
        @Override
        public myParcelable createFromParcel(Parcel in) {
            return new myParcelable(in);
        }

        @Override
        public myParcelable[] newArray(int size) {
            return new myParcelable[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //数据存储至Parcel
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(sex);
    }

    //方便数据清晰
    @Override
    public String toString() {
        return "myParcelable{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}
  • 在aidl包下创建一个aidl文件(myParcelable.aidl)用于定义parcelable对象(非默认支持数据类型必须通过AIDL文件定义才能被使用)。这个myParcelable.aidl和myParcelable.java的包名要一致,所以上个类创建时才说要与aidl包名一致。
// myParcelable.aidl
package ready.mumu.service;

    parcelable myParcelable;
  • 在原来的MyAidl.aidl中添加一个readText方法
// MyAidl.aidl
package ready.mumu.service;

//注意需要导入包
import  ready.mumu.service.myParcelable;

interface MyAidl {

     int addnum(int num1 , int num2);

     //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
     String readText(in myParcelable par);
}
客户端

结构附图
这里写图片描述

  • 将服务端的myParcelable.java和myParcelable.aidl拷贝过来,注意包名一致
  • 将服务端的MyAidl.ail复制到客户端,此文件要保持客户端和服务端一致。
  • 实现activity中“序列化调用”按钮onclick方法
if(view == bt_par){

            try {
                String msg = myaidl.readText(new myParcelable("张三",18,"男"));
                et_res.setText(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
                et_res.setText("序列化出错了");
            }

        }

如此,序列化数据的远程调用也就结束了。

结语

可能看起来会稍微混乱一点,实现一下就会发现其实aidl使用还是很简单的,最后留下dome地址,看dome可能会相对更容易理解一些。dome下载地址

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值