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 Studio中如何创建AIDL

Android 9使用Android Studio调试系统应用之MTK MLocalMM2移植:新增AIDL部分

Android 9使用Android Studio调试系统应用之MTK MLocalMM2移植:新增AIDL部分

Android Studio创建AIDL文件并实现进程间通讯

android studio编译aidl,找不到类

Android Studio AIDL创建案例(解决自动生成java问题)