android studio中使用AIDL进行客户端与服务端互相通信
Posted 优雅的心情
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android studio中使用AIDL进行客户端与服务端互相通信相关的知识,希望对你有一定的参考价值。
前言
在AIDL实现IPC通信,调用远程服务端的方法。但是,远程服务端并不能主动给客户端返回信息。在很多情况下是需要远程服务端主动给客户端返回数据,客户端只需要进行监听即可,这是典型的观察者模式。这篇文章主要来解决一下这个问题。
1、首先是AIDL接口定义
这里定义了三个接口,首先是 IMyAidlInterface.aidl;这个接口主要是用于客户端注册和解注册回调接口,这样服务端就可以往客户端回传数据。
package com.csda.aidl.service;
import com.csda.aidl.service.Person;
import com.csda.aidl.service.IOnNewPersonArrivedListener;
interface IMyAidlInterface
List<Person> getPersonList();
void addPeroson(in Person person);
void registListener(IOnNewPersonArrivedListener listener);
void unregistListener(IOnNewPersonArrivedListener listener);
然后是IOnNewPersonArrivedListener.aidl,这个是回调接口,用于往客户端回传信息。由于AIDL接口中不支持一般的interface,所以接口也得是aidl接口类型,如下所示:
package com.csda.aidl.service;
import com.csda.aidl.service.Person;
interface IOnNewPersonArrivedListener
void onNewPersonArrived(in Person person);
还有定义的实体类Person.aidl
package com.csda.aidl.service;
parcelable Person;
在android Studio中AIDL文件的位置如上,
因为在AIDL文件中,并不是所有的数据类型都是可以使用的,那么AIDL文件支持哪些数据类型呢?如下所示:
● 基本数据类型(int、long、char、boolean、double等);
● String和CharSequence;
● List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
● Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
● Percelable:所有实现了Parecelable接口的对象
● AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
另外,如果AIDL文件中用到了自定义的Parcelable对象,必须新建一个和他同名的AIDL文件,并在其中声明他为Parcelable类型,同时在引用的AIDL文件中必须导入这个AIDL,如上的IOnNewPersonArrivedListener.aidl
继续定义用于传送的实体类数据封装:
Person实体类:
package com.csda.aidl.service;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Administrator on 2017/3/29.
*/
public class Person implements Parcelable
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];
;
private String name;
public Person(String name)
this.name = name;
protected Person(Parcel in)
name = in.readString();
public String getName()
return name;
public void setName(String name)
this.name = name;
@Override
public String toString()
return "Person" +
"name='" + name + '\\'' +
'';
@Override
public int describeContents()
return 0;
@Override
public void writeToParcel(Parcel dest, int flags)
dest.writeString(name);
AIDL文件声明完毕后,我们可以先运行编译:
然后会在
中看到这两个编译出来的文件
2:创建Service
接着我们定义我们的Service
package com.csda.aidl.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by vegetable on 2017/3/29.
*/
public class AIDLService extends Service
//RemoteCallbackList是专门用于删除跨进程listener的接口,它是一个泛型,支持管理任意的AIDL接口
private RemoteCallbackList<IOnNewPersonArrivedListener> mListener=new RemoteCallbackList<>();
private CopyOnWriteArrayList<Person> persons=new CopyOnWriteArrayList<>();
private AtomicBoolean isServiceDestory=new AtomicBoolean(false);
@Override
public void onCreate()
super.onCreate();
persons.add(new Person("小乐"));
new Thread(new ServiceWork()).start();
@Nullable
@Override
public IBinder onBind(Intent intent)
return new IMyService();
@Override
public void onDestroy()
isServiceDestory.set(true);
super.onDestroy();
public class IMyService extends IMyAidlInterface.Stub
@Override
public List<Person> getPersonList() throws RemoteException
return persons;
//客户端可以通过调用这个方法想服务端发送消息
@Override
public void addPeroson(Person person) throws RemoteException
//进行相应处理
Log.i("添加人数","添加人数"+persons.size());
persons.add(person);
@Override
public void registListener(IOnNewPersonArrivedListener listener) throws RemoteException
mListener.register(listener);
@Override
public void unregistListener(IOnNewPersonArrivedListener listener) throws RemoteException
mListener.unregister(listener);
private void onNewPerson(Person person)throws Exception
persons.add(person);
int n=mListener.beginBroadcast();
for(int i=0;i<n;i++)
IOnNewPersonArrivedListener l=mListener.getBroadcastItem(i);
if (l!=null)
try
l.onNewPersonArrived(person);//服务端通过这个向客户端发送消息
catch (RemoteException e)
e.printStackTrace();
mListener.finishBroadcast();
private class ServiceWork implements Runnable
@Override
public void run()
while (!isServiceDestory.get())
try
Thread.sleep(5000);
catch (Exception e)
int i=persons.size()+1;
Person person=new Person("小乐"+i);
try
onNewPerson(person);
catch (Exception e)
e.printStackTrace();
值得一提的是:
1:RemoteCallbackList是专门用于删除跨进程listener的接口,它是一个泛型,支持管理任意的AIDL接口。
为什么要使用它呢?因为在服务端进行注册客户端发送过来的listener时,Binder会把客户端传递过来的对象重新转化并生成一个新的对象,因为对象是不能进行跨进程直接传输的,对象的跨进程传世都是反序列化的过程,这就是为什么AIDL中自定义对象都必须实现Parcelable的原因
2:使用RemoteCallbackList,我们无法像操作list一样去操作它,它并不是一个list,遍历的时候
int n=mListener.beginBroadcast();
for(int i=0;i<n;i++)
IOnNewPersonArrivedListener l=mListener.getBroadcastItem(i);
if (l!=null)
try
l.onNewPersonArrived(person);//服务端通过这个向客户端发送消息
catch (RemoteException e)
e.printStackTrace();
mListener.finishBroadcast();
这样去进行,其中beginBroadcast和finishBroadcast必须要配对出现,哪怕是要获取RemoteCallbackList中的元素个数。
在Manifest文件里面注册Service:
<service
android:name=".AIDLService"
android:process=":remote">
<intent-filter>
<action android:name="com.example.lambert.aidlproject.MyService" />
</intent-filter>
</service>
在Service当中加了几个action,用于别的组件通过Intent隐式启动此Service。
3:客户端的实现
首先客户端也必须添加AIDL文件
这个AIDL的目录名需要跟服务端相同,使用的传输对象也需要跟服务端相同
客户端界面主要是由三个按钮:绑定、解除绑定、向服务器发送消息,然后还有一个显示状态的文本控件。
布局文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.csda.aidl.client.MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/btn_bind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bind"/>
<Button
android:id="@+id/btn_unbind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="unbind"/>
<Button
android:id="@+id/btn_get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="send"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
主Activity如下
package com.csda.aidl.client;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.csda.aidl.service.IMyAidlInterface;
import com.csda.aidl.service.IOnNewPersonArrivedListener;
import com.csda.aidl.service.Person;
public class MainActivity extends Activity implements View.OnClickListener
private IMyAidlInterface mService;
private Button btn_bind, btn_get,btn_unbind;
private TextView tv;
private Handler handler=new Handler()
@Override
public void handleMessage(Message msg)
super.handleMessage(msg);
switch (msg.what)
case 1:
Person person=(Person) msg.obj;
tv.setText(person.getName());
break;
;
private IOnNewPersonArrivedListener listener=new IOnNewPersonArrivedListener.Stub()
@Override
public void onNewPersonArrived(Person person) throws RemoteException
handler.obtainMessage(1,person).sendToTarget();
;
private ServiceConnection connection = new ServiceConnection()
@Override
public void onServiceConnected(ComponentName name, IBinder service)
mService = IMyAidlInterface.Stub.asInterface(service);
try
//设置死亡代理
service.linkToDeath(mDeathRecipient, 0);
catch (RemoteException e)
e.printStackTrace();
try
mService.registListener(listener);
catch (RemoteException e)
e.printStackTrace();
@Override
public void onServiceDisconnected(ComponentName name)
mService = null;
;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
private void init()
btn_bind = (Button) findViewById(R.id.btn_bind);
btn_get = (Button) findViewById(R.id.btn_get);
btn_unbind=(Button) findViewById(R.id.btn_unbind);
tv = (TextView) findViewById(R.id.tv);
btn_bind.setOnClickListener(this);
btn_unbind.setOnClickListener(this);
btn_get.setOnClickListener(this);
@Override
public void onClick(View v)
switch (v.getId())
case R.id.btn_bind:
bind();
break;
case R.id.btn_unbind:
unbind();
break;
case R.id.btn_get:
try
mService.addPeroson(new Person("周盖"));
catch (RemoteException e)
e.printStackTrace();
break;
private void bind()
Intent intent = new Intent();
intent.setAction("com.example.lambert.aidlproject.MyService");
//从 Android 5.0开始 隐式Intent绑定服务的方式已不能使用,所以这里需要设置Service所在服务端的包名
intent.setPackage("com.csda.aidl.service");
bindService(intent, connection, Context.BIND_AUTO_CREATE);
/**
* 监听Binder是否死亡
*/
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient()
@Override
public void binderDied()
if (mService == null)
return;
mService.asBinder().unlinkToDeath(mDeathRecipient, 0);
mService = null;
//重新绑定
bind();
;
private void unbind()
if (connection != null&&mService.asBinder().isBinderAlive())
try
mService.unregistListener(listener);
catch (RemoteException e)
e.printStackTrace();
unbindService(connection);
@Override
protected void onDestroy()
if (connection != null&&mService.asBinder().isBinderAlive())
try
mService.unregistListener(listener);
catch (RemoteException e)
e.printStackTrace();
unbindService(connection);
super.onDestroy();
需要说明的是:
1:在执行bindService的时候,代码如下所示,第三个参数有几个可选项,一般选Context.BIND_AUTO_CREATE,意思是如果在绑定过程中,Service进程被意外杀死了,系统还会自动重新启动被绑定的Service。所以当我们点击KILL PROCESS按钮的时候会杀死Service进程,但是马上又会自动重启,重新调用onServiceConnected方法重新绑定。当然,这个参数还有别的一些选择。
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
2:点击unbind按钮的时候,需要先解注册之前注册的IRemoteServiceCallback回调接口,然后再unbindService。
3:从 Android 5.0开始 隐式Intent绑定服务的方式已不能使用,所以这里需要设置Service所在服务端的包名
4:如果服务端的onNewPersonArrived方法比较耗时的话,请确保该方法运行在非UI线程,同样,如果服务端处理客户端的方法也比较耗时的话,客户端的方法调用也需要运行在非UI线程中
至此,客户端与服务端通过AIDL互相通信介绍到此。
后话
另外,在AIDL中我们还可以加入权限验证
1:第一种方法时在onBind中验证:
验证方式有多种,比如使用permission验证,这种方式我们之间在
<permission android:name="com.csda.aidl.service.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
在onbind方法中:
@Nullable
@Override
public IBinder onBind(Intent intent)
int check=checkCallingPermission("com.csda.aidl.service.ACCESS_BOOK_SERVICE");
if (check== PackageManager.PERMISSION_DENIED)
return null;
return new IMyService();
如果想要绑定到服务中,只需要在它的配置文件中加入:
<uses-permission android:name="com.csda.aidl.service.ACCESS_BOOK_SERVICE"/>
这样就可以绑定到服务中
ps:如果服务端和客户端是两个工程,则在Service中无法验证客户端的权限,因为onBinde方法不是一个binder调用的,它运行在服务端的UI线程,因此在onBind中只能验证服务端的权限,这样就木有意义了,所以推荐使用第二种。
第二种方法,在服务端的onTransact方法中进行权限验证,如果验证失败直接返回false,这也服务端也不会执行AIDL中的方法,从而达到保护服务端的效果。具体的验证方式很多,可以采用permission验证,实现和第一种一样,还可以采用pid和uid来做验证,通过getCallingUid和getCallingPid可以拿到客户端所属应用的uid和pid,通过这种方式可以做一些验证工作,比如包名。下面既验证了permission,又验证了报名。一个应用如果想远程调用服务的方法,首先要使用我们刚才定义的权限,并且包名相同,否则就会调用失败
2:另外一种是在服务端的Binder重写onTransact方法
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException
String packageName = null;
int callingPid = getCallingPid();
int callingUid = getCallingUid();
Log.i(TAG, "callingPid = " + callingPid + ",callingUid = " + callingUid);
String[] packagesForUid = BookService.this.getPackageManager().getPackagesForUid(callingUid);
if (packagesForUid != null && packagesForUid.length > 0)
packageName = packagesForUid[0];
Log.i(TAG, "packageName = " + packageName);
if (TextUtils.isEmpty(packageName) || !"com.csda.aidl.client".equals(packageName))
return false;
return super.onTransact(code, data, reply, flags);
或者直接检测权限
public class IMyService extends IMyAidlInterface.Stub
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException
int check=checkCallingPermission("com.csda.aidl.service.ACCESS_BOOK_SERVICE");
if (check== PackageManager.PERMISSION_DENIED)
return false;
return super.onTransact(code, data, reply, flags);
@Override
public List<Person> getPersonList() throws RemoteException
return persons;
//客户端可以通过调用这个方法想服务端发送消息
@Override
public void addPeroson(Person person) throws RemoteException
//进行相应处理
Log.i("添加人数","添加人数"+persons.size());
persons.add(person);
@Override
public void registListener(IOnNewPersonArrivedListener listener) throws RemoteException
mListener.register(listener);
@Override
public void unregistListener(IOnNewPersonArrivedListener listener) throws RemoteException
mListener.unregister(listener);
3:还可以为Service指定android:permission属性等
以上是关于android studio中使用AIDL进行客户端与服务端互相通信的主要内容,如果未能解决你的问题,请参考以下文章
Android 9使用Android Studio调试系统应用之MTK MLocalMM2移植:新增AIDL部分
Android 9使用Android Studio调试系统应用之MTK MLocalMM2移植:新增AIDL部分