在詳細介紹之前,先講講GeoClue的來龍去脈。大家知道,原先在應用程序中要獲取設備的當前位置,最常見的做法是通過GPS定位,程序員往往需要直接訪問串口讀取GPS設備發來的NMEA幀,然後通過分析獲得需要的位置數據。這種做法有2個缺點:第一,工作量大;第二,GPS為某個進程獨占,其他應用如果也有類似需求就只能說抱歉了。在這種情況下,引入了GPS Daemon,就是Daemon獨占了GPS設備,分析NMEA幀獲取位置信息,並把位置信息提供給應用程序。這樣GPS Daemon就可以同時為多個進程服務,並且大大減輕了程序員的工作量,不用再為編寫分析NMEA數據的代碼而頭大了。目前Linux上比較流行的GPS Daemon有gpsd和gypsy。不過使用GPS定位有一定的局限性,首先帶有GPS的設備並不普及,其次,室內無法收到GPS信號。隨著技術的發展,越來越多的定位方式出現在世人面前,如IP地址定位,wifi定位,基站定位等,這些新出現的定位技術對傳統的定位方式起到了補充的作用。於是程序員想在程序中采用多種定位技術結合,自動選取最優定位技術來獲取位置信息。問題又來了:又要自己寫代碼分析位置了,程序裡要加不少代碼來判斷最優,如果需要支持新的定位技術怎麼辦?在這種情況下,GeoClue出現了。
GeoClue是架構在D-BUS上的一套提供位置相關信息的中間件,遵循LGPL。這裡的位置相關信息包括了位置、地址和運動速度等等。GeoClue下有若干Provider提供位置相關信息。同一個Provider可能提供若干種不同類型的信息,比如gypsy可以提供位置信息與速度;不同的Provider也可能提供相同類型的信息,比如gypsy與hostip都可以提供位置信息,所不同的是前者的信息是通過GPS獲得,而後者則是通過互聯網上IP轉經緯度的服務而獲得。GeoClue提供了2套API供應用程序使用,一套是C語言API,另一套是D-BUS API,用戶可以根據實際情況選擇。
GeoClue API通過Provider Interface讓用戶訪問Provider,目前支持的Interface有:
• GeocluePosition
• GeoclueVelocity
• GeoclueAddress
• GeoclueGeocode
• GeoclueReverseGeocode
下面貼一段示例代碼:
#include <geoclue/geoclue-position.h>
/ * device name or bluetooth address * /
#define GPS_DEVICE_NAME "00:02:76:C5:81:BF"
static void
position_changed (GeocluePosition *position,
GeocluePositionFields fields,
int timestamp,
double latitude,
double longitude,
double altitude,
GeoclueAccuracy *accuracy,
gpointer userdata)
{
g_print ("Position changed:\n");
if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE &&
fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) {
g_print ("\t%f, %f\n\n", latitude, longitude);
} else {
g_print ("\tLatitude and longitude not available.\n\n");
}
}
int main()
{
GMainLoop *loop;
GHashTable *options;
GeocluePosition *pos;
GError *error = NULL;
g_type_init ();
/ * Create the position object * /
pos = geoclue_position_new ("org.freedesktop.Geoclue.Providers.Gypsy",
"/org/freedesktop/Geoclue/Providers/Gypsy");
/ * Set GPS device name option for Gypsy * /
options = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (options, "org.freedesktop.Geoclue.GPSDevice", GPS_DEVICE_NAME);
if (!geoclue_provider_set_options (GEOCLUE_PROVIDER (pos), options, &error)) {
g_printerr ("Error setting options: %s\n", error->message);
g_error_free (error);
g_hash_table_destroy (options);
g_object_unref (pos);
return 1;
}
g_hash_table_destroy (options);
/ * connect to signal * /
g_signal_connect (G_OBJECT (pos), "position-changed",
G_CALLBACK (position_changed), NULL);
g_print ("Waiting for position change signals...\n");
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
g_main_loop_unref (loop);
g_object_unref (pos);
return 0;
}
通過這段代碼我們發現,還是需要顯式制定要訪問的Provider。可對於應用程序的程序員來說,他們更關心的是與自身應用、業務相關的內容,他們所要的是一個位置信息,而並不需要太關心數據的具體來源。有沒有更好的方法?GeoClue的開發團隊也想到了這一點,他們在API中提供了稱為Master Provider的功能,我的理解是Provider總管J。程序員只需在代碼中告訴總管所需要的數據精度和數據大致來源,總管就能自動的返回一個最優的Provider來。我們再來看一段示例代碼:
無論是直接使用Provider Interface還是Master Provider,使用GeoClue開發時需要注意以下兩點:
1. 不能在程序中假設函數調用能夠立即返回或者返回的數據均是有效數據。比如GPS初次定位需要一定時間;網絡上的數據請求可能有延遲等等。
2. 盡量使用Signal或者異步調用。同步調用會阻塞線程。
#include <geoclue/geoclue-master.h>
static void
position_changed (GeocluePosition *position,
GeocluePositionFields fields,
int timestamp,
double latitude,
double longitude,
double altitude,
GeoclueAccuracy *accuracy,
gpointer userdata)
{
g_print ("Position changed:\n");
if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE &&
fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) {
g_print ("\t%f, %f\n\n", latitude, longitude);
} else {
g_print ("\tLatitude and longitude not available.\n\n");
}
}
int main()
{
GMainLoop *loop;
GeoclueMaster *master;
GeoclueMasterClient *client;
GeocluePosition *pos;
GeocluePositionFields fields;
double lat, lon;
GError *error = NULL;
g_type_init ();
/ * create a MasterClient using Master * /
master = geoclue_master_get_default ();
client = geoclue_master_create_client (master, NULL, &error);
g_object_unref (master);
if (!client) {
g_printerr ("Error creating GeoclueMasterClient: %s\n", error->message);
g_error_free (error);
return 1;
}
/ * Set our requirements: We want at least city level accuracy, require signals,
and allow the use of network (but not e.g. GPS) * /
if (!geoclue_master_client_set_requirements (client,
GEOCLUE_ACCURACY_LEVEL_LOCALITY,
0, TRUE,
GEOCLUE_RESOURCE_NETWORK,
&error)){
g_printerr ("set_requirements failed: %s", error->message);
g_error_free (error);
g_object_unref (client);
return 1;
}
/ * Get a Position object * /
pos = geoclue_master_client_create_position (client, NULL);
if (!pos) {
g_printerr ("Failed to get a position object");
g_object_unref (client);
return 1;
}
/ * call get_position. We do not know which provider actually provides
the answer (although we could find out using MasterClient API) * /
fields = geoclue_position_get_position (pos, NULL,
&lat, &lon, NULL,
NULL, &error);
if (error) {
g_printerr ("Error in geoclue_position_get_position: %s.\n", error->message);
g_error_free (error);
error = NULL;
} else {
if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE &&
fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) {
g_print ("We're at %.3f, %.3f.\n", lat, lon);
}
}
g_signal_connect (G_OBJECT (pos), "position-changed",
G_CALLBACK (position_changed), NULL);
g_print ("Waiting for position change signals...\n");
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
g_main_loop_unref (loop);
g_object_unref (pos);
g_object_unref (client);
return 0;
}