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属性等

代码下载






  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优雅的心情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值