搬砖多年的同学都有一个经验,那就是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的生命周期函数有以下几个:
onCreate()
:首次创建服务时,系统将调用此方法。如果服务已经运行,则不会调用此方法,该方法只调用一次。onStartCommand()
:当另一个组件通过调用startService()
请求启动服务时,系统将调用此方法。onDestroy()
:当服务不再使用且将被销毁时,系统将调用此方法。onBind()
:当另一个组件通过调用bindService()
与服务绑定时,系统将调用此方法。onUnbind()
:当另一个组件通过调用unbindService()
与服务解绑时,系统将调用此方法。onRebind()
:这个方法只在onUnbind()
返回true
时,才会被调用。当旧的组件与服务解绑后,另一个新的组件与服务绑定时,系统将调用此方法。
onUnbind 的返回值
可以看到,上面onRebind函数的执行是依托于这个函数的返回值的。这个值默认是false。 在Android的Service中,onUnbind()方法返回true和false的区别在于是否允许再次绑定。 如果onUnbind()方法返回true,那么在上次的绑定已经解除后,允许再次进行绑定。换句话说,当一个客户端与Service解绑后,它可以再次绑定到这个Service。 如果onUnbind()方法返回false,则表示只允许绑定一次。一旦客户端与服务解绑,它不能再重新绑定到该服务。
onStartCommand的返回值
在Android中,Service的onStartCommand()
方法的返回值有三种,分别是START_STICKY
、START_NOT_STICKY
和START_REDELIVER_INTENT
。
START_STICKY
:当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand()
方法,但其中的Intent将为null,除非有挂起的Intent,如pendingIntent
。这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。START_NOT_STICKY
:当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。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是用于描述接口中方法参数类型的三种类型。
- in:这是默认的参数类型。如果一个参数被标记为in,那么这个参数的值将在方法内部被使用,但不会改变。在AIDL中,如果没有明确指定参数类型,那么默认的类型就是in。
- out:如果一个参数被标记为out,那么这个参数的值将在方法内部被改变,并且这个改变后的值将被用于方法外部。通常,这种类型的参数被用于从方法返回一个值。
- inout:如果一个参数被标记为inout,那么这个参数的值将在方法内部被改变,并且这个改变后的值将被用于方法外部。但是,这个参数也可能会被使用在方法内部,也就是说,它的值可能会被再次改变。
所以说setName(String name)其实等价于:
setName(in String name);
对于自定义的数据类型,统一建议使用inout标记,除非业务诉求特别明晰,大致的不会改了。
AIDL 定义一个接口
这个操作很简单,选择快捷创建方式,创建一个aidl 文件即可。比如说,我们新建一个IWork.aidl文件,然后里面单纯的就一个接口:
package com.example.blogdemo;
interface IWork {
void refresh();
}
默认情况下,AIDL 支持下列数据类型:
- Java 编程语言中的所有原语类型(如
int
、long
、char
、boolean
等) 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开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)