Maemo Linux手机平台系列分析:11 Maemo平台开发之 异步GConf

 
 
异步 GConf
  • GConf来监视变化
  • 当监视对象发生变化时,用GConf给通知
 
GConf 来监视对象的改变
Maemo 中, GConf 是主要的用于存储一些设置参数的底层软件。下面,我们将会看到如何去扩展 GConf ,使它能够更适合于异步方式工作,特别是实现一些服务时,如果把握 GConf 的使用。
 
这些情况下需要使用 GConf: 1 你的服务程序有些比较简单的参数设置,并且需要能够实时地对参数的变化进行响应; 2 人们也想偷懒,不想自己写一个配置文件解析程序(尽管 Glib 已经自带了一个这样的解析程序);我们的例子程序就是模仿第一种情况。
 
例子程序主要关注两个字符串:一个是设置设备的通信 (connection), 另外一个是设置通信参数 (connectionparams) 。这里我们主要关心 gconf 值变化的通知,这里会省略一些简单的东西,所以你需要预先给那两个值设置初始值。用 gconftool-2 来设置。 写到 makefile 中:
# Define a variable for this so that the GConf root may be changed
gconf_root := /apps/Maemo/platdev_ex
 
# ... Listing cut for brevity ...
 
# This will setup the keys into default values.
# It will first do a clear to remove any existing keys.
primekeys: clearkeys
      gconftool-2 --set --type string /
                  $(gconf_root)/connection btcomm0
      gconftool-2 --set --type string /
                  $(gconf_root)/connectionparams 9600,8,N,1
 
# Remove all application keys
clearkeys:
      @gconftool-2 --recursive-unset $(gconf_root)
 
# Dump all application keys
dumpkeys:
      @echo Keys under $(gconf_root):
      @gconftool-2 --recursive-list $(gconf_root)
运行两个命令设置主键,并且查看是否设置 OK:
[sbox-CHINOOK_X86: ~/gconf-listener] > make primekeys
gconftool-2 --set --type string /
            /apps/Maemo/platdev_ex/connection btcomm0
gconftool-2 --set --type string /
            /apps/Maemo/platdev_ex/connectionparams 9600,8,N,1
[sbox-CHINOOK_X86: ~/gconf-listener] > make dumpkeys
Keys under /apps/Maemo/platdev_ex:
 connectionparams = 9600,8,N,1
 connection = btcomm0
 
当监视对象发生变化时,用 GConf 给通知
定义一个根健和两个主键,即所谓的名字空间 (name space):
#include <glib.h>
#include <gconf/gconf-client.h>
#include <string.h> /* strcmp */
 
/* As per maemo Coding Style and Guidelines document, we use the
   /apps/Maemo/ -prefix.
   NOTE: There is no central registry (as of this moment) that you
         could check that your application name doesn't collide with
         other application names, so caution is advised! */
#define SERVICE_GCONF_ROOT "/apps/Maemo/platdev_ex"
 
/* We define the names of the keys symbolically so that we may change
   them later if necessary, and so that the GConf "root directory" for
   our application will be automatically prefixed to the paths. */
#define SERVICE_KEY_CONNECTION /
        SERVICE_GCONF_ROOT "/connection"
#define SERVICE_KEY_CONNECTIONPARAMS /
        SERVICE_GCONF_ROOT "/connectionparams"
