多进程间通信AIIDL使用

AIDL 是Android上提供的一套进程间通讯的机制,在多进程间的通信上他是比较常见的,那为什么要用aidl 呢,我们的四大组件都可以实现跨进程,我只能说使用的场景有所不同罢了。

1.activity 可以跨进程调用另一个进程 的activity 并且也可以携带参数,但是局限性比较大,只能通过intent 在启动时传递。

2.BroadcastReceiver 广播接收器,他可以无限次的发送全局广播,但是它的数据可能不是安全的,并且通信的效率也没有aidl 高,并且维护成本也很高,不能处理耗时操作。

3.ContentProvider 是一种数据共享型组件,用于向其他组件乃至其他应用共享数据。在它内部维持着一份数据集合, 这个数据集合既可以通过数据库来实现,虽然很强大,但是有点重了,实现起来比较麻烦,但是对于大量数据共享是非常有用的。

Service 我们叫它服务,他可以在后台执行一些耗时任务。可以单独启动执行任务,也可以绑定activity交互数据,这种只能在本进程的数据通信。其次就是通过aidl 来实现进程的通信,当然也可以通过信使Messenger 进程间通信。

介绍完了上边四大组件的跨进程通信方式,对跨进程通信的方式有了一个大概了解,下面来着重介绍aidl 的使用。

AIDL 是什么能干什么?

AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,可以绑定其他进程service 通过aidl 实现进程间数据的通信。

具体的使用步骤:

1.建立两个module ,一个用于服务端app (aidlapplication),一个用于客户端app(clientservice)

2.服务端新建aidl类定义aidl 接口和方法及自定义数据类,配置build.gradle (看情况配置)

3.服务端新建service类(MyService),并且新建内部类(MyBinder)继承aidl 接口类的Stub抽象类

4.服务端 MyService 重写onBind 函数 并返回内部类MyBinder 实例

5.服务端 manifest注册service

6.客户端 ,将服务端定义的aidl 及自定义数据类,复制到服务端,注意包名要完全一致

7.客户端 ,通过包名和action 绑定服务端的service

8.客户端 ,通过绑定成功后返回的service 获取定义的aidl 接口

9.客户端 ,通过接口和服务端通信了。

总共分为9部,这里分的比较细,下面按步骤来学习:

先来假定一个业务逻辑然后我们来实现,客户端可以通过接口直接获取服务端的基本数据类型,可以获取服务端的自定义数据类型,可以修改添加服务端的自定义数据类型。

1.新建两个app这个就不用说,掠过

2.新建aidl 类:

   新建aidl 接口类IMyAidlInterface  及自定义数据类Person 类,IMyAidlInterface 新建完成后,会自动新建一个接口方法

 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

他只是告诉我们,他支持的基本数据类型,这些基本数据类型不需要import 方式导包,没啥用可以删除了,其他自定义的数据类型以及其他类型数据都要使用import 方式来导包。

创建Person 类并且实现Parcelable 接口

public class Person implements Parcelable {
    int age;
    int height;
    String name;

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

    public int getAge() {
        return age;
    }

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

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public String getName() {
        return name;
    }

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

    public Person() {
    }

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

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

    protected Person(Parcel in) {
        this.age = in.readInt();
        this.height = in.readInt();
        this.name = in.readString();
    }

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

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

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

   添加IMyAidlInterface 接口的方法,定义我们的业务方法。

package com.example.aidlapplication;

// Declare any non-default types here with import statements
//导入Person 包路径
import com.example.aidlapplication.Person;
import java.util.List;
//自定义数据类型必须parcelable 关键字标注否则会找不到类
parcelable Person;
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     //定义提供的接口 返回值是一个基本数据类型
     String getName();
     // 返回值是一个自定义的数据类型集合,用于获取本进程内的数据 不在基本数据类型内的,都要导包
     List<Person> getPerson();
     // 提供远程操作本进程内数据的接口方法
     void addPerson(in Person person);

}

注意,此处有坑,先来看这些类的包路径情况

1坑.  我们刚才生成的类都在aidl 文件夹下,这时就会报找不Person 的错误,这时要配置下build.gradle文件,注意是module 的,添加如下

sourceSets {
    main {
        java.srcDirs = ["src/main/java", "src/main/aidl"]
    }
}

但是如果放在java 目录包下就不用配置了,因为默认是去java下找类的。

 

2坑.  自定义数据类型一定要用 parcelable 关键字标注 如

parcelable Person;

其次必须要正确的导入Person 的包路径,否则也会找不到类错误。

