本例要实现的效果是两个app,client和server,客户端能调用server端的Service提供的方法。
本次示例分为客服端和服务器端,两端的代码目录如下:
服务器端代码目录:
客户端代码目录:
代码说明
--->服务器端代码:
首先,要创建一个AIDL接口,IPersonManager.aidl,里面定义了服务器端提示给客户端的方法。
// IPersonManager.aidl
package com.demo.aidlprojectserver;
import com.demo.aidlprojectserver.Person; //要传递的自定义对象需要显示的import,即使是在一个目录
interface IPersonManager {
List<Person> getPersons();
void addPerson(in Person person); //除了基本数据类型,要添加 in out inout
String greet(String name);
}
系统自动编译会根据这个IPersonManager.aidl生成一个IPersonManager.java类。如图所示:
这个IPersonManager.java里的内部内IPersonManager.Stub就是一个Binder
public static abstract class Stub extends android.os.Binder
提示服务的Service里new一个这样的Binder,通过这个Binder向外提示服务。
AIDLService.java
package com.demo.aidlprojectserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class AIDLService extends Service {
public static final String TAG = AIDLService.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
try {
mManage.addPerson(new Person("张三", "男", 22));
mManage.addPerson(new Person("李四", "男", 30));
mManage.addPerson(new Person("婉儿", "女", 18));
} catch (RemoteException e) {
e.printStackTrace();
}
}
private List<Person> mPersons = new ArrayList<>();
private IPersonManager.Stub mManage = new IPersonManager.Stub() {
@Override
public List<Person> getPersons() throws RemoteException {
return mPersons;
}
@Override
public void addPerson(Person person) throws RemoteException {
Log.d(TAG, "client add person-----" + person.toString());
mPersons.add(person);
}
@Override
public String greet(String name) {
return "热烈祝贺" + name + "来我院视察";
}
};
@Override
public IBinder onBind(Intent intent) {
return mManage;
}
}
由于客户端也要定义同样的IPersonManager.aidl(所属的包目录结构一样,里面的代码也一样),生成的IPersonManager.java类也一样,所以客户端就可以通过客户端的IPersonManger来调用服务器端的Binder提示的方法,续而调用到Service提示的操作。
服务器端还有两个文件没有介绍:Person.java和Person.aidl。
默认情况下,AIDL 支持下列数据类型:
Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
String/CharSequence
List
List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
Map
Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map< String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
您必须为以上未列出的每个附加类型加入一个 import 语句,即使这些类型是在与您的接口相同的软件包中定义。
通过 IPC 传递对象
有时一些简单的数据无法满足我们的需求,我们需要的一个自定义实体,这时就需要使用Parcelable,要传递的实体类必须支持 Parcelable 接口。
//Person.java
package com.demo.aidlprojectserver;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
private String name;
private String sex;
private int age;
protected Person(Parcel in) {
this.name = in.readString(); //读取的顺序要和writeToParcel的write顺序一致
this.sex = in.readString();
this.age = in.readInt();
}
public Person(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(sex);
dest.writeInt(age);
}
@Override
public String toString() {
return name + ":" + sex + ":" + age;
}
}
除了实现Parcelable接口外,系统还要求我们自定义的对象需要写一个aidl文件。如Person.aidl
// Person.aidl
package com.demo.aidlprojectserver;
parcelable Person;
到此,服务器端代码介绍完毕
--->客户端代码
和服务器端一样,我们需要编写IPersonManager.aidl(生成IPersonManager.java)文件,编写Person.java,Person.aidl文件,并且package要和服务器端一样。代码目录回看上面介绍。
最后直接看调用的代码MainActivity.java
package com.demo.aidlprojectclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.demo.aidlprojectserver.IPersonManager;
import com.demo.aidlprojectserver.Person;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private boolean connected = false;
private IPersonManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt_bind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClassName("com.demo.aidlprojectserver", "com.demo.aidlprojectserver.AIDLService");
connected = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
});
findViewById(R.id.bt_add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (connected) {
Person person = new Person("lili", "女", 18);
try {
manager.addPerson(person);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
findViewById(R.id.bt_greet).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (connected && manager != null) {
try {
Toast.makeText(MainActivity.this, manager.greet("曹操"), 0).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
manager = IPersonManager.Stub.asInterface(service);
try {
List<com.demo.aidlprojectserver.Person> personList = manager.getPersons();
for (Person person : personList) {
Log.d(TAG, person.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
上面代码中,就是AIDL通过Binder机制,让客户端拿到了服务器端的Binder对象,从而用服务器端的Binder去调用服务器端的Service提示的方法。
本例子的效果就是:客户端通过调用 manager.greet("曹操"),调用到了另外一个进程(App)的方法:
public String greet(String name) {
return "热烈祝贺" + name + "来我院视察";
}
};
返回结果是:热烈祝贺曹操来我院视察
=========================================================================
至此完成了一次通过Binder(AIDL)实现跨进程通信。这和App通过跨进程通信调用系统服务是一样的道理。如下实例:
Binder运行的实例解释
首先我们看看我们的程序跨进程调用系统服务的简单示例,实现浮动窗口部分代码:
//获取WindowManager服务引用
WindowManager wm = (WindowManager) getSystemService(getApplication().WINDOW_SERVICE);
//布局参数layoutParams相关设置略...
View view = LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);
//添加view
wm.addView(view, layoutParams);
注册服务(addService): 在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。这一步是系统自动完成的。
获取服务(getService): 客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,通常是Service引用的代理对象,对数据进行一些处理操作。即第2行代码中,得到的wm是WindowManager对象的引用。
+
使用服务: 通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。即第6行调用WindowManager的addView函数,将触发远程调用,调用的是运行在systemServer进程中的WindowManager的addView函数。
代码下载地址:https://github.com/tomyZhou/AidlDemo
更多Binder相关知识推荐如下:
Android 中的 IPC 方式 https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/ipc.html
Android Binder机制及AIDL https://lrh1993.gitbooks.io/android_interview_guide/content/android/advance/binder.html
从getSystemService()开始,开撸Binder通讯机制 https://www.jianshu.com/p/1050ce12bc1e