主函数创建一个 GConf client 对象 ( 就是通过这个对象链接到 Gconf 后台 daemon 程序的 ) ,然后显示前面设置的值:
int main (int argc, char** argv) {
 /* Will hold reference to the GConfClient object. */
 GConfClient* client = NULL;
 /* Initialize this to NULL so that we'll know whether an error
     occurred or not (and we don't have an existing GError object
     anyway at this point). */
 GError* error = NULL;
 /* This will hold a reference to the mainloop object. */
 GMainLoop* mainloop = NULL;
 
 g_print(PROGNAME ":main Starting./n");
 
 /* Must be called to initialize GType system. The API reference for
     gconf_client_get_default() insists.
     NOTE: Using gconf_init() is deprecated! */
//已经废弃gconf_init()
 g_type_init();
 
 /* Create a new mainloop structure that we'll use. Use default
     context (NULL) and set the 'running' flag to FALSE. */
 mainloop = g_main_loop_new(NULL, FALSE);
 if (mainloop == NULL) {
    g_error(PROGNAME ": Failed to create mainloop!/n");
 }
 
 /* Create a new GConfClient object using the default settings. */
 client = gconf_client_get_default();
 if (client == NULL) {
    g_error(PROGNAME ": Failed to create GConfClient!/n");
 }
 
 g_print(PROGNAME ":main GType and GConfClient initialized./n");
 
 /* Display the starting values for the two keys. */
 dispStringKey(client, SERVICE_KEY_CONNECTION);
 dispStringKey(client, SERVICE_KEY_CONNECTIONPARAMS);
函数 dispStringKey 非常简单,主要是提取和显示一个字符串:
/**
 * Utility to retrieve a string key and display it.
 * (Just as a small refresher on the API.)
 */
static void dispStringKey(GConfClient* client,
                          const gchar* keyname) {
 
 /* This will hold the string value of the key. It will be
     dynamically allocated for us, so we need to release it ourselves
     when done (before returning). */
 gchar* valueStr = NULL;
 
 /* We're not interested in the errors themselves (the last
     parameter), but the function will return NULL if there is one,
     so we just end in that case. */
 valueStr = gconf_client_get_string(client, keyname, NULL);
 
 if (valueStr == NULL) {
    g_error(PROGNAME ": No string value for %s. Quitting/n", keyname);
    /* Application terminates. */
 }
 
 g_print(PROGNAME ": Value for key '%s' is set to '%s'/n",
          keyname, valueStr);
 
 /* Normally one would want to use the value for something beyond
     just displaying it, but since this code doesn't, we release the
    allocated value string. */
 g_free(valueStr);
}
下面我们吧 GConf client 绑定到特定的路径(我们将操作它,并监视它的改变):
 /**
   * 把要监视的路径注册给GConf client, 当这个路径中的内容发生改变时,会发射信号“value-changed”出来,
   * Register directory to watch for changes. This will then tell
   * GConf to watch for changes in this namespace, and cause the
   * "value-changed"-signal to be emitted. We won't be using that
   * mechanism, but will opt to a more modern (and potentially more
   * scalable solution). The directory needs to be added to the
   * watch list in either case.
   *
   * GConfClient可以预加载一些键值,
  * When adding directories, you can sometimes optimize your program
   * performance by asking GConfClient to preload some (or all) keys
   * under a specific directory. This is done via the preload_type
   * parameter (we use GCONF_CLIENT_PRELOAD_NONE below). Since our
   * program will only listen for changes, we don't want to use extra
   * memory to keep the keys cached.
   *
   * Parameters:
   * - client: GConf-client object
   * - SERVICEPATH: the name of the GConf namespace to follow
   * - GCONF_CLIENT_PRELOAD_NONE: do not preload any of contents
   * - error: where to store the pointer to allocated GError on
   *          errors.
   */
 gconf_client_add_dir(client,
                       SERVICE_GCONF_ROOT,//要监听的路径
                       GCONF_CLIENT_PRELOAD_NONE,
                       &error);
 
 if (error != NULL) {
    g_error(PROGNAME ": Failed to add a watch to GCClient: %s/n",
            error->message);
    /* Normally we'd also release the allocated GError, but since
       this program will terminate on g_error, we won't do that.
       Hence the next line is commented. */
    /* g_error_free(error); */
 
    /* When you want to release the error if it has been allocated,
       or just continue if not, use g_clear_error(&error); */
 }
 
  g_print(PROGNAME ":main Added " SERVICE_GCONF_ROOT "./n");
接下来,我们需要注册回调函数,这个回调函数在监视对象发生改变时,被调用:
 /* Register our interest (in the form of a callback function) for
     any changes under the namespace that we just added.
 
     Parameters:
     - client: GConfClient object.
     - SERVICEPATH: namespace under which we can get notified for
                    changes.
     - gconf_notify_func: callback that will be called on changes.
     - NULL: user-data pointer (not used here).
     - NULL: function to call on user-data when notify is removed or
             GConfClient destroyed. NULL for none (since we don't
             have user-data anyway).
     - error: return location for an allocated GError.
 
     Returns:
     guint: an ID for this notification so that we could remove it
            later with gconf_client_notify_remove(). We're not going
            to use it so we don't store it anywhere. */
 gconf_client_notify_add(client, SERVICE_GCONF_ROOT,
                          keyChangeCallback, NULL, NULL, &error);
 if (error != NULL) {
    g_error(PROGNAME ": Failed to add register the callback: %s/n",
            error->message);
    /* Program terminates. */
 }
 
 g_print(PROGNAME ":main CB registered & starting main loop/n");
当我们处理桌面软件时,你可能使用多个回调函数,每个回调去跟踪一个键值。其实这样做不好,我们可以使用一个回调处理多个键值的跟踪:
/**
 * 当监视路径发生改变时,会调用回调函数,并且这个回调函数要和GConfClientNotifyFunc原型一致;
 * Callback called when a key in watched directory changes.
 * Prototype for the callback must be compatible with
 * GConfClientNotifyFunc (for ref).
 *
 * It will find out which key changed (using strcmp, since the same
 * callback is used to track both keys) and the display the new value
 * of the key.
 *
 * 通常情况下,userData会把改变的值携带给回调函数,这样我们才能根据改变的值修改界面,不过这里仅仅是个示例,就不这样搞了。
 * The changed key/value pair will be communicated in the entry
 * parameter. userData will be NULL (can be set on notify_add [in
 * main]). Normally the application state would be carried within the
 * userData parameter, so that this callback could then modify the
 * view based on the change. Since this program does not have a state,
 * there is little that we can do within the function (it will abort
 * the program on errors though).
 */
static void keyChangeCallback(GConfClient* client,
                              guint        cnxn_id,
                              GConfEntry* entry,
                              gpointer     userData) {
 
 /* This will hold the pointer to the value. */
 const GConfValue* value = NULL;
 /* This will hold a pointer to the name of the key that changed. */
 const gchar* keyname = NULL;
 /* This will hold a dynamically allocated human-readable
     representation of the changed value. */
 gchar* strValue = NULL;
 
 g_print(PROGNAME ": keyChangeCallback invoked./n");
 
 /* Get a pointer to the key (this is not a copy). */
 keyname = gconf_entry_get_key(entry);
 
 /* It will be quite fatal if after change we cannot retrieve even
     the name for the gconf entry, so we error out here. */
 if (keyname == NULL) {
    g_error(PROGNAME ": Couldn't get the key name!/n");
    /* Application terminates. */
 }
 
 /* Get a pointer to the value from changed entry. */
 value = gconf_entry_get_value(entry);
 
 /* If we get a NULL as the value, it means that the value either has
     not been set, or is at default. As a precaution we assume that
     this cannot ever happen, and will abort if it does.
     NOTE: A real program should be more resilient in this case, but
           the problem is: what is the correct action in this case?
           This is not always simple to decide.
     NOTE: You can trip this assert with 'make primekeys', since that
           will first remove all the keys (which causes the CB to
           be invoked, and abort here). */
 g_assert(value != NULL);
 
 /* Check that it looks like a valid type for the value. */
 if (!GCONF_VALUE_TYPE_VALID(value->type)) {
    g_error(PROGNAME ": Invalid type for gconfvalue!/n");
 }
 
 /* Create a human readable representation of the value. Since this
     will be a new string created just for us, we'll need to be
     careful and free it later. */
 strValue = gconf_value_to_string(value);
 
 /* Print out a message (depending on which of the tracked keys
     change. */
 if (strcmp(keyname, SERVICE_KEY_CONNECTION) == 0) {
    g_print(PROGNAME ": Connection type setting changed: [%s]/n",
            strValue);
 } else if (strcmp(keyname, SERVICE_KEY_CONNECTIONPARAMS) == 0) {
    g_print(PROGNAME ": Connection params setting changed: [%s]/n",
            strValue);
 } else {
    g_print(PROGNAME ":Unknown key: %s (value: [%s])/n", keyname,
            strValue);
 }
 
 /* Free the string representation of the value. */
 g_free(strValue);
 
 g_print(PROGNAME ": keyChangeCallback done./n");
}
上面的代码有点复杂,原因是 GConf 使用 GValue 作为变量来传递数据: GValue 可以携带任何简单的数据类型。由于我们不能完全相信 GConf 会返回正确的值,我们需要格外小心:对返回值的类型做仔细的检查,防止 NULL 引起的程序异常和其它类型不匹配错误。
 
好了,下面我们编译并测试该程序:在后台启动这个程序,然后使用 gconftool-2 修改键值,看看监视函数能不能对键值的改变做出及时的响应:
[sbox-CHINOOK_X86: ~/gconf-listener] > make
cc -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/gconf/2 /
   -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -Wall -g /
   -DPROGNAME=/"gconf-key-watch/" gconf-key-watch.c -o gconf-key-watch /
   -lgconf-2 -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0
[sbox-CHINOOK_X86: ~/gconf-listener] > run-standalone.sh ./gconf-key-watch & //后台运行
[2] 21385
gconf-key-watch:main Starting.
gconf-key-watch:main GType and GConfClient initialized.
gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connection'
 is set to 'btcomm0'
gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connectionparams'
 is set to '9600,8,N,1'
gconf-key-watch:main Added /apps/Maemo/platdev_ex.
gconf-key-watch:main CB registered & starting main loop
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type string / //修改键值
  /apps/Maemo/platdev_ex/connection ttyS0
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection type setting changed: [ttyS0] //监视函数及时检测到键值的修改
gconf-key-watch: keyChangeCallback done.
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type string / //修改键值
 /apps/Maemo/platdev_ex/connectionparams ''
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection params setting changed: [] //监视函数及时检测到键值的修改
gconf-key-watch: keyChangeCallback done.
後一个修改是有问题的:修改成空值了。由于 GConf 本身并不提供语法和语义的检查,因此,对于修改键值的检查也是你需要仔细考虑的。
 
当发现键值被修改为非法键值时,你可以把该键值复位为初始值。不过,这种复位操作可能会影响到其它关联的程序,比较冒险,一般不这样做。另外:由于特定的环境没有办法告诉用户你的程序已经退出,所以当遇到非法键值时,程序退出也不是一个好的办法。
 
另外一个问题:一个动作可能影响到多个键值。你需要处理这个问题。上面的例子也可以验证这个问题:当 connection-key 键值改变时, server 应该重新初始化这个 connection 吗?或者如果键值 同时 改变了,则 connection 会被重新初始化两次的。这是很严重的问题。 Gconf 并不支持原子操作。这是实际工作中必须面对的问题。
 
下面我们再测试一些异常的问题:
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type int /
 /apps/Maemo/platdev_ex/connectionparams 5 //设置的类型不对,不是字符串
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection params setting changed: [5]
gconf-key-watch: keyChangeCallback done.
[sbox-CHINOOK_X86: ~/gconf-listener] > gconftool-2 --set --type boolean /
 /apps/Maemo/platdev_ex/connectionparams true
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch: Connection params setting changed: [true]
gconf-key-watch: keyChangeCallback done.
对于前面设置为空的键值,清除时发生了崩溃!
[sbox-CHINOOK_X86: ~/gconf-listener] > make clearkeys
gconf-key-watch: keyChangeCallback invoked.
gconf-key-watch[21403]: GLIB ERROR ** default -
 file gconf-key-watch.c: line 129 (keyChangeCallback):
 assertion failed: (value != NULL)
aborting...
/usr/bin/run-standalone.sh: line 11: 21403 Aborted (core dumped) "$@"
[1]+ Exit 134 run-standalone.sh ./gconf-key-watch
在回调函数里面,我们检查了非 NULL 值,对于 NULL 的值,我们退出程序,所以上面的异常在我们的意料之内。
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值