完成上面的操作后我们需要构建下项目,用于通过aidl  生成java 接口文件。

生成的位置位于:

3.服务端新建service类(MyService),并且新建内部类(MyBinder)继承aidl 接口类的Stub抽象类,实现aidl 的所有接口,至于问什么要继承Stub 类,这要去看下构建生成的IMyAidlInterface 类,本篇只讲使用。

 

public class MyService extends Service {
    private static final String TAG = "ClientService";
    List<Person> personList = new ArrayList<>();

    public MyService() {
    }

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

    class MyBinder extends IMyAidlInterface.Stub {


        @Override
        public String getName() throws RemoteException {
            Log.d(TAG, "getName: 远程调用获取基本类型数据");
            return "幺妹子";
        }

        @Override
        public List<Person> getPerson() throws RemoteException {
            Log.d(TAG, "getPerson: 远程调用获取自定义类型数据");
            return personList;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            Log.d(TAG, "addPerson: " +"收到添加进来的person" + person.toString());
            personList.add(person);
        }


    }
}

4.服务端 MyService 重写onBind 函数 并返回内部类MyBinder 实例

  MyBinder 类继承自Stub抽象类,stub实现了IMyAidlInterface 接口并且继承自

binder 类,binder实现了IBinder 接口。这里返回MyBinder 实例,实际返回类型就是IBinder类,当绑定service时返回的就是这个MyBinder 实例。
public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

5.服务端 manifest注册service

 这部非常关键,四大组件都必须在manifest 中注册,并且要简单配置下,以便可以远程调用。

 <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <action android:name="com.example.service.aidl"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
 enabled:是否这个service能被系统实例化,如果能则为true,否则为false。默认为true。
 exported :是否支持其它应用调用当前组件  
 process :创建本应用的私有的新进程
 Action:Action属性的值为一个字符串,它代表了系统中已经定义了一系列常用的动作。
 Category:Category属性用于指定当前动作(Action)被执行的环境

通过Intent Filter 隐式启动service  

6.客户端 ,将服务端定义的aidl 及自定义数据类,复制到服务端,注意包名要完全一致.

将服务端 aidl 文件夹复制客户端

注意配置要和服务端一致。

7.客户端 ,通过包名和action 绑定服务端的service

注意包名是服务端的包名,action 启动标示 ,用于匹配intent-filter 启动service

 Intent intent = new Intent();
        intent.setPackage("com.example.aidlapplication");
        intent.setAction("com.example.service.aidl");
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);

8.客户端 ,通过绑定成功后返回的service 获取定义的aidl 接口

 绑定成功后onServiceConnected 方法会返回来两个参数,其中IBinder就是我上边说的onBind 返回的接口类,

通过IMyAidlInterface.Stub.asInterface方法转为我们定义的接口

iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);

9.客户端 ,通过接口和服务端通信了

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    IMyAidlInterface iMyAidlInterface;
    private TextView getAidlTest;
    private TextView getAidlCustom;
    private TextView getAidlAddCustom;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setPackage("com.example.aidlapplication");
        intent.setAction("com.example.service.aidl");
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);
        initView();
    }

    private void initView() {
        getAidlTest = findViewById(R.id.get_aidl_Test);
        getAidlAddCustom = findViewById(R.id.get_aidl_add_custom);
        getAidlCustom = findViewById(R.id.get_aidl_custom);
        getAidlTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exception();
                try {
                    Toast.makeText(MainActivity.this, iMyAidlInterface.getName(), Toast.LENGTH_LONG).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        getAidlCustom.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exception();
                try {
                    List<Person> personList = iMyAidlInterface.getPerson();
                    Iterator<Person> pe = personList.iterator();
                    for (int i = 0; i < personList.size(); ++i) {
                        Person person = pe.next();
                        Log.d(TAG, "onClick: " + person.toString());
                    }

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        getAidlAddCustom.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exception();
                try {
                    iMyAidlInterface.addPerson(new Person(34,56,"这是一条插入的人名" + System.currentTimeMillis() +""));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    private void exception() {
        if (iMyAidlInterface == null) {
            try {
                throw new IllegalAccessException("接口为空");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return;
        }
    }
}

xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_marginTop="20dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/get_aidl_Test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获取基本数据类型信息"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/get_aidl_custom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="获取自定义数据类型"
            android:textSize="18sp" />
        <TextView
            android:id="@+id/get_aidl_add_custom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="输入自定义数据类型"
            android:textSize="18sp" />
    </LinearLayout>

</merge>

 

最后记得构建项目,然后测试的时候先启动服务端,然后启动客户端。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章