Android Service aidl使用及进阶

Posted ITRenj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Service aidl使用及进阶相关的知识,希望对你有一定的参考价值。

Android Service 代码地址

AIDL:android 接口定义语言,可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 使用 AIDL 为简化此问题。

注意: 只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,才有必要使用 AIDL。如果无需跨不同应用执行并发 IPC,则应通过实现 Binder 来创建接口;或者,如果想执行 IPC,但不需要处理多线程,请使用 Messenger 来实现接口。

使用AIDL

服务端定义

  1. 创建aidl文件

    此文件定义带有方法签名的编程接口。

  2. 实现接口

    Android SDK 工具会基于定义的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。代码中必须扩展 Stub 类并实现这些方法。

  3. 向客户端公开接口

    实现 Service 并重写 onBind(),从而返回 Stub 类的实现。

创建 .aidl 文件

AIDL 使用一种简单语法,允许通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是 AIDL 生成的其他接口。(AIDL文件的创建和java接口的创建类似,或者就是按照创建java接口的方式(注意:不能像Java 8以上带非抽象方法),然后将文件的扩展名修改为 .aidl)。

AIDL支持的数据:

  • Java 编程语言中的所有基本数据类型(如 int、long、char、boolean 等)
  • String、CharSequence
  • 自定义的Bean(查看以下 “在aidl中使用自定义对象时的步骤或者注意” 内容)
  • List(List 中的所有元素必须是以上列表中支持的数据类型,或者声明的由 AIDL 生成的其他接口或 Parcelable 类型。可以选择将 List 用作“泛型”类(例如,List)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。)
  • Map(Map 中的所有元素必须是以上列表中支持的数据类型,或者声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。)

在aidl中使用自定义对象时的步骤或者注意:

  1. 实现 Parcelable 接口

  2. 创建声明 Parcelable 类的 .aidl 文件(如下: BookBean.aidl)

     package com.renj.service.bean;
    
     parcelable BookBean; // BoodBean 为自定义的对象
    
  3. 自定义的数据即使与aidl接口文件在相同的包内,也需要通过 import 导包。

定义服务接口时,请注意:

  1. 方法可带零个或多个参数,可以有返回值或无返回值
  2. 所有非基本数据类型的参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout。基本数据类型默认为 in,不能是其他方向。(应将方向限定为真正需要的方向,因为编组参数的开销较大。)

in、out 和 inout说明:

in、out、inout表示非基本数据类型的数据的走向标记,三个值都是针对服务端的(服务端接收客户端的输入和服务端输出给客户端),in表示只能输入;out表示只能输出;inout表示既能输入也能输出。

  • in:服务端只能接收输入,不能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息,但是服务端进行修改之后,客户端不会发生改变,还是原来的。

  • out:服务端不能接收输入,只能输出给客户端。当客户端传递BookBean给服务端时,服务端获取到的BookBean对象之后,并不能获取BookBean里面的字段信息(都是默认值),但是服务端修改了这些值后,客户端的信息会发生改变,变成服务端修改之后的值。

  • inout:服务端既能接收输入,也能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息;服务端修改了这些值后,客户端的信息也会发生改变,变成服务端修改之后的值。

      // BoodBean.aidl
      package com.renj.service.bean;
      parcelable BookBean; // BoodBean 为自定义的对象
    
      // IRemoteBook.aidl
      import com.renj.service.bean.BookBean;
      import java.util.List;
    
      interface IRemoteBookBinder 
          void addBookIn(in BookBean bookBean);
      
          void addBookOut(out BookBean bookBean);
      
          void addBookInOut(inout BookBean bookBean);
      
          List<BookBean> getBookList();
      
    

实现接口

Android SDK 工具会基于定义的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类(YourInterface.Stub),用于扩展 Binder 类并实现 AIDL 接口中的方法。代码中必须扩展 Stub 类并实现这些方法。

注意:Stub 还会定义几个辅助方法,其中最值得注意的是 asInterface(),该方法会接收 IBinder(通常是传递给客户端 onServiceConnected() 回调方法的参数),并返回 Stub 接口的实例。

public class IRemoteBookBinderImpl extends IRemoteBookBinder.Stub 
    private List<BookBean> bookBeans = new ArrayList<>();

    @Override
    public void addBookIn(BookBean bookBean) throws RemoteException 
       
    

    @Override
    public void addBookOut(BookBean bookBean) throws RemoteException 
       
    

    @Override
    public void addBookInOut(BookBean bookBean) throws RemoteException 
       
    

    @Override
    public List<BookBean> getBookList() throws RemoteException 
        return bookBeans;
    

向客户端公开接口

