Android中AIDL的使用步骤、传递对象以及各部分代码的执行线程

AIDL (Android Interface Definition Language 接口定义语言)是一种IDL 语言,用于在两个进程(两个应用)之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL的方式实现

AIDL传输数据支持: Java基本数据类型,String、CharSequence,List、Map和实现了Parcelable接口的自定义对象

注意
传递非基本参数类型和String参数类型的前面必须有修饰符(表明这个数据的去向):in、out、inout修饰,否则会报错

in:参数由客户端设置,即客户端传入参数值(如果服务端调用则没有数据)
out:参数由服务端设置,即服务端返回值(如果客户端调用则没有数据)
inout:客户端输入端都可以设置,即双向都可传入值
基本类型只能是in

AIDL实现步骤

服务端步骤

  1. 编写AIDL文件,定义接口方法(放到main文件夹下aidl目录的包名下)
  2. 在Service类中新建实现该AIDL文件名.stub类的子类,并实现AIDL接口方法
  3. 在Service类onBind方法中返回实现了Stub的子类

客户端步骤

  1. 把编写的aidl目录和目录下的所有文件拷贝到客户端的main文件夹下
  2. 在客户端Activity中自定义类实现ServiceConnection接口
  3. 在onServiceConnected方法内通过 AIDL文件名.Stub.asInterface(service)拿到IBinder对象
  4. 在onServiceConnected方法内通过 AIDL文件
  5. 隐式启动服务端Service,开始获取服务端数据

实现步骤分解

服务端工程步骤

第一步:在main下新建aidl文件夹,再新建IPeople.aidl

如图可快速创建

这里写图片描述

新建People类

public class People implements Parcelable {
    private String name;
    private int age;

