学习framework(01):Service与AIDL

搬砖多年的同学都有一个经验,那就是Android 很多系统级服务包括硬件通信都是一个service。比如说PMS,AMS,WMS等等,我们想要从源码层面去看懂这些服务做了什么?理解跨进程通信就至关重要了,而Android在虚拟机层便提供了一种跨进程的通信方式那就是Service+AIDL,这仅仅是Android 跨进程通信在应用层的体现,我们尝试通过理解Android的Service和AIDL的写法,去理解Binder,OK,那就开整。

正文

话说,大多数小型应用都是单进程,这就导致了Service与AIDL的使用其实很少,但是一些中大项目里面,往往都是框架做好了,直接调用,除非一些特殊的业务场景,比如说,音视频相关的。像一些小程序,webview 就直接封装好了,这就导致写到机会也比较少,但是,用的少不代表能不会,OK,开整。

Service

我们知道Service是4大组件之一,所以必须写到AndroidManifest里面,至于为什么必须写到这里面我们PMS的时候再细说。官网地址

通过最新的API文档来看,Service分为前台Service和后台Service,区别在于前台Service需要权限且必须绑定通知栏,后台Service则没有这个限制,同时启动和关闭也不一样,但是前台Service有版本限制,其他地方几乎是一致的。

生命周期函数

通过生命周期函数,我们可以很好的知道Service能做什么,然后有一些什么限制等等。 Android Service的生命周期函数有以下几个:

  1. onCreate():首次创建服务时,系统将调用此方法。如果服务已经运行,则不会调用此方法,该方法只调用一次。
  2. onStartCommand():当另一个组件通过调用startService()请求启动服务时,系统将调用此方法。
  3. onDestroy():当服务不再使用且将被销毁时,系统将调用此方法。
  4. onBind():当另一个组件通过调用bindService()与服务绑定时,系统将调用此方法。
  5. onUnbind():当另一个组件通过调用unbindService()与服务解绑时,系统将调用此方法。
  6. onRebind():这个方法只在onUnbind()返回true时,才会被调用。当旧的组件与服务解绑后,另一个新的组件与服务绑定时,系统将调用此方法。

onUnbind 的返回值

可以看到,上面onRebind函数的执行是依托于这个函数的返回值的。这个值默认是false。 在Android的Service中,onUnbind()方法返回true和false的区别在于是否允许再次绑定。 如果onUnbind()方法返回true,那么在上次的绑定已经解除后,允许再次进行绑定。换句话说,当一个客户端与Service解绑后,它可以再次绑定到这个Service。 如果onUnbind()方法返回false,则表示只允许绑定一次。一旦客户端与服务解绑,它不能再重新绑定到该服务。

onStartCommand的返回值

在Android中,Service的onStartCommand()方法的返回值有三种,分别是START_STICKYSTART_NOT_STICKYSTART_REDELIVER_INTENT

  1. START_STICKY:当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand()方法,但其中的Intent将为null,除非有挂起的Intent,如pendingIntent。这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
  2. START_NOT_STICKY:当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。
  3. START_REDELIVER_INTENT:当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。

通常而言,这个是用的START_REDELIVER_INTENT,但是还是要看业务场景。主要是一个APP如果真的要考虑到所有地方的内存回收啥的,一般都有高阶带然后走查代码,你没有写,他也会为了业绩,督促你写,或者就是他搭好框框。

启动和绑定服务

启动服务和绑定服务还是有一些区别的,比如说服务没有启动的时候,调用绑定服务服务会启动,执行onCreate,onBind,而且同一个ServiceConnection且action相同的情况下,只能绑一次,如果说一个Service 存在多个action,Service已经创建了,绑定的时候就会调用多次onBind(),而启动服务每次调用都会触发onStartCommand(),所以我们经常看到有Service通过intent传参调函数在onStartCommand里面进行分发的代码,这种情况下是不会绑定服务的,当然也可以绑定服务,就是逻辑乱了点。

