当前位置: 代码迷 >> Android >> [源码级分析][Android4.0]蓝牙打开流程分析——jni层偏下的偷偷摸摸(Service Record的创建)
  详细解决方案

[源码级分析][Android4.0]蓝牙打开流程分析——jni层偏下的偷偷摸摸(Service Record的创建)

热度:106   发布时间:2016-04-28 07:42:37.0
[源码级分析][Android4.0]蓝牙打开流程分析——jni层之下的偷偷摸摸(Service Record的创建)

在上一篇文章中我们详细介绍了蓝牙打开过程中,jni之上的各个方方面面,应该说涉及到的地方全部讲清楚了,从这一章开始就来讲解一下打开过程到了jni之下都做了些什么。

为什么取名为偷偷摸摸,因为从这里往下在互联网上就基本找不到任何资料了,大家都是凭借函数的名字去猜测一下做了一些什么,然后继续回到jni之上去分析了。然而仅了解那些明面上的东西对我们分析蓝牙来说显然是不够的,我们必须要一探究竟,看看jni之下都偷偷摸摸做了些什么。

这一章节我们将从这几个方面来分析:

1Service Record的创建

2enable native

3profile的使能

本篇博文将会主要分析第一点:Service Record的创建。

1ServiceRecord的创建

Service Record通俗一点来讲就是用来保存我们所支持service的一个记录。他是用来为SDP服务的,所谓的SDP就是Service Discovery Profile,从名字上可以看到就是用来发现支持哪些服务的Profile。那这些在具体的应用中有什么作用呢?

举一个例子,比如说我想通过蓝牙发送一个文件给对方的设备,可是我怎么知道对方能不能接收文件呢,我总不能从手机上发送一个文件给蓝牙耳机吧。所以,需要我们在配对的时候知道对方支持不支持文件的接收,这就是通过SDP来获知的,而对方所支持的内容(比如说接收文件的能力,我们称之为opp)就是保存在它的Service Record中的。

通过上面的例子,我们也就不难理解为什么Service Record需要在蓝牙打开的过程中创建了。好了,废话不多说,我们直接去看关于service Record,代码中是如何实现的。

1.1jniaddReservedServiceRecordsNative的分析

基本这个函数就是调用AddReservedServiceRecordsdbus函数,然后根据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.2bluez中的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.1add_opush_record的分析

在正式分析add_opush_record函数之前,我们需要先了解一下spec中对service record是如何定义的。否则,我们匆忙去看下面的代码,十有八九会是一头雾水。

在蓝牙specSDP这一节中,明确指出,一个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

3Attribute 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 oppservicerecord

         至此,service record的创建就已经全部结束了。根据上层传入的需要bluez创建的uuid创建对应的service record。然后把他们的handle通过一个数组返回给上层。最终由framework层的mAdapterSdpHandles保存,后期就可以通过这个handle来进行信息的交互了。

 

若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·
 

 

  相关解决方案