初次发帖,对Android了解和掌握还不是很深,敬请各位指正交流,一起进步。
Android为了实现进程间通信,提供了AIDL机制。AIDL全称是Android Interface Definition Language,即进程间接口描述语言。通过AIDL机制,应用程序可以通过描述的接口访问远程服务中的方法。
下面说一下AIDL机制的使用方法,因为自身水平问题,范例都比较简单。
Server端与Client端功能简介:
Server端:在Server端开启一个后台服务, 该服务只提供两种方法供调用
1、int型的getNumber(int)方法,该方法将客户端传递过来的数据加3,再返回给客户端。
2、void型的setNumber()方法,该方法是设置Service中的变量为客户端传递进来的数据。
Client端:Activity,主要有两个按钮,一个是调用按钮,读取远程Service中的数据,一个是设置按钮,将Client端EditText中的数值传递给远程Service。
Service端与Client端的交互逻辑如下:
Server端负责后台Service的开启与中止,Client端开启后,首先判断服务是否存在,若存在则可以通过AIDL机制调用远程Service提供的方法并设置远程Service中的全局变量,若服务不存在则不可调用(本人在此处一个不经意的错误,排查了很长时间,在后面说)
使用AIDL方法实现上述功能的主要步骤如下:
一、AIDL接口定义:
在本步骤中,定义的是接口的名称以及方法的参数、返回值,新建一个.aidl文件,文件名需要与接口名相同。本范例中AIDL定义如下:
package com.example.servicedemo;
interface IGetNumber{
int getNumber();
void setNumber(int i);
}
二、在Service中实现定义的AIDL接口
定义完AIDL后,在gen包中,会自动生成一个与AIDL同名的.java文件,即依据所定义的接口自动生成的一个接口文件,该文件包含一个Stub抽象类,该抽象类又包含了我们在AIDL中定义的方法,Stub抽象类继承了Binder,Activity是通过Binder机制来实现与Service通信的,因此我们需要在Service中实现该Stub抽象类,以具体实现在AIDL中定义的方法,这样在Client端绑定远程Service时通过返回一个stub类可以调用Service的方法。
Service端的主要代码如下:
public class ServiceDemo extends Service{
public int num=0;
public Stub getNumber=new MyBinder();
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return getNumber;
}
public class MyBinder extends IGetNumber.Stub
{
@Override
public int getNumber() throws RemoteException {
// TODO Auto-generated method stub
num=num+3;
return num;
}
@Override
public void setNumber(int i) throws RemoteException {
// TODO Auto-generated method stub
num=i;
}
}
三、Client端绑定远程Service
Client端首先需要导入Server端gen文件夹下生成的IGetNumber.java文件,具体的方法为在Client端src文件夹下新建一个与Server端同名的包,并将IGetNumber.java复制到该包下。Client端Activity主要代码如下:
1、首先判断远程Service是否开启,此处与本文关系不大,一并贴一下。该功能是通过ActivityManager实现的,ActivityManager类中有一个getRunningServices(int MaxNum)方法,该方法可以返回一个正在运行的服务的列表,参数定义了返回的列表中列表项的最大数目。该判断过程放在onCreate()方法中,主要代码如下:
public List <ActivityManager.RunningServiceInfo> mList;
public ActivityManager mAcManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
findView(); //获取控件变量的函数
mAcManager=(ActivityManager)getSystemService(ACTIVITY_SERVICE);
mList=mAcManager.getRunningServices(30);
if(ServiceIsExist(mList,ServiceName))
{
Toast.makeText(getApplicationContext(), "远程服务已经开启", 0).show();
}
else
{
Toast.makeText(getApplicationContext(), "远程服务尚未开启", 0).show();
}
}
ServiceIsExist(mList,ServiceName)即为判断一个服务是否存在的函数,返回一个Boolean型,具体代码为:
private boolean ServiceIsExist(List<RunningServiceInfo> servicelist,
String classname ) {
// TODO Auto-generated method stub
for(int i=0;i<servicelist.size();i++)
{
if(classname.equals(servicelist.get(i).service.getClassName()))
{
return true;
}
}
return false;
}
2、绑定远程Service,该步骤需要通过之前导入的IGetNumber.java对象和一个ServiceConnection对象实现。主要代码如下,其中btn_start即为开始调用的按钮:
private IGetNumber igetNumber=null;
public ServiceConnection servConn;
btn_start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
servConn=new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
igetNumber=IGetNumber.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
igetNumber=null;
}};
Intent intent=new Intent(IGetNumber.class.getName());
bindService(intent, servConn, 0);
if(igetNumber!=null)
{
try {
Toast.makeText(getApplicationContext(),String.valueOf(igetNumber.getNumber()),0).show();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}});
需要特别注意的是,在bindService()函数的第三个参数中,本人吃了大亏。由于自己的大意,第三个参数我写的是Context.BIND_AUTO_CREATE,由于本文的应用场景是远程服务不开启则无法调用,因此在测试的时候发现即使Server端关闭了Client端仍旧可以调用AIDL中定义的方法,即使调用了Unbind()方法和onDestroy()方法。这显然是不对的,经过仔细的研究才发现,BIND_AUTO_CREATE的意义在于,若绑定的Service不存在则自动创建一个Service实例,因此即使通过Server端将开启的Service解除绑定并销毁,Client端仍然可以在系统内实例化一个远程Service并绑定。排查过后改为0,问题就解决了,此处花了好几个小时,新手学艺不精,长教训了。有此应用需求的朋友一定要注意。
通过Toast.makeText(getApplicationContext(),String.valueOf(igetNumber.getNumber()),0).show(),我们成功的通过AIDL方式调用了远程Service中的方法,并将返回值以Toast的方式显示在了客户端界面中。
还要再说一点的是,众所周知,onCreate()函数只在Activity创建的过程中调用一次,而本文中需要绑定的远程Service不一定开启,因此我没有将绑定远程Service的代码放在onCreate()函数中,而是放在了“开始调用”按钮的点击事件响应代码中,这样做不好的地方就是如果远程Service已经开启,则Activity创建后需要点击两次才能真正的开始调用AIDL中定义的方法
3、设置远程Service中的全局变量,即实现AIDL中定义的setNumber(int )方法。上一步实现之后,这一步就非常简单了,直接贴代码:
btn_set.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
igetNumber.setNumber(Integer.parseInt(edt_input.getText().toString()));
} catch (NumberFormatException | RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}});
在有些应用场景中,需要将参数传递给远程Service进行处理,实际上该方法就是演示这样一个过程。
四、Manifest文件中对远程Service的定义
这一步就比较简单了,需要注意的是在对Service定义的时候,android:process参数需要设置为":remote"
经过以上步骤,基本就可以通过AIDL的方式来调用远程Service提供的方法了。