启动关闭服务

启动服务很简单,需要获取到服务的class。例如:

kotlin 复制代码
startService(Intent(this, NameService::class.java))

停止服务

kotlin 复制代码
stopService(Intent(this, NameService::class.java))

绑定服务

我们创建一个ServiceConnection 对象。

kotlin 复制代码
private val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder?) {
        LogUtils.e(name)
        nameService = IMyAidlInterface.Stub.asInterface(service)
    }

    override fun onServiceDisconnected(name: ComponentName) {
        nameService=null
        LogUtils.e(name)
    }
}

开始绑定服务:

scss 复制代码
bindService(Intent().apply {
    action="service.setName"
    setPackage("com.example.blogdemo")
}, serviceConnection, Service.BIND_AUTO_CREATE)

可以看到,绑定服务是不需要服务的具体类名的,这个很重要,因为很多系统服务也是通过action 进行获取到的。一开始我以为Service.BIND_AUTO_CREATE 这个flag可以设置多个,但是通常建议只有这一个。更多的flags参考

"BIND_AUTO_CREATE": "如果设置了此标志,当服务被绑定时,如果服务还没有运行,系统会自动创建并启动服务。这是默认的标志,一般不需要显式设置。"

取消绑定服务

scss 复制代码
unbindService(serviceConnection)

AIDL

官网地址 这个玩意可以理解成为了让开发者更简单的使用跨进程通信IPC的一种编码方式。他的目的就是降低开发难度。

环境问题

因为AIDL文件需要被编译成各种class 文件,所以说,这个玩意涉及到编译时技术,所以得把配置打开:

aidl 复制代码
    buildFeatures {
        aidl true
    }

高版本AS默认关闭了。

in out inout 关键字

至于为什么先写这个玩意,因为我下午跑demo他一直报错,而设置了这个就不报错了,麻了。我们先来看一下一个AIDL文件里面写一个接口定义一个函数:

arduino 复制代码
interface IMyAidlInterface {

    void setName(String name);
    }

在Android AIDL中,in、out和inout是用于描述接口中方法参数类型的三种类型。

  1. in:这是默认的参数类型。如果一个参数被标记为in,那么这个参数的值将在方法内部被使用,但不会改变。在AIDL中,如果没有明确指定参数类型,那么默认的类型就是in。
  2. out:如果一个参数被标记为out,那么这个参数的值将在方法内部被改变,并且这个改变后的值将被用于方法外部。通常,这种类型的参数被用于从方法返回一个值。
  3. inout:如果一个参数被标记为inout,那么这个参数的值将在方法内部被改变,并且这个改变后的值将被用于方法外部。但是,这个参数也可能会被使用在方法内部,也就是说,它的值可能会被再次改变。

所以说setName(String name)其实等价于:

scss 复制代码
setName(in String name);

对于自定义的数据类型,统一建议使用inout标记,除非业务诉求特别明晰,大致的不会改了。

AIDL 定义一个接口

这个操作很简单,选择快捷创建方式,创建一个aidl 文件即可。比如说,我们新建一个IWork.aidl文件,然后里面单纯的就一个接口:

csharp 复制代码
package com.example.blogdemo;
interface IWork {
  void refresh();
}

默认情况下,AIDL 支持下列数据类型:

  • Java 编程语言中的所有原语类型(如 intlongcharboolean 等)
  • String
  • CharSequence
  • List List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 List 用作"泛型"类(例如,List<String>)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList
  • Map Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap

定义一个数据类

大致分为2步,第一步是定义数据类。第二步是在AIDL里面声明。

定义数据类

我们定义一个数据类之后,需要实现Parcelable接口,然后写writeToParcel和readFromParcel方法,否则会编译报错,例如:

