在上一篇文章中我们详细介绍了蓝牙打开过程中,jni之上的各个方方面面,应该说涉及到的地方全部讲清楚了,从这一章开始就来讲解一下打开过程到了jni之下都做了些什么。
为什么取名为偷偷摸摸,因为从这里往下在互联网上就基本找不到任何资料了,大家都是凭借函数的名字去猜测一下做了一些什么,然后继续回到jni之上去分析了。然而仅了解那些明面上的东西对我们分析蓝牙来说显然是不够的,我们必须要一探究竟,看看jni之下都偷偷摸摸做了些什么。
这一章节我们将从这几个方面来分析:
1)Service Record的创建
2)enable native
3)profile的使能
本篇博文将会主要分析第一点:Service Record的创建。
1、ServiceRecord的创建
Service Record通俗一点来讲就是用来保存我们所支持service的一个记录。他是用来为SDP服务的,所谓的SDP就是Service Discovery Profile,从名字上可以看到就是用来发现支持哪些服务的Profile。那这些在具体的应用中有什么作用呢?
举一个例子,比如说我想通过蓝牙发送一个文件给对方的设备,可是我怎么知道对方能不能接收文件呢,我总不能从手机上发送一个文件给蓝牙耳机吧。所以,需要我们在配对的时候知道对方支持不支持文件的接收,这就是通过SDP来获知的,而对方所支持的内容(比如说接收文件的能力,我们称之为opp)就是保存在它的Service Record中的。
通过上面的例子,我们也就不难理解为什么Service Record需要在蓝牙打开的过程中创建了。好了,废话不多说,我们直接去看关于service Record,代码中是如何实现的。
1.1、jni中addReservedServiceRecordsNative的分析
基本这个函数就是调用AddReservedServiceRecords的dbus函数,然后根据reply来返回hanles。
static jintArray addReservedServiceRecordsNative(JNIEnv *env, jobject object, jintArray uuids) { LOGV("%s", __FUNCTION__);#ifdef HAVE_BLUETOOTH DBusMessage *reply = NULL; native_data_t *nat = get_native_data(env, object); jint* svc_classes = env->GetIntArrayElements(uuids, NULL); if (!svc_classes) return NULL; int len = env->GetArrayLength(uuids); //会调用到bluez中的AddReservedServiceRecords函数,分析见下面的1.2 reply = dbus_func_args(env, nat->conn, get_adapter_path(env, object), DBUS_ADAPTER_IFACE, "AddReservedServiceRecords", DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &svc_classes, len, DBUS_TYPE_INVALID); env->ReleaseIntArrayElements(uuids, svc_classes, 0); //根据reply来返回handle,handle是用来标识对应的record的 return reply ? extract_handles(env, reply) : NULL;#endif return NULL;}
1.2、bluez中的AddReservedServiceRecords函数分析
我们在bluez中可以轻易找到一下语句:
{"AddReservedServiceRecords", "au", "au", add_reserved_service_records },
我就不具体解释这些语句都是什么意思,你只要了解在jni层调用AddReservedServiceRecords后,事实上会进入到add_reserved_service_records函数中去执行,所以,我们就直接去看该函数都做了些什么:
static DBusMessage *add_reserved_service_records(DBusConnection *conn, DBusMessage *msg, void *data) { DBusMessage *reply; struct btd_adapter *adapter = data; uint32_t *svc_classes; uint32_t *handles; uint32_t len, i; int ret;//得到传入的参数,即传入的uuid array,保存到svc_classed中 if (dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &svc_classes, &len, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg);//申请uuid array空间 handles = g_malloc0(sizeof(uint32_t) * len);//根据不同的uuid,加入不同的profile//我们在上面有hsp_ag,opp,hfp_ag,pbap//所以,下面4个是都需要的,我们将在1.2.1中详细分析,//因为这4个其实是类似的,所以,我选择了其中opp来进行分析 for (i = 0; i < len; i++) { switch (svc_classes[i]) { case PBAP_PSE_SVCLASS_ID: ret = add_pbap_pse_record(adapter); break; case HEADSET_AGW_SVCLASS_ID: ret = add_headset_ag_record(adapter); break; case HANDSFREE_AGW_SVCLASS_ID: ret = add_handsfree_ag_record(adapter); break; case OBEX_OBJPUSH_SVCLASS_ID: ret = add_opush_record(adapter); break; } if (ret < 0) { g_free(handles); return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Failed to add sdp record"); } else handles[i] = ret; }//就是把handles返回,他的每一个元素表明了一个add的返回值,用于上层对结果的判断。 reply = dbus_message_new_method_return(msg); dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &handles, len, DBUS_TYPE_INVALID); g_free(handles); return reply;}
1.2.1、add_opush_record的分析
在正式分析add_opush_record函数之前,我们需要先了解一下spec中对service record是如何定义的。否则,我们匆忙去看下面的代码,十有八九会是一头雾水。
在蓝牙spec的SDP这一节中,明确指出,一个Servicerecord是由多个Service Attribute组成的。如下图1所示:
图1 Servicerecord的组成
每一个ServiceAttribute又会分为两个部分,一个是Attribute ID,一个是Attribute Value。他的组成如下图2所示。
图2 ServiceAttribute的组成
其中Attribute ID就是一个16bit的数字,用来区分各个Attribute。那个每一个ID所表示的Attribute见下图3,这是蓝牙组织的官网上截图下来的,若想了解更多,可以直接去访问以下网址:https://www.bluetooth.org/zh-cn/specification/assigned-numbers/service-discovery
图3,Attribute ID列表
这里再补充说明一下的是在ServiceRecord中,各个service Attribute是根据Attribute ID由小到大进行排序的。
好了,有了以上的知识,我想下面再来看add_opush_record这个函数的实现就会简单很多了。
static int add_opush_record(struct btd_adapter *adapter){//sdp的列表 sdp_list_t *svclass_id, *pfseq, *apseq, *root;//opp的架构,是opp->obex->rfcomm->l2cap//所以在proto的list中就会包含obex,rfcomm和l2cap uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;//sdp profile descriptor就是uuid和version sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[3]; sdp_record_t *record; uint8_t u8 = 12; sdp_data_t *channel;//用于支持的format,这里是android,所以只支持vcard 2.1,vcard 3.0以及any type of object#ifdef ANDROID uint8_t formats[] = { 0x01, 0x02, 0xff };#else uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff };#endif void *dtds[sizeof(formats)], *values[sizeof(formats)]; unsigned int i; uint8_t dtd = SDP_UINT8; sdp_data_t *sflist; int ret = 0;//申请sdp_record_t的空间,没有什么好看的,就是一个malloc record = sdp_record_alloc(); if (!record) return -1;// PUBLIC_BROWSE_GROUP是一个UUID,他是一个Attribute Value,//他对应的Attribute ID是Browse group list也就是0x0005//这里就是先创建PUBLIC_BROWSE_GROUP sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid);//然后把它加入Browse group list对应的Attribute ID中//这两个函数的具体实现我就不深入分析了,运用了多个单向链表,大家仔细分析一下其实对C语言会有好处,哈哈~~ sdp_set_browse_groups(record, root);//这里的OBEX_OBJPUSH_SVCLASS_ID是service class Id attribute的value sdp_uuid16_create(&opush_uuid, OBEX_OBJPUSH_SVCLASS_ID); svclass_id = sdp_list_append(0, &opush_uuid);//把这个value和service class id attribute相关联 sdp_set_service_classes(record, svclass_id);//这是Profile descriptor这个attribute的value//需要注意的和上面不一样的是这个value有两个值,一个是OBEX_OBJPUSH_PROFILE_ID,一个是profile的version:0x0100,也就是version1.0 sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID); //oPP version 1.0 profile[0].version = 0x0100; pfseq = sdp_list_append(0, profile); //profile descriptor attribute//和Profile descriptor attribute相关联 sdp_set_profile_descs(record, pfseq); //proto descriptor attribute //有L2CAP, RFCOMM. OBEX //rfcomm还有一个channel的参数 sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap_uuid); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); proto[1] = sdp_list_append(0, &rfcomm_uuid); channel = sdp_data_alloc(SDP_UINT8, &u8); proto[1] = sdp_list_append(proto[1], channel); apseq = sdp_list_append(apseq, proto[1]); sdp_uuid16_create(&obex_uuid, OBEX_UUID); proto[2] = sdp_list_append(0, &obex_uuid); apseq = sdp_list_append(apseq, proto[2]); aproto = sdp_list_append(0, apseq); //这里就是proto descriptor sdp_set_access_protos(record, aproto);//support formats list attribute value for (i = 0; i < sizeof(formats); i++) { dtds[i] = &dtd; values[i] = &formats[i]; } sflist = sdp_seq_alloc(dtds, values, sizeof(formats)); //加入到support format list sdp_attr_add(record, SDP_ATTR_SUPPORTED_FORMATS_LIST, sflist); //设定一些信息,比如service name sdp_set_info_attr(record, "OBEX Object Push", 0, 0); //这个地方就有一个record handle的设置 if (add_record_to_server(&adapter->bdaddr, record) < 0) ret = -1; sdp_data_free(channel); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(proto[2], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); if (!ret) return record->handle; return ret;}
所以,从上面这个函数分析下来,我们会得到如图4所示的一个service record。
图4, opp的servicerecord
至此,service record的创建就已经全部结束了。根据上层传入的需要bluez创建的uuid创建对应的service record。然后把他们的handle通过一个数组返回给上层。最终由framework层的mAdapterSdpHandles保存,后期就可以通过这个handle来进行信息的交互了。
若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·