    public People() {
    }

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected People(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

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

        @Override
        public People[] newArray(int size) {
            return new People[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;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

新建People.aidl

传递对象时,必须为对象创建aidl类,类中只需要这样写就行

package com.cn.lyz.aidlservicedemo;
parcelable People;

新建AIDL类 IPeople.aidl

package com.cn.lyz.aidlservicedemo;
import com.cn.lyz.aidlservicedemo.People; //需手动导入People包

interface IPeople {

        //客户端调用会阻塞,此方法在binder线程池中执行
        String getName(int num);
        String addPeople_in(in People people);
        String addPeople_out(out People people);
        String addPeople_inout(inout People people);
        People getPeople(int i);
        List<People> getPeopleList();
        int size();

        //oneway修饰的方法不能有返回值
        //oneway修饰的客户端不会阻塞,此方法在binder线程池中执行
        oneway void replaceName(int index, String name);
}

提示
当参数是对象(People)时,每个参数前面必须有修饰符(表明这个数据的去向):in、out、inout修饰,否则会报错

  1. in:参数由客户端设置,即客户端传入参数值
  2. out:参数由服务端设置,即服务端返回值
  3. inout:客户端输入端都可以设置,即双向都可传入值
  4. 基本类型只能是in

oneway修饰符说明

  1. 未使用oneway修饰的方法,客户端调用会直接阻塞(不能执行耗时操作),服务端方法在binder线程池中执行
  2. 使用oneway修饰的方法(不能有返回值),客户端不会阻塞(可执行耗时操作),服务端方法在binder线程池中执行

注意
IPeople接口中使用了People类,需手动导入People的包, 否则会报错

import com.cn.lyz.aidlservicedemo.People;

重新编译后会在build文件夹下生成同名java文件如图

这里写图片描述

打开build文件夹下的IPeople.java文件,里面有一个报错如图

这里写图片描述
或者在运行时报这个错误,如图

这里写图片描述

这是需要在People类中添加readFromParcel方法

public void readFromParcel(Parcel source) {
    name = source.readString();
    age = source.readInt();
}

readFromParcel中读的顺序要和writeToParcel写的顺序一致,否则会数据错乱

第二步:新建PeopleService服务类

public class PeopleService extends Service {

    private String[] peopleName = {"刘备","关羽","张飞"};

    private IBinder mIBinder = new PeopleBinder();

    private ArrayList<People> list = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("thread:", Thread.currentThread() + "-onCreate-");

    }

    /**
     * 返回IBinder对象
     * 运行在主线程
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("thread:", Thread.currentThread() + "-onBind-");

        return mIBinder;
    }

    /**
     * 继承IPeople.Stub实现IPeople接口方法
     * 运行在子线程
     */
    private class PeopleBinder extends IPeople.Stub{

        @Override
        public String getName(int num) throws RemoteException {
            Log.d("thread:", Thread.currentThread() + "-PeopleBinder-");
            return peopleName[num];
        }

        @Override
        public String addPeople_in(People people) throws RemoteException {
            list.add(people);
            return people.toString();
        }

        @Override
        public String addPeople_out(People people) throws RemoteException {
            list.add(people);
            return people.toString();
        }

        @Override
        public String addPeople_inout(People people) throws RemoteException {
            list.add(people);
            return people.toString();
        }

        @Override
        public People getPeople(int i) throws RemoteException {
            return list.get(i);
        }

        @Override
        public List<People> getPeopleList() throws RemoteException {
            return list;
        }

        @Override
        public int size() throws RemoteException {
            return list.size();
        }

        //oneway修饰的方法 客户端调用不会阻塞,此方法在binder线程池中执行
        @Override
        public void replaceName(int index, String name) throws RemoteException {
            try {
                //睡眠5秒
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            peopleName[index] = name;
            for (int i = 0; i < peopleName.length; i++) {
                Log.d("click2Btn:", "name:"+peopleName[i]);
            }
        }
    }
}

在AndroidManifest.xml文件配置PeopleService:

<service
    android:name=".PeopleService">
    <intent-filter>
        <!--添加action用于隐式启动Service-->
        <action android:name="This is a magic code"/>
    </intent-filter>
</service>

编译运行,报错提示找不到People类,如图

这里写图片描述

需要在app目录下的build.gradle内添加

android {
    .....省略.....
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
}

此时就可以把AIDLService工程运行到手机上了(客户端工程中也需要添加此代码)

客户端工程步骤

第三步:把服务端的aidl文件夹拷贝到客户端的main目录下,编译一下即可

第四步:在客户端Activity中自定义类实现ServiceConnection接口

public class MainActivity extends AppCompatActivity {

    private Button mBtn1;
    private Button mBtn2;
    private IPeople peopleQuery;
    private PeopleConnection peopleConn = new PeopleConnection();

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

    private void initView() {
        mBtn1 = (Button) findViewById(R.id.btn1);
        mBtn2 = (Button) findViewById(R.id.btn2);
    }

    private void initData() {
        mBtn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        click1Btn();
                    }
                }).start();
//                click1Btn();
            }
        });

        mBtn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                click2Btn();
            }
        });
    }

    /**
     * 默认在主线程执行
     * 运行在主线程
     */
    private final class PeopleConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //用此方法获取IPeople接口对象
            //运行在主线程
            Log.d("thread:", Thread.currentThread() + "-PeopleConnection-");
            peopleQuery = IPeople.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //运行在主线程
            peopleQuery = null;
        }
    }

    private void click1Btn() {
        Log.d("thread:", Thread.currentThread() + "-click1Btn-");
        //设置服务端服务的action
        Intent intent = new Intent("This.is.a.magic.code");
        //设置服务端的包名
        intent.setPackage("com.cn.lyz.aidlservicedemo");
        //必须使用bindService开启服务
        bindService(intent, peopleConn, BIND_AUTO_CREATE);
    }

    /**
     *
     * 客户端调用接口方法会直接阻塞,服务端方法在binder线程(子线程)中执行
     */
    private void click2Btn() {
        try {
            //获取服务端数组内角标为1的名字
            String name = peopleQuery.getName(1);
            Log.d("click2Btn:", "name:" + name);

            //给服务端列表添加People
            String p1 = peopleQuery.addPeople_in(new People("张三", 20));
            String p2 = peopleQuery.addPeople_out(new People("李四", 30));
            String p3 = peopleQuery.addPeople_inout(new People("王五", 40));
            Log.d("click2Btn:", "p1:" + p1 + "--p2:" + p2 + "--p3:" + p3);

            //获取服务端列表中第一个元素
            People people = peopleQuery.getPeople(0);
            Log.d("click2Btn:", "people:" + "name:"+people.getName()+"-age:"+people.getAge());

            //获取服务端列表大小
            int size = peopleQuery.size();
            Log.d("click2Btn:", "size=" + size);

            //循环打印服务端列表的内容
            List<People> peopleList = peopleQuery.getPeopleList();
            for (int i = 0; i < peopleList.size(); i++) {
                People p = peopleList.get(i);
                Log.d("click2Btn:", "for循环内people:" + p.toString());
            }

            //非阻塞执行
            peopleQuery.replaceName(1, "吕布");
            Log.d("click2Btn:", "非阻塞执行");

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(peopleConn);//解绑
    }
}

打印结果

12-21 16:05:34.819 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: name:关羽
12-21 16:05:34.822 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: p1:People{name='张三', age=20}--p2:People{name='null', age=0}--p3:People{name='王五', age=40}
12-21 16:05:34.822 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: people:name:张三-age:20
12-21 16:05:34.822 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: size=3
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: for循环内people:People{name='张三', age=20}
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: for循环内people:People{name='null', age=0}
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: for循环内people:People{name='王五', age=40}
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: 非阻塞执行
12-21 16:05:39.824 29209-29223/com.cn.lyz.aidlservicedemo D/click2Btn:: name:刘备
12-21 16:05:39.824 29209-29223/com.cn.lyz.aidlservicedemo D/click2Btn:: name:吕布
12-21 16:05:39.825 29209-29223/com.cn.lyz.aidlservicedemo D/click2Btn:: name:张飞

第二行打印结果为,p1、p3对象有内容,p2对象内容为空,由此得出各个修饰符的作用

in:参数由客户端设置,即客户端传入参数值(如果服务端调用则没有数据)
out:参数由服务端设置,即服务端返回值(如果客户端调用则没有数据)
inout:客户端输入端都可以设置,即双向都可传入值

AIDL简单源码分析

自动生成的IPeople.java文件

package com.cn.lyz.aidlservicedemo;
public interface IPeople extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.cn.lyz.aidlservicedemo.IPeople {
        private static final java.lang.String DESCRIPTOR = "com.cn.lyz.aidlservicedemo.IPeople";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static com.cn.lyz.aidlservicedemo.IPeople asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.cn.lyz.aidlservicedemo.IPeople))) {
                return ((com.cn.lyz.aidlservicedemo.IPeople) iin);
            }
            return new com.cn.lyz.aidlservicedemo.IPeople.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getName: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    java.lang.String _result = this.getName(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }

                //省略....
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.cn.lyz.aidlservicedemo.IPeople {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.lang.String getName(int num) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(num);
                    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            //省略....
        }
    }
}

peopleQuery = IPeople.Stub.asInterface(service);

当跨进程进行通信peopleQuery调用方法时,其实就是在调用本地的
com.cn.lyz.aidlservicedemo.IPeople.Stub.Proxy(obj)对象中的方法,在Proxy代理对象中每个方法都会执行了mRemote.transact(Stub.TRANSACTION_xxx, _data, _reply, 0)方法

mRemote:远程服务端对象
远程对象mRemote对象继承IPeople.Stub对象,IPeople.Stub继承Binder,Binder实现IBinder
当执行mRemote.transact代码的时候,就开始进行进程间通信了,由客户端开始调用服务端的代码了
所以mRemote会调用IBinder的transact,继而传递到Binder对象中的transact方法中

public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }

在transact方法内部会调用onTransact方法,继而调用IPeople.Stub类中onTransact方法,而在此方法中会调用我们自己的方法(如:IPeople.java文件的第38行)待方法内部逻辑处理完成后,再返回给客户端进行处理

注意事项

1:Android5.0以上系统不支持隐式启动service,报错如下:

java.lang.IllegalArgumentException: Service Intent must be explicit

解决
给发送的隐式意图设置服务端包名
intent.setPackage(“设置服务端工程的包名”);

2:报错找不到传递的对象类

解决
需要在app目录下的build.gradle内添加

android {
    .....省略.....
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
}

3:找不到方法readFromParcel(Parcel)方法

解决
在People类中添加readFromParcel方法,并且读的顺序要和writeToParcel写的顺序一致,否则会数据错乱

4:当传递参数为对象时,每个参数前必须有修饰符:in,out,inout,否则会报错
5:当传递参数为对象时,需要手动导入该对象的包,否则报错
6:AIDL服务默认是运行在主线程中,客户端调用未使用oneway修饰的方法,会直接阻塞(不能执行耗时操作),若有耗时操作,应该在子线程中调用AIDL客户端使用oneway修饰的方法(不能有返回值),不会阻塞(可执行耗时操作)

DEMO下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值