您的位置:首页 > 移动开发 > Android开发

[Android源码分析]蓝牙打开流程分析——jni层之下的偷偷摸摸(Service Record的创建)

2013-10-17 09:04 676 查看
在上一篇文章中我们详细介绍了蓝牙打开过程中,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 H***E_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来进行信息的交互了。

若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: