轻松搞定AIDL

来源:互联网 发布:万网域名增加二级 编辑:程序博客网 时间:2024/06/01 09:27

前言

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

概述

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.aidlpackage ready.mumu.service;    parcelable myParcelable;
  • 在原来的MyAidl.aidl中添加一个readText方法
// MyAidl.aidlpackage 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下载地址

1 0
原创粉丝点击