在为服务实现接口后,您需要向客户端公开该接口,以便客户端进行绑定。如要为您的服务公开该接口,请扩展 Service 并实现 onBind(),从而返回实现生成的 Stub 的类实例

public class RemoteBookService extends Service 
	public static final String SERVICE_NAME = RemoteBookService.class.getSimpleName();
	
	private IRemoteBookBinderImpl remoteBookBinder;
	
	@Nullable
	@Override
	public IBinder onBind(Intent intent) 
	    remoteBookBinder = new IRemoteBookBinderImpl();
	    return remoteBookBinder;
	


// AndroidManifest.xml 文件声明
<service
    android:name=".remote.RemoteBookService"
    android:exported="true"
    android:label="远程绑定服务"
    android:process="com.renj.remote.binder">
    <intent-filter>
        <action android:name="com.renj.remote.binder" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

现在,当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法所返回的 binder 实例。

客户端还必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的 src/ 目录内必须包含 .aidl 文件(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。(将服务端定义的关于aidl的部分复制一份到客户端,同时保证相关类的包名和服务端保持一致)

当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service),以将返回的参数转换成 YourServiceInterface 类型。例如:

// 绑定服务连接对象
private IRemoteBookBinder iRemoteBookBinder;
private ServiceConnection bindConnection = new ServiceConnection() 
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) 
        iRemoteBookBinder = IRemoteBookBinder.Stub.asInterface(service);
    

    @Override
    public void onServiceDisconnected(ComponentName name) 
        iRemoteBookBinder = null;
    
;

// 绑定远程服务
Intent intent = new Intent();
intent.setAction("com.renj.remote.binder");
intent.setPackage("com.renj.service");
bindService(intent, bindConnection, Service.BIND_AUTO_CREATE);

客户端调用

通过以上操作,当我们在客户端(如 Activity)调用 bindService() 以连接到服务成功并且在onServiceConnected() 回调会接收服务的 onBind() 方法所返回的 binder 实例后,就可以通过该实例调用服务器方法了。

try 
    // addBookIn
    Logger.i("-------- addBookIn() ------------------------");
    int nextIntIn = RandomUtils.randomInt(10000);
    BookBean bookBeanIn = new BookBean("书名-" + nextIntIn, "作者-" + nextIntIn,
            (RandomUtils.randomInt(10000) + 10000) / 100d);
    Logger.i(RemoteBinderService.SERVICE_NAME + " addBookIn() before info:" + bookBeanIn.toString());
    iRemoteBookBinder.addBookIn(bookBeanIn);
    Logger.i(RemoteBinderService.SERVICE_NAME + " addBookIn() after info:" + bookBeanIn.toString());

    // addBookOut
    Logger.i("-------- addBookOut() ------------------------");
    int nextIntOut = RandomUtils.randomInt(10000);
    BookBean bookBeanOut = new BookBean("书名-" + nextIntOut, "作者-" + nextIntOut,
            (RandomUtils.randomInt(10000) + 10000) / 100d);
    Logger.i(RemoteBinderService.SERVICE_NAME + " addBookOut() before info:" + bookBeanOut.toString());
    iRemoteBookBinder.addBookOut(bookBeanOut);
    Logger.i(RemoteBinderService.SERVICE_NAME + " addBookOut() after info:" + bookBeanOut.toString());

    // addBookInOut
    Logger.i("-------- addBookInOut() ------------------------");
    int nextIntInOut = RandomUtils.randomInt(10000);
    BookBean bookBeanInOut = new BookBean("书名-" + nextIntInOut, "作者-" + nextIntInOut,
            (RandomUtils.randomInt(10000) + 10000) / 100d);
    Logger.i(RemoteBinderService.SERVICE_NAME + " addBookInOut() before info:" + bookBeanInOut.toString());
    iRemoteBookBinder.addBookInOut(bookBeanInOut);
    Logger.i(RemoteBinderService.SERVICE_NAME + " addBookInOut() after info:" + bookBeanInOut.toString());

    Logger.i("-------------------------------------------------");
 catch (RemoteException e) 
    e.printStackTrace();

完整代码实现

Binder池

