今天心血来潮来写一篇关于如何在AS下结合AIDL创建和使用远程service。在此之前我先跟大家解释下什么是远程service?以及为什么要使用远程service?相信不管对于安卓新手还是老手而言,对于service并不感到陌生,所以这里就不跟讨论其基础概念和一些知识点了。所谓的远程service意思其实就是提供一个独立于某个app进程而创建的服务,这个服务可以提供给多个app共同使用,当然了,这些app必须拥有访问这个远程service的某种协议或者说接口,而这正是我们今天博文的另一个重点,就是AIDL,所谓的AIDL其实就是安卓接口定义语言,在本篇当中,它将用于提供一个我们访问远程service的一个接口。如果我的表述不是很清楚或者你完全听不懂,那么没关系,我们通过代码和截图一步步帮你搞懂我在本篇所要表达的内容,相信你能明白,因为本篇的内容很简单。
一、"服务端"项目
按照国际惯例,我们先来打开Android Studio并创建一个项目,项目名称随你定,这里我就不演示了。然后在项目中创建一个service。下面我会贴出这个service的截图和代码:
- public class MyService extends Service {
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("mylog", "onCreate");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("mylog","onStartCommand");
- return super.onStartCommand(intent, flags, startId);
- }
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("mylog","onTestBind");
- return null;
- }
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("mylog","unBind");
- return super.onUnbind(intent);
- }
- @Override
- public void onDestroy() {
- Log.i("mylog","onDestroy");
- super.onDestroy();
- }
可以发现,这个自定义的service类中并没有什么特别的东西,都是些重写的方法,当我们调用bindService方法时,就会调用onBind方法了,我们后面会通过logcat来跟踪。我们往下继续。。。。
接下来,我们为了让这个service变为远程的service,需要在AndroidManifest.xml文件中注册并加一个:reomote标识,具体代码如下:
- <service android:name=".service.MyService"
- android:process=":remote">
- </service>
然后,我们来开始本篇的重点,我们那就是使用AIDL来实现我们远程service的使用,我们首先在AS下来创建一个AIDL文件,右键app,然后new一个aidl文件,如下图:
然后输入文件名,点击finish完成创建。请注意这里的AIDL文件所在的路径(包名)要跟项目的包名保持一致。
下面我把我创建出来的AIDL文件代码也贴出来,其实很简单,因为我只写了一个sayHelloWorld方法而已:
- // IMyAidlService.aidl
- package com.example.marktrace003.test;
- // Declare any non-default types here with import statements
- interface IMyAidlService {
- /**
- * Demonstrates some basic types that you can use as parameters
- * and return values in AIDL.
- */
- void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
- double aDouble, String aString);
- void sayHelloWorld();
- }
然后点击Bulid->Make project生成我们的java文件。
这时你会发现我们已经可以调用这个接口并实现它的方法了。接下请注意,我们为了让其他app能够找到我们共享的远程service,需要在服务中加入intent-filter标识,并指定我们的AIDL文件路径。也就是说AndroidManifest.xml文件中的注册代码部分将变为如下:
- <service android:name=".service.MyService"
- android:process=":remote">
- <intent-filter>
- <action android:name="com.example.marktrace003.test.IMyAidlService"/>
- </intent-filter>
- </service>
最后,我们完善下MyService这个类,实现sayHelloWorld方法,并在onBind方法中返回实现了这个方法的binder:
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.support.annotation.Nullable;
- import android.util.Log;
- import com.example.marktrace003.test.IMyAidlService;
- /**
- * Created by Marktrace003 on 2016/7/4.
- */
- public class MyService extends Service {
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("mylog", "onCreate");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("mylog","onStartCommand");
- return super.onStartCommand(intent, flags, startId);
- }
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("mylog","onTestBind");
- return binder;
- }
- IMyAidlService.Stub binder = new IMyAidlService.Stub() {
- @Override
- public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
- }
- @Override
- public void sayHelloWorld() throws RemoteException {
- Log.i("mylog","Hello World");
- }
- };
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("mylog","unBind");
- return super.onUnbind(intent);
- }
- @Override
- public void onDestroy() {
- Log.i("mylog","onDestroy");
- super.onDestroy();
- }
- }
二、“客户端”项目
接下来,我们就可以来新建一个项目了,在这个项目中我们将访问我们上面“服务端”项目的MyService,也就是实现远程service的访问。
同样的,项目名由你来定,然后我们接下来要做的就是把我们编译生成的那个AIDL java文件拷贝到我们的这个新项目中,如何找到这个文件呢?其实不难,在“服务端”项目中,我们切换到package模式下就能找到。
接着将我们的IMyAidlService文件拷贝到我们的新项目即“客户端”项目中,注意这里文件的存放路径要跟“服务端”项目的存放路径一致,也就是“客户端”IMyAidlService的包名要跟服务端存放的包名相同,如下图:
好了,接下来我们“客户端”项目中新建一个Activity,在MainActivity中,我们将演示远程访问“服务端”项目的service,下面直接贴出这个MainActivity的代码:
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- private Button bind_btn;
- private IMyAidlService iMyAidlService;
- private ServiceConnection connection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- iMyAidlService = IMyAidlService.Stub.asInterface(iBinder);
- try {
- iMyAidlService.sayHelloWorld();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- bind_btn = (Button)findViewById(R.id.bind_btn);
- bind_btn.setOnClickListener(this);
- }
- @Override
- public void onClick(View view) {
- switch (view.getId()){
- case R.id.bind_btn:
- Intent intent = new Intent("com.example.marktrace003.test.IMyAidlService");
- intent.setPackage("com.example.marktrace003.test"); //Android5.0之后需要指定共享Service所在应用的应用包名,否则会抛异常
- bindService(intent,connection,BIND_AUTO_CREATE);
- break;
- }
- }
- }
好了,接下来我们运行一下我们这个新建的“客户端”项目。
点击一下绑定按钮,按钮布局我没给出,自己拖一个,我们主要在logcat中看下运行效果图。
看看上图,你会发现不仅打印出了Hello World,而且还调用了onBind方法,也就是说已经访问到了我们那个远程service了。哈哈,是不是很神奇。那么我们本篇的内容到此为止也就结束了,有错误或遗漏之处还请广大读者指出,谢谢大家的阅读!咱们下期见!
下面是另一个实例:
在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);
- }
- package com.csda.aidl.service;
- import com.csda.aidl.service.Person;
- interface IOnNewPersonArrivedListener {
- void onNewPersonArrived(in Person person);
- }
- 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);
- }
- }
然后会在
中看到这两个编译出来的文件
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>
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"/>
- @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);
- }
- }