在具体讲述每个模块前我们先来看看一些公共的知识点,如Binder、JNI、Service、AIDL、Broadcast等,它们都是Android/Java基础的知识点,在网络上有许多相关的文章,本文就不深入的讲述它们,但在Bluetooth Framework或者app中都大量的使用了它们,所以本文主要是结合代码做一个回顾,后续文章可能一笔带过,不再花费篇幅讲解,主要集中在具体代码逻辑中。
Service
Service是一种可在后台执行长时间运行的操作的应用组件。不提供界面。启动后,即使用户切换到其他应用,服务也可能会继续运行一段时间。此外,组件可以绑定到服务以与其交互,甚至可以执行进程间通信 (IPC)。例如,服务可以在后台处理网络事务、播放音乐、执行文件 I/O 或与 content provider 交互。
引用自googl开发文档 https://developer.android.com/develop/background-work/services?hl=zh-cn
服务有三种类型:前台服务、后台服务、绑定服务,在Android蓝牙中使用的都是绑定服务,因此另外两种就不做过多介绍,主要看看绑定服务。
服务的实现
实现一个Service,需要实现一个Service
的子类或者使用现有子类,在蓝牙服务中AdapterService
就是它的子类(public class AdapterService extends Service {}
),ProfileService
继承自Service
,而其他所有的Profile如A2DP、GATT、HFP等都是ProfileService
的子类,然后实现一些回调方法:
onStartCommand()
当其他组件调用startService()
请求启动服务时会执行该方法,启动后服务在后台无限期运行,直到调用stopSelf()
或者stopService()
。每次调用startService()
时都会调用该方法,如果只想提供绑定,则不需要实现该方法,如AdapterService
,而ProfileService
则实现了该方法,即可以绑定也可以直接启动。
public class AdapterService extends Service {private void setProfileServiceState(Class service, int state) {Intent intent = new Intent(this, service);intent.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED);intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);startService(intent); //启动或停止Profile服务}
}public abstract class ProfileService extends Service {public int onStartCommand(Intent intent, int flags, int startId) {...String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);if (state == BluetoothAdapter.STATE_OFF)doStop(); //由各具体的Profile Service实现else if (state == BluetoothAdapter.STATE_ON)doStart(); //由各具体的Profile Service实现}return PROFILE_SERVICE_MODE;}
}
onBind()
其他组件想要绑定服务时,则需要实现该方法,当调用bindService()
时会调用此方法,它必须返回一个IBinder
以提动接口供客户端和服务端通信,如果不允许绑定也需要实现此方法,返回null
即可。
public class AdapterService extends Service {private AdapterServiceBinder mBinder;public IBinder onBind(Intent intent) {debugLog("onBind()");return mBinder;}
}public class BluetoothManagerService extends IBluetoothManager.Stub {boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) {ComponentName comp = resolveSystemService(intent, mContext.getPackageManager(), 0);intent.setComponent(comp);//绑定服务if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) {Log.e(TAG, "Fail to bind to: " + intent);return false;}return true;}
}
onCreate()
首次创建服务时(调用onStartCommand()
或onBind()
之前)会调用此方法,即该方法只会调用一次。
public abstract class ProfileService extends Service {public void onCreate() {super.onCreate();mAdapter = BluetoothAdapter.getDefaultAdapter();mBinder = initBinder();create();}
}
onDestroy()
当服务不再使用且即将被销毁是掉用此方法,以清理资源。
除了实现上面方法外,还需要在AndroidManifest.xml文件中声明服务,通过intent-filter标签声明可以过滤的action,在启动或绑定时只有声明中指定的action才可以启动或者绑定。
<application><service android:process="@string/process"android:name="com.android.bluetooth.btservice.AdapterService"android:exported="true"android:permission="android.permission.ACCESS_BLUETOOTH_SHARE"><intent-filter><action android:name="android.bluetooth.IBluetooth"/></intent-filter></service>
</application>
启动服务和绑定服务生命周期如下:
服务可以同时支持启动(startService()
)和绑定(bindService()
)两种方式,只需要同时实现onStartCommand()
以及onBind()
返回一个非空的IBinder
,当使用startService()
启动服务后,所有客户端去掉绑定时服务不会停止,只能通过调用stopSelf()
或者stopService()
停止服务。蓝牙中的Profile就是这样实现的。具体代码参考前面的onStartCommand()
和onBind()
处的描述。
绑定服务创建
蓝牙中的服务都是绑定服务,它们为App提供了进程间通信的接口,绑定服务的的创建有三种方式:扩展Binder类、使用Messenger、使用AIDL,同样的蓝牙中只使用了AIDL,因此重点分析AIDL,这里简单看看它们的区别和使用场景(扩展Binder类和使用Messenger可以参考google开发文档的详细描述)。
- 扩展Binder类
服务仅供自己的应用专用,服务与客户端在同一进程的场景下优先使用。 - 使用Messenger
需要在不同进程间通信,且Messenger会在单个进程中创建包含所有请求的队列的场景下使用,这是跨进程通信最简单的方式,它不需要对服务做线程安全处理。 - 使用AIDL
如果需要让服务同时处理多个请求,则可以直接使用AIDL,编程时只需要定义编程接口的aidl
文件,然后实现对应的接口扩展即可实现跨进程通信。采用 Messenger 的方法实际上是以 AIDL 作为其底层原理。
绑定到服务
客户端可以通过bindService()
绑定到服务,然后系统会调用服务的onBind()
方法,该方法返回可以和服务交互的IBinder
,绑定操作是异步的,客户端要接收IBinder
,需要创建一个ServiceConntion
实例,调用bindService()
时传递该实例,实现步骤如下:
- 实现
ServiceConnection
,必须实现它的两个回调方法:
onServiceConnected()
系统会调用该方法将IBinder
返回给客户端onServiceDisconnected()
系统会在服务的连接意外中断是调用此方法,客户端取消绑定时不会调用该方法。
- 调用
bindService()
,并将第一步实现的ServiceConnection
实例传入。 - 当系统调用
onServiceConnected()
时,客户端就可以获得IBinder
并调用服务。 - 需要断开与服务的连接时调用
unbindService()
。
以BluetoothManagerService.java中绑定AdapterService
为例。
private class BluetoothServiceConnection implements ServiceConnection {public void onServiceConnected(ComponentName componentName, IBinder service) {Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);...msg.obj = service; //获得Service的IBindermHandler.sendMessage(msg);}public void onServiceDisconnected(ComponentName componentName) {Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);...mHandler.sendMessage(msg);}
}case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: {...IBinder service = (IBinder) msg.obj;mBluetooth = IBluetooth.Stub.asInterface(service); //获得Service提供的接口...
}//绑定服务
private BluetoothServiceConnection mConnection = new BluetoothServiceConnection();
Intent i = new Intent(IBluetooth.class.getName()); //AndroidManifest.xml文件设置的intent-filter过滤标签
doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT));// 调用接口
mBluetooth.enable(quietMode, attributionSource, recv);
mBluetooth.getName(attributionSource, recv);
接下来将剖析一下AIDL的实现,在分析AIDL之前先看看Binder机制。
Binder
Binder是Android中一种实现跨进程通信的方式,底层实现了Binder驱动,用于连接Service进程、Client进程、ServiceManager进程,上层实现的Binder类,它实现了IBinder接口,为进程间通信提供接口。Binder的模型如下:
从Android系统角度分为两部分: Android系统框架部分(由系统框架实现,是一个通用的框架)、Android应用层部分(不同应用的client和service实现不同);从进程角度分为两部:内核空间(Binder驱动)、用户空间(ServiceManager进程、Client进程、Service进程),这两个部分之间通过系统调用进行通信。
通信过程如下:
- Service进程向ServiceManager进程注册服务
- Client进程向ServiceManger进程获取服务的代理,此时Client进程已经获取到Service对外提供的服务的接口。
- Client通过代理调用接口,Binder驱动将请求发送到Service
- Service收到调用请求后执行真正的函数调用,然后将reply通过Binder驱动返回给Client。
整个过程从开发到使用,都不会感知到Binder驱动和ServiceManager进程的存在,给人的感觉就像是Client进程和Service进程在进行通信。此外,Android Binder机制是很高效的,Android底层是基于Linux kernel的,我们都知道用户空间和内核空间的内存不能直接相互访问,需要调用copy_from_user()
和copy_to_user()
函数进行拷贝,Linux常见的进程通信机制如pipe、FIFO、socket等都需要进程两次拷贝,即进程A -> 内核 -> 进程B,而Binder则只需要一次拷贝,实现原理如下:
当发送进程和接收进程和Binder驱动建立连接时,在接收进程和kernel之间映射一片内存(调用mmap()
),此时内核空间和用户空间之间传输数据不需要拷贝可以直接访问,当发送进程从用户空间将数据拷贝到内核间时,通知接收进程从映射内存在获取数据(内核空间中内存本身就是共享的),此时从发送进程将数据传输到接收进程就只使用了一次拷贝。
为什么不在两个进程之间直接使用内存映射?
直接内存映射虽然不需要拷贝数据,但没有Client与Service之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题,而Binder驱动采用了C/S架构,Client端有什么需求,直接发送给Server端去完成,架构清晰明朗,Server端与Client端相对独立,稳定性较好,其性能也仅次于内存映射。
AIDL也是基于Binder机制实现的,下面将详细分析一下AIDL的实现。
AIDL
前面了解了绑定服务的三种实现方式,蓝牙中的所有服务都不是仅仅供自己的应用专用,它们需要向开发者提供蓝牙的各种能力,因此使用AIDL(Android Interface Definition Language)是最适合的。在实际的开发过程中,我们不需要自己去从头开始实现整个AIDL的底层机制,Android SDK提供了一套完整的方案,开发时只需要编写.aidl
文件,然后在Service中实现.aidl
文件中定义的接口,最后Client就可以调用Service提供的这些能力了,看一下蓝牙中的实现:
- 创建 .aidl 文件
此文件定义带有方法签名的编程接口,以IBluetooth.aidl部分接口为例,
package android.bluetooth;
import android.app.PendingIntent;
...
interface IBluetooth
{oneway void setName(in String name, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);oneway void getName(in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
}
- 实现接口
Android SDK 工具会根据您的 .aidl 文件,使用 Java 编程语言生成一个接口,此接口具有一个名为 Stub 的内部抽象类,该类扩展 Binder 并实现 AIDL 接口中的方法,必须扩展 Stub 类并实现相应方法。
public static class AdapterServiceBinder extends IBluetooth.Stub {@Overridepublic void setName(String name, AttributionSource source, SynchronousResultReceiver receiver) {try {receiver.send(setName(name, source));// } catch (RuntimeException e) {receiver.propagateException(e);}}@Overridepublic void getName(AttributionSource source, SynchronousResultReceiver receiver) {try {receiver.send(getName(source));} catch (RuntimeException e) {receiver.propagateException(e);}}
}
- 向客户端公开该接口
实现 Service 并替换 onBind(),以返回 Stub 类的实现。
public class AdapterService extends Serviceprivate AdapterServiceBinder mBinder = new AdapterServiceBinder(this);@Overridepublic IBinder onBind(Intent intent) {return mBinder;}
}
- 客户端绑定到服务
参考前面 绑定到服务 的内容。
以上步骤都是开发人员需要完成的内容,接下来的内主要有Android SDK中的工具完成,它会将.aidl
文件转换成什么,如何实现的Client和Service的通行。
编译之后,.aidl
文件会生成一个同名的但将后缀替换为.java
的文件,其内容如下(以IBluetooth.aidl为例,仅保留了核心代码):
package android.bluetooth;
public interface IBluetooth extends android.os.IInterface
{public static abstract class Stub extends android.os.Binder implements android.bluetooth.IBluetooth{private static final java.lang.String DESCRIPTOR = "android.bluetooth.IBluetooth";public Stub() { this.attachInterface(this, DESCRIPTOR); }public static android.bluetooth.IBluetooth asInterface(android.os.IBinder obj){android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof android.bluetooth.IBluetooth)))return ((android.bluetooth.IBluetooth)iin);return new android.bluetooth.IBluetooth.Stub.Proxy(obj);}public android.os.IBinder asBinder() { return this; }public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags){java.lang.String descriptor = DESCRIPTOR;switch (code){case TRANSACTION_setName: {java.lang.String _arg0 = data.readString();boolean _result = this.setName(_arg0);reply.writeInt(((_result)?(1):(0)));return true;}case TRANSACTION_getName: {java.lang.String _result = this.getName();reply.writeString(_result);return true;}}}private static class Proxy implements android.bluetooth.IBluetooth{private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) { mRemote = remote; }public android.os.IBinder asBinder() { return mRemote; }public boolean setName(java.lang.String name){android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();_data.writeString(name);mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);return (0!=_reply.readInt());}public java.lang.String getName(){android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);return _reply.readString();}}static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 7);static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 8);}public boolean setName(java.lang.String name);public java.lang.String getName();
}
总体可分为三个部分:
-
IBluetooth
它是一个接口,即IBluetooth.aidl文件中定义的接口,直接转换成了java的interface
,格式也基本相同,aidl文件中定义了几个,这里就有几个接口,如setName()
、getName()
。 -
IBluetooth.Stub
它是Service端的一个抽象类且继承了IBluetooth
中的接口,因此Service必须继承Stub
并实现里面的所有接口(前面已经介绍过),当收到Client Proxy调用时它会回调onTransace
方法,在该方法中根据code
判断调用子类实现的具体方法,如this.setName()
、this.getName()
。code
是一组不会重复的整数,如TRANSACTION_setName
、TRANSACTION_getName
, 该类的构造函数中调用Binder的attachInterface
方法想ServiceManager
注册服务,在客户端绑定服务时可以获取到服务的IBinder
,通过IBinder
可以调用到asInterface
方法,并获取到IBluetooth
,在asInterface
方法中还会判断是本地调用还是远程调用,如果是本地调用直接返回本地的IBluetooth
,如果是远程调用才返回Proxy
,mBluetooth = IBluetooth.Stub.asInterface(service);
-
IBluetooth.Stub.Proxy
它是Client端对Service端的代理实现,它不是一个抽象类可以直接创建实例,它也继承了IBluetooth
,并在类中实现了这些接口,不过这些接口都是通过调用远程IBinder中的transact
方法发起远程调用。
这几个类和接口之间的关系如下:
JNI
JNI(Java Native Interface)是Java字节码与C/C++进行交互的方法,简单来说即JNI可以实现Java调用C/C++,或则C/C++调用Java。在Android中有很多引用第三发的库或者有些模块对性能要求很高,它们都可能是C/C++实现的,蓝牙协议栈Flouride就是其中之一。这里不会介绍JNI的底层原理,只会描述Framework和Native之间蓝牙JNI的使用,代码实现路径为android/app/jni, 包含以下几部分内容:
- 在app启动时调用
System.loadLibrary()
加载C++实现的Java与C++之间交互的代码,生成的动态库为libbluetooth_jni.so
,编译脚本在Android.bp文件中。
cc_library_shared {name: "libbluetooth_jni",defaults: ["fluoride_basic_defaults"],srcs: ["jni/**/*.cpp"],
}
加载动态库的代码如下:
public class AdapterApp extends Application {static {System.loadLibrary("bluetooth_jni");}
}
- 调用
System.loadLibrary()
加载动态库时会调用动态库中实现的JNI_OnLoad()
,该函数是JNI初始化的入口,函数原型为jint JNI_OnLoad(JavaVM* jvm, void* reserved)
。在该函数中调用jniRegisterNativeMethods()
注册Native实现的方法,函数原型为int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
。
- env:Java环境变量,通过
jvm->GetEnv()
获取 - className:Java中Class的名称,如:
"com/android/bluetooth/btservice/AdapterService"
。 - gMethods:C++中实现的方法的列表,类型
JNINativeMethod
的定义如下:
typedef struct {const char* name; //java中声明的方法名称const char* signature; // 函数签名,即参数、返回值等定义void* fnPtr; // C++中对应的该方法的函数地址
} JNINativeMethod;
AdapterService
部分方法定义如下:
static JNINativeMethod sMethods[] = {{"classInitNative", "()V", (void*)classInitNative},{"initNative", "(ZZI[Ljava/lang/String;ZLjava/lang/String;)Z", (void*)initNative},{"cleanupNative", "()V", (void*)cleanupNative},{"enableNative", "()Z", (void*)enableNative},{"disableNative", "()Z", (void*)disableNative},...
};
- numMethods: gMethods数组中的元素个数。
- C++中获取Java中实现的方法,首先调用
env->FindClass()
查找java的class实例,FindClass()
方法的参数是java class的名称,如:"com/android/bluetooth/btservice/JniCallbacks"
,然后调用env->GetMethodID()
获取JniCallbacks
中实现的方法,参数包括获取到的java class的实例、方法名称、函数签名。
static void classInitNative(JNIEnv* env, jclass clazz) {...jclass jniCallbackClass =env->FindClass("com/android/bluetooth/btservice/JniCallbacks");method_oobDataReceivedCallback =env->GetMethodID(jniCallbackClass, "oobDataReceivedCallback","(ILandroid/bluetooth/OobData;)V");method_stateChangeCallback =env->GetMethodID(jniCallbackClass, "stateChangeCallback", "(I)V");...
}
- C++中获取Java中的class实例,调用方法,先通过
env->GetFieldID()
获取实例,参数包括需要获取的实例对象的实例、实例的成员名称、实例的类型,最后调用方法。
static void classInitNative(JNIEnv* env, jclass clazz) {sJniCallbacksField = env->GetFieldID(clazz, "mJniCallbacks", "Lcom/android/bluetooth/btservice/JniCallbacks;");
}static bool initNative(JNIEnv* env, jobject obj, ...) {sJniCallbacksObj =env->NewGlobalRef(env->GetObjectField(obj, sJniCallbacksField));
}static void adapter_state_change_callback(bt_state_t status) {...sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_stateChangeCallback,(jint)status);
}
- Java声明并调用C++的方法,注意Java的Class名称必须和第二中
jniRegisterNativeMethods()
传入的名称相同,声明的方法名称也必须和传入数组中的方法名称相同。
public class AdapterService extends Service {static {classInitNative(); //调用方法}...public void onCreate() {...initNative(mUserManager.isGuestUser(), isCommonCriteriaMode(), configCompareResult,getInitFlags(), isAtvDevice, getApplicationInfo().dataDir);}...static native void classInitNative();native boolean initNative(boolean startRestricted, boolean isCommonCriteriaMode,int configCompareResult, String[] initFlags, boolean isAtvDevice,String userDataDirectory);native void cleanupNative();/*package*/native boolean enableNative();/*package*/native boolean disableNative();...
}
- 在java中实现C++中使用的Class,并创建实例,注意Class名称必须和第3中
FindClass()
传入的名称相同,实例名称必须和第4中GetFieldID()
传入的名称、类型相同。
public class AdapterService extends Service {...private JniCallbacks mJniCallbacks;public void onCreate() {...mJniCallbacks = new JniCallbacks(this, mAdapterProperties);}
}final class JniCallbacks {...void stateChangeCallback(int status) {mAdapterService.stateChangeCallback(status);}void discoveryStateChangeCallback(int state) {mAdapterProperties.discoveryStateChangeCallback(state);}...
}
Broadcast
Android 应用可以从 Android 系统和其他 Android 应用发送或接收广播消息,类似于发布-订阅设计模式。这些广播会在相关事件发生时发送。例如,Android 系统会在发生各种系统事件时发送广播,如系统启动或设备开始充电时。应用还可以发送自定义广播,例如,通知其他应用它们可能感兴趣的内容(例如,一些新数据已下载)。
系统会优化广播的传送,以保持最佳的系统运行状况。因此,广播的传递时间无法保证。需要进行低延迟进程间通信的应用应考虑绑定服务。
应用可以注册接收特定的广播。发送广播后,系统会自动将广播路由到已订阅接收该特定类型的广播的应用。
一般来说,广播可用作跨应用和正常用户流之外的消息传递系统。但是,您必须格外小心,不要滥用在后台响应广播和运行作业的机会,否则可能会导致系统性能变慢。
引用自google开发文档 https://developer.android.com/develop/background-work/background-tasks/broadcasts?hl=zh-cn
在蓝牙Framework中Broadcast用于通知各状态变化,如:
// 发送端
Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);// 添加附带的数据
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);// 设置flag
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Utils.sendBroadcast(mA2dpService, intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());//接收端
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);// 设置关注的action
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);//注册接收器
mAdapterService.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);private final BroadcastReceiver mReceiver = new BroadcastReceiver() {public void onReceive(Context context, Intent intent) {String action = intent.getAction();// 获取数据final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);final int previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);final int currentState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);switch (action) {// 根据action做处理case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:...break;}}
}