通过以上的介绍,我们知道了通过aidl来调用远程服务的方法,就是定义aidl文件(类似接口),然后实现扩展YourInterface.Stub类,最后通过Service的onBind()方法返回给客户端,客户端获取到了定义的接口对象,就可以实现方法的调用了。通过上面我们能增加和获取BookBean信息了,但是现在我的需求增加了,我不止要获取BookBean信息,我现在增加一个增加和获取PersonBean信息的模块。怎么办了?好办,继续重复上面的步骤,再写一个aidl,创建一个Service返回。这样实现是没有任何问题的,但是随着我后面继续增加更多的模块,那么我们就需要继续的创建Service了,理论上是没有任何问题的。但是就会出现两个问题了

  1. 我们的Service虽然说是在后台运行的,在应用中对用户不可见,但是在Android中可以通过查看所有正在运行的程序查看到当前服务,一看很多不知道有什么作用的东西在运行,让人及其不爽
  2. 当然,相对于第一点,第二点就更加严重了。因为Service作为Android四大组件之一,他的创建、运行和销毁都是需要耗时和消耗资源的,当我们创建了很多的Service时,那么对手机的电池、内存和我们应用的流畅度都是非常不友好的,从而直接影响用户体验,这当然是不可忍受的。

我们在使用线程时,为了避免线程的不断创建与销毁,使用线程池。那么在Service中是不是也可以利用这种思想了,答案是可以的,我们可以通过Binder连接池来处理对多个远程的aidl接口进行调用。具体的思路就是:通过一个aidl接口,这个接口的功能就是客户端通过不同的参数来获取对应的IBinder对象,这样的话,我们只需要创建一个Service返回这个aidl接口,在客户端调用这个接口方法,通过传递不同的参数来获取不同的IBinder对象即可。(Service相当于Binder池,返回不同IBinder对象的aidl接口实现有点类似简单工厂模式)

  1. 定义返回不同IBinder的aidl文件 IBinderPoolBinder.aidl

     // IBinderPoolBinder.aidl
     interface IBinderPoolBinder 
         IBinder getBinder(int type);
     
    
  2. 扩展 IBinderPoolBinder.Stub IBinderPoolBinderImpl

     // IBinderPoolBinder 实现类
     public class BinderPoolBinderImpl extends IBinderPoolBinder.Stub 
         public final static int BINDER_TYPE_BOOK = 0;
         public final static int BINDER_TYPE_PERSON = 1;
         public final static int BINDER_TYPE_STRING = 2;
     
         private RemoteBookBinderImpl remoteBookBinder;
         private RemotePersonBinderImpl remotePersonBinder;
         private RemoteStringBinderImpl remoteStringBinder;
     
         @Override
         public IBinder getBinder(int type) throws RemoteException 
             switch (type) 
                 case BINDER_TYPE_BOOK:
                     if (remoteBookBinder == null)
                         remoteBookBinder = new RemoteBookBinderImpl();
                     return remoteBookBinder;
                 case BINDER_TYPE_PERSON:
                     if (remotePersonBinder == null)
                         remotePersonBinder = new RemotePersonBinderImpl();
                     return remotePersonBinder;
                 case BINDER_TYPE_STRING:
                     if (remoteStringBinder == null)
                         remoteStringBinder = new RemoteStringBinderImpl();
                     return remoteStringBinder;
             
             return null;
         
     
    
  3. 通过Service返回IBinderPoolBinder BinderPoolService

     public class BinderPoolService extends Service 
     
         private BinderPoolBinderImpl binderPoolBinder;
     
         @Nullable
         @Override
         public IBinder onBind(Intent intent) 
             binderPoolBinder = new BinderPoolBinderImpl();
             return binderPoolBinder;
         
     
    
  4. 客户端使用 BinderPoolManager

     // 绑定池aidl
     private IBinderPoolBinder iBinderPoolBinder;
     // ServiceConnection 对象
     private ServiceConnection bindConnection = new ServiceConnection() 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) 
             iBinderPoolBinder = IBinderPoolBinder.Stub.asInterface(service);
         
    
         @Override
         public void onServiceDisconnected(ComponentName name) 
    
         
     ;
    
     // 绑定服务
     Intent intent = new Intent();
     intent.setAction("com.renj.remote.pool");
     intent.setPackage("com.renj.service");
     applicationContext.bindService(intent, bindConnection, Service.BIND_AUTO_CREATE);
    
     // 根据类型返回 IBinder
     if (iBinderPoolBinder != null) 
         try 
             IBinder iBinder =  iBinderPoolBinder.getBinder(binderType);
          catch (RemoteException e) 
             e.printStackTrace();
         
     
    

Android Service 完整代码实现

以上是关于Android Service aidl使用及进阶的主要内容,如果未能解决你的问题,请参考以下文章

Android Service 流程分析

Android进阶——借鉴FlutterEngine,实现子进程渲染视图

Android进阶——借鉴FlutterEngine,实现子进程渲染视图

Android进阶——借鉴FlutterEngine,实现子进程渲染视图

谈谈Android Binder机制及AIDL使用

Android进阶笔记:AIDL内部实现详解