[Android原始碼分析]藍芽開啟流程分析——jni層之下的偷偷摸摸(Service Record的建立)
在上一篇文章中我們詳細介紹了藍芽開啟過程中,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;
}
在正式分析add_opush_record函式之前,我們需要先了解一下spec中對service record是如何定義的。否則,我們匆忙去看下面的程式碼,十有八九會是一頭霧水。
在藍芽spec的SDP這一節中,明確指出,一個Servicerecord是由多個Service Attribute組成的。如下圖1所示:
圖1 Servicerecord的組成
每一個ServiceAttribute又會分為兩個部分,一個是Attribute ID,一個是Attribute Value。他的組成如下圖2所示。
圖2 ServiceAttribute的組成
圖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來進行資訊的互動了。
若您覺得該文章對您有幫助,請在下面用滑鼠輕輕按一下“頂”,哈哈~~·