Android Bluetooth Framework源码剖析(二)它们都重要

在具体讲述每个模块前我们先来看看一些公共的知识点,如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()时传递该实例,实现步骤如下:

  1. 实现ServiceConnection,必须实现它的两个回调方法:
  • onServiceConnected()
    系统会调用该方法将IBinder返回给客户端
  • onServiceDisconnected()
    系统会在服务的连接意外中断是调用此方法,客户端取消绑定时不会调用该方法。
  1. 调用bindService(),并将第一步实现的ServiceConnection实例传入。
  2. 当系统调用onServiceConnected()时,客户端就可以获得IBinder并调用服务。
  3. 需要断开与服务的连接时调用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的IBinder
		mHandler.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进程),这两个部分之间通过系统调用进行通信。
通信过程如下:

  1. Service进程向ServiceManager进程注册服务
  2. Client进程向ServiceManger进程获取服务的代理,此时Client进程已经获取到Service对外提供的服务的接口。
  3. Client通过代理调用接口,Binder驱动将请求发送到Service
  4. 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提供的这些能力了,看一下蓝牙中的实现:

  1. 创建 .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);
}
  1. 实现接口
    Android SDK 工具会根据您的 .aidl 文件,使用 Java 编程语言生成一个接口,此接口具有一个名为 Stub 的内部抽象类,该类扩展 Binder 并实现 AIDL 接口中的方法,必须扩展 Stub 类并实现相应方法。
public static class AdapterServiceBinder extends IBluetooth.Stub {
	@Override
    public void setName(String name, AttributionSource source, SynchronousResultReceiver receiver) {
        try {
            receiver.send(setName(name, source));// 
        } catch (RuntimeException e) {
            receiver.propagateException(e);
        }
    }
	@Override
	public void getName(AttributionSource source, SynchronousResultReceiver receiver) {
	    try {
	        receiver.send(getName(source));
	    } catch (RuntimeException e) {
	        receiver.propagateException(e);
	    }
	}
}
  1. 向客户端公开该接口
    实现 Service 并替换 onBind(),以返回 Stub 类的实现。
public class AdapterService extends Service
	private AdapterServiceBinder mBinder = new AdapterServiceBinder(this);
	@Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
  1. 客户端绑定到服务
    参考前面 绑定到服务 的内容。

以上步骤都是开发人员需要完成的内容,接下来的内主要有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_setNameTRANSACTION_getName, 该类的构造函数中调用Binder的attachInterface方法想ServiceManager注册服务,在客户端绑定服务时可以获取到服务的IBinder,通过IBinder可以调用到asInterface方法,并获取到IBluetooth,在asInterface方法中还会判断是本地调用还是远程调用,如果是本地调用直接返回本地的IBluetooth,如果是远程调用才返回ProxymBluetooth = 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, 包含以下几部分内容:

  1. 在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");
    }
}
  1. 调用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数组中的元素个数。
  1. 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");
    ...
}
  1. 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);
}
  1. 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();
    ...
}
  1. 在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;
	    }
	}
}

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值