AIDL跨进程通信的使用

作者: 宝塔山上的猫 | 来源:发表于2016-11-21 01:30 被阅读200次

    项目链接:https://github.com/liaozhoubei/AIDLService

    AIDL:Android Interface Definition Language,即Android接口定义语言,这是一种用于Android的跨进程通信(IPC)的工具。
    为什么有AIDL呢?这是因为在android中一个进程不允许直接访问另一个进程的内存。
    进程是一个应用在手机上执行的过程,这是一个动态的概念,一个app的主进程可以从这个app打开算起,然后直接退出app,这时就算是一个进程的结束。
    在android中允许有多进程,因此多进程通信可以在自己的应用中进行通信,也可以在不同应用中通信,而这便是AIDL的使用方向。
    但是AIDL有个缺点,那就是比较耗资源,如非必要,就不适用AIDL的为好

    AIDL的使用方法

    我们知道了AIDL是用来干什么的,那么我们就开始学习如何使用AIDL吧!

    我们先建立一个AIDLService项目,这个项目用来模拟远程服务端口的,即我们想通过AIDL调用这个项目中的方法,这就是跨进程通信。

    由于只是一个小Demo,所以并没有做太复杂的东西,我们只想通过AIDLService这个进程进行一次基本数据类型的传递,即进行简单的计算,然后将计算结果传回去客户端中。
    其次,我们还想通过AIDLService将一个Object对象传给客户端。

    那么我们就开始吧。

    AIDLService服务端: 创建一个AIDL文件

    首先必须要有一个AIDL接口文件,即后缀名为aidl的文件,在这里我们是IService.aidl,代码如下

    interface IService {
        // 传入两个正整数,返回他们的和
        int add(int num1, int num2);
        // 传入一个Person对象,返回person集合
        List<Person> addPerson(in Person p);
    }
    

    看上去是不是很熟悉,没错,这其实就是一个interface接口而已,区别在于它并没有public或者private等修饰符。
    其中addPerson(in Person p)中我们看到有个in Person p,这个in是什么呢?简单的说表示这个Person是从外面传进来的(可以自行搜索其中的意思)。

    除此之外还需要创建一个Person.aidl,传递序列号之后的Person对象,代码很简单,如下:

    parcelable Person;
    

    就这么一行代码。

    AIDLService服务端: 创建一个RemoteService远程服务

    RemoteService远程服务中将实现IService.aidl中的两个方法,让后通过IBinder传出(IBinder可以让外界调用服务中的方法)。具体代码如下:

    public class RemoteService extends Service {
        public final String TAG = this.getClass().getSimpleName();
        private ArrayList<Person> persons;
    
        public RemoteService() {
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            persons = new ArrayList<>();
            return iBinder;
        }
    
    
    
        private IBinder iBinder = new IService.Stub() {
            @Override
            public int add(int num1, int num2) throws RemoteException {
                return num1 + num2;
            }
    
            @Override
            public List<Person> addPerson(Person p) throws RemoteException {
                persons.add(p);
                return persons;
            }
        };
    }
    

    当然,我们不要忘记建立Person.java,而且要对这个类实现序列号,因为Object必须实现序列号才能同传输,代码如下:

    public class Person implements Parcelable {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        protected Person(Parcel in) {
            // 从序列号中取出数据,要按照writeToParcel()写入的顺序一样取出
            this.name = in.readString();
            this.age = in.readInt();
        }
    
    
    
        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 String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        /**
         * 将数据写入序列化中
         * @param dest
         * @param flags
         */
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            // 写入时的顺序决定取出数据时的顺序
            dest.writeString(name);
            dest.writeInt(age);
        }
    
    
        /**
         * 必须实现的方法,写法基本固定
         */
        public static final Creator<Person> CREATOR = new Creator<Person>() {
            /**
             * 从序列化中取出一个个的字段
             * @param in
             * @return
             */
            @Override
            public Person createFromParcel(Parcel in) {
                return new Person(in);
            }
    
            @Override
            public Person[] newArray(int size) {
                return new Person[size];
            }
        };
    }
    

    OK,这样我们的AIDLService服务端就已经完成了,剩下的就是使用客户端调用了。

    AIDLClient客户端

    客户端用来调用AIDLService中的两个方法,那么如何调用呢?
    首先必须要创建AIDLClient客户端。

    然后我们直接把AIDLService当中的Person.aidl和IService.aidl两个文件拷贝到,需要注意的是这里两个文件所在的包名必须与在AIDLService时的包名保持一致。
    还需要把Person.java拷贝过来,包名也需要保持一致。

    AIDLClient客户端需要一个简单的界面,activity_main.xml代码如下:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.aidlclient.MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_gravity="center_horizontal"
            >
    
            <EditText
                android:id="@+id/num1"
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="+" />
    
            <EditText
                android:id="@+id/num2"
                android:layout_width="50dp"
                android:layout_height="wrap_content" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="=" />
    
            <EditText
                android:id="@+id/result"
                android:layout_width="50dp"
                android:layout_height="wrap_content" />
    
        </LinearLayout>
    
        <Button
            android:id="@+id/btn_addNumber"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="调用"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_gravity="center_horizontal"
            >
    
            <EditText
                android:id="@+id/name"
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="+" />
    
            <EditText
                android:id="@+id/age"
                android:layout_width="50dp"
                android:layout_height="wrap_content" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="=" />
    
            <EditText
                android:id="@+id/getPerson"
                android:layout_width="50dp"
                android:layout_height="wrap_content" />
    
        </LinearLayout>
    
        <Button
            android:id="@+id/btn_addPerson"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="调用Person"/>
    
    </LinearLayout>
    

    非常简单,这里就不做解释了。

    AIDLClient客户端:调用远程服务

    MainActivity中的布局写好了,那么我们就开始进行远程吧。方法其实就使用本地服务基本一样。代码如下:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private EditText num1;
        private EditText num2;
        private EditText result;
        private EditText name;
        private EditText age;
        private EditText getPerson;
        private Button btn_1;
        private Button btn_2;
    
        IService iService;
    
        /**
         * 创建ServiceConnection与服务进行链接
         */
        private ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iService = IService.Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                iService = null;
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
            bindService();
    
        }
    
        private void initView() {
            num1 = (EditText) findViewById(R.id.num1);
            num2 = (EditText) findViewById(R.id.num2);
            result = (EditText) findViewById(R.id.result);
    
            btn_1 = (Button) findViewById(R.id.btn_addNumber);
            btn_2 = (Button) findViewById(R.id.btn_addPerson);
    
            name = (EditText) findViewById(R.id.name);
            age = (EditText) findViewById(R.id.age);
            getPerson = (EditText) findViewById(R.id.getPerson);
    
            btn_1.setOnClickListener(this);
            btn_2.setOnClickListener(this);
        }
    
        /**
         * 与远程服务进行绑定
         */
        private void bindService() {
            Intent intent = new Intent();
            // 传入AIDL的包名,以及调用远程服务的全类名
            intent.setComponent(new ComponentName("com.example.aidlservice", "com.example.aidlservice.RemoteService"));
            bindService(intent, conn, Context.BIND_AUTO_CREATE);
    
        }
    
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_addNumber:
                    int num1 = Integer.parseInt(this.num1.getText().toString());
                    int num2 = Integer.parseInt(this.num2.getText().toString());
    
    
                    try {
                        int num = iService.add(num1, num2);
                        result.setText(num + "");
                        Toast.makeText(getApplicationContext(), num + "来到了", Toast.LENGTH_LONG).show();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
    
                case R.id.btn_addPerson:
                    String p1 = name.getText().toString();
                    int num3 = Integer.parseInt(age.getText().toString());
                    Person person = new Person(p1, num3);
                    try {
                        ArrayList<Person> persons = (ArrayList<Person>) iService.addPerson(person);
                        Toast.makeText(getApplicationContext(), persons.toString(), Toast.LENGTH_SHORT).show();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    }
    

    在这里,我们看到基本与调用本地的服务的方法没多大区别,只是在使用bindService()方法时,我们要设置包名,即设置我们要访问的服务的信息,

    intent.setComponent(new ComponentName("com.example.aidlservice", "com.example.aidlservice.RemoteService"));
    

    总结

    AIDL的使用并不难,而且在调用的时候也就象使用普通的服务一样,区别只在双方都必须有aidl文件,其中表明能够被调用的方法而已。
    下面总结一下aidl多进程通信的使用步骤

    • 首先服务都创建aidl接口文件,这个接口文件表示里面的方法能被外部调用。同时要注意的是如果有Object对象传输,必须实现Parcelable接口。
    • 创建一个Service,通过IBinder实现aidl接口中的方法,然后再onBind()中返回这个IBinder.
    • 在客户端复制服务端的aidl文件,同时保持包名一致,如果有Object对象,也一样要复制。
    • 直接通过bindService()方法绑定服务,调用IBinder中的方法。

    相关文章

      网友评论

      • Exception_Cui:知道服务端和客户端怎么写 ,以前只会写客户端
      • FynnJason:写的好。但一时半会儿需要消化一下:sweat:

      本文标题:AIDL跨进程通信的使用

      本文链接:https://www.haomeiwen.com/subject/hjaypttx.html