Android Framework之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。例如:

startService(Intent(this, NameService::class.java))

停止服务
stopService(Intent(this, NameService::class.java))

绑定服务

我们创建一个ServiceConnection 对象。

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)
    }
}

开始绑定服务:

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

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

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

取消绑定服务
unbindService(serviceConnection)

AIDL

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

环境问题

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

    buildFeatures {
        aidl true
    }

高版本AS默认关闭了。

in out inout 关键字

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

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)其实等价于:

setName(in String name);

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

AIDL 定义一个接口

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

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方法,否则会编译报错,例如:

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文件,写上这句话即可。

parcelable MyBookWork;

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

组合使用

创建AIDL 文件

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

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 注册

        <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。

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

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

nameService = IMyAidlInterface.Stub.asInterface(service)

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

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

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值