1. 程式人生 > >[Android原始碼分析]藍芽開啟流程分析——jni層之下的偷偷摸摸(Service Record的建立)

[Android原始碼分析]藍芽開啟流程分析——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;
}

在正式分析add_opush_record函式之前,我們需要先了解一下spec中對service record是如何定義的。否則,我們匆忙去看下面的程式碼,十有八九會是一頭霧水。

在藍芽specSDP這一節中,明確指出,一個Servicerecord是由多個Service Attribute組成的。如下圖1所示:

1 Servicerecord的組成

每一個ServiceAttribute又會分為兩個部分,一個是Attribute ID,一個是Attribute Value。他的組成如下圖2所示。

2 ServiceAttribute的組成

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來進行資訊的互動了。

若您覺得該文章對您有幫助,請在下面用滑鼠輕輕按一下“頂”,哈哈~~·