kotlin 复制代码
class MyBookWork(var name: String?, var code: String?) :Parcelable{
    constructor(parcel: Parcel) : this(
        parcel.readString(),
        parcel.readString()
    ) {
    }


    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeString(code)
    }


    fun toLog(){
        LogUtils.e(name+code)
    }

    override fun describeContents(): Int {
        return 0
    }

    fun readFromParcel(reply: Parcel) {
        name=reply.readString()
        code=reply.readString()
    }

    companion object CREATOR : Parcelable.Creator<MyBookWork> {
        override fun createFromParcel(parcel: Parcel): MyBookWork {
            return MyBookWork(parcel)
        }

        override fun newArray(size: Int): Array<MyBookWork?> {
            return arrayOfNulls(size)
        }
    }
}

可以看到,我们上面还有一个toLog 函数。这也证明了,我们可以不通过定义接口的方式,直接定义数据类,去实现一些对象功能的处理。

设置AIDL

找一个AIDL文件,写上这句话即可。

ini 复制代码
parcelable MyBookWork;

MyBookWork是上面数据类的类名,不写会一直提示这个类找不到。

组合使用

创建AIDL 文件

arduino 复制代码
package com.example.blogdemo;
import com.example.blogdemo.IWork;
import com.example.blogdemo.MyBookWork;
parcelable MyBookWork;
interface IMyAidlInterface {
    void setName(String name);
    String getName();
    void binWork(IWork work);
    void binBook(inout MyBookWork rect);
    void saveRect(in Bundle bundle);
}

我们这存在2个AIDL文件,一个是IWork,一个是IMyAidlInterface。

创建Service

kotlin 复制代码
class NameService : Service() {
    override fun onBind(intent: Intent?): IBinder {
        LogUtils.e("当被一个组件通过bindService 与服务进行绑定时候调用")
        intent?.let {
            LogUtils.e(it.flags)
            it.extras?.keySet()?.forEach { key ->
                LogUtils.e("$key  ${it.extras?.get(key)}")
            }
        }
        return mBinder
    }

    override fun onCreate() {
        super.onCreate()
        LogUtils.e("首次创建服务的时候调用")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        LogUtils.e("当被另一个组件通过startService 请求服务的时候:${flags}  ${startId} ")
        intent?.let {
            LogUtils.e(it.flags)
            it.extras?.keySet()?.forEach { key ->
                LogUtils.e("$key  ${it.extras?.get(key)}")
            }
        }
        return START_REDELIVER_INTENT
    }

    override fun onDestroy() {
        super.onDestroy()
        LogUtils.e("当服务不再被使用的且被销毁的时候")
    }

    override fun onUnbind(intent: Intent?): Boolean {
        LogUtils.e("当一个组件通过unbindService 的时候调用")
        intent?.let {
            LogUtils.e(it.flags)
            it.extras?.keySet()?.forEach { key ->
                LogUtils.e("$key  ${it.extras?.get(key)}")
            }
        }
        return false
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        LogUtils.e("这个方法只在`onUnbind()`返回`true`时,才会被调用。当旧的组件与服务解绑后,另一个新的组件与服务绑定时,系统将调用此方法。")
        intent?.let {
            LogUtils.e(it.flags)
            it.extras?.keySet()?.forEach { key ->
                LogUtils.e("$key  ${it.extras?.get(key)}")
            }
        }
    }


    private val mBinder: IMyAidlInterface.Stub = object : IMyAidlInterface.Stub() {
        var serviceName: String = "default"
        override fun setName(name: String) {
            serviceName = name
        }

        override fun getName(): String {
            return serviceName
        }

        override fun binWork(work: IWork) {
            work.refresh()
        }

        override fun binBook(book: MyBookWork?) {
            book?.let {
                it.toLog()
                LogUtils.e(it.name + it.code)
            }
        }

        override fun saveRect(bundle: Bundle) {
            // 这个bundle里面如果有自定义数据模型,这需要设置一下classLoader
            // 参考:https://developer.android.com/guide/components/aidl?hl=zh-cn
            bundle.classLoader=classLoader
            bundle.getParcelable<Rect>("rect")
        }
    }
}

Androidmanifest 注册

ini 复制代码
        <service android:name=".service.NameService"
            android:enabled="true"
            android:exported="false"
            android:process=".name">
            <intent-filter>
                <action android:name="service.getName"/>
                <action android:name="service.setName"/>
            </intent-filter>
        </service>

每个节点的含义参考官方文档

总结

可以看到,onBind的返回值类型是android.os.IBinder,但是我们定义的接口却没有实现这个接口,那么就一定是编译时技术帮我们处理了,我们还是看简单的IWork.aidl 文件生成的class。在build/generated/saidl_source_output.dir 目录中,可以看到我们生成的IWrok.class。

java 复制代码
public interface IWork extends android.os.IInterface
{
  /** Default implementation for IWork. */
  public static class Default implements com.example.blogdemo.IWork
  {
    @Override public void refresh() throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.blogdemo.IWork
  {
    private static final java.lang.String DESCRIPTOR = "com.example.blogdemo.IWork";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.blogdemo.IWork interface,
     * generating a proxy if needed.
     */
    public static com.example.blogdemo.IWork asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.blogdemo.IWork))) {
        return ((com.example.blogdemo.IWork)iin);
      }
      return new com.example.blogdemo.IWork.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_refresh:
        {
          data.enforceInterface(descriptor);
          this.refresh();
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.blogdemo.IWork
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      @Override public void refresh() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_refresh, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().refresh();
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.example.blogdemo.IWork sDefaultImpl;
    }
    static final int TRANSACTION_refresh = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.example.blogdemo.IWork impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.blogdemo.IWork getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public void refresh() throws android.os.RemoteException;
}

整个编译时技术为我们做了以下几步:

  • 帮我们继承了android.os.IInterface 接口。
  • 实现了一个Default的内部类
  • 实现了一个内部类Stub
  • 实现了一个内部类Proxy

回顾下上面,我们如何通过绑定服务获取到的对象:

ini 复制代码
nameService = IMyAidlInterface.Stub.asInterface(service)

我们正是通过生成的class 获取到了一个IBander 接口,而包含各种入参及其自定义入参的AIDL最终的class是极其复杂的。所以需要Ibander 对象还是建议通过AIDL去实现,如果自己写这一套下来,有点恼火。

最后,主要是要熟悉我们绑定服务获取到的其实是IBinder 的子类接口,其实是真正服务的对象。这个很重要,当我们把这个逻辑捋顺了之后,看进程通信才不会打脑壳。

相关推荐
studyForMokey1 小时前
【Android面试】View绘制流程专题
android·面试·职场和发展
jjinl3 小时前
Android 资源说明
android
恋猫de小郭5 小时前
Swift 6.3 正式发布支持 Android ,它能在跨平台发挥什么优势?
android·前端·flutter
一只会跑会跳会发疯的猴子5 小时前
php操作ssl,亲测可用
android·php·ssl
程序员码歌6 小时前
火爆了,一个Skill搞定AI热点自动化:RSS 聚合 + AI 筛选 + 公众号 + 邮件全流程
android·前端·ai编程
优选资源分享6 小时前
小白转文字 v1.2.8.0 | 安卓离线免费音视频转写工具
android·音视频
安卓机器6 小时前
安卓玩机自做小工具------用于ROM修改 安卓设备联机应用扫描工具 查看应用中文名称 包名 应用路径等
android·修改rom·定制rom·修改系统应用
梦里花开知多少6 小时前
深入理解Android binder线程模型
android·架构
千里马学框架6 小时前
aospc/c++的native 模块VScode和Clion
android·开发语言·c++·vscode·安卓framework开发·clion·车载开发
洞见不一样的自己6 小时前
深度解析Kotlin泛型:从基础到实战
android