awtk/docs/how_to_use_app_conf.md
2024-09-17 09:19:20 +08:00

14 KiB
Raw Blame History

如何存取应用程序的配置信息

1. 介绍

AWTK 提供了 app_conf 接口,用于保存或读取应用程序的配置信息。其特点有:

  • 线程安全,可以多线程访问。
  • 观察者模式,配置变化时通知关注者。
  • 统一接口,不用关注存储格式和存储介质。
  • 接口支持多种数据类型,开发者无需在字符串和其它类型之间转换。

配置信息可以保存为任意格式,目前支持下列文件格式(其它格式开发者可以自行增加):

配置信息可以存放在任何介质上,目前支持以下介质:

  • 文件系统。

  • Flash(TODO)

为了方便软件升级时,保留用户配置,先从用户配置读取,如果配置不存在,再读取默认配置。

2. 初始化(任意选一种格式即可)

app_conf 作为可选组件,需要开发者自己初始化。

初始化函数一般在 application_init 函数中调用。所有读写等函数,必须在成功初始化之后才能调用。

配置文件通常保存在用户目录下比如应用程序的名称为demo存储格式为json那么在不同的平台下存放的位置为

  • linux
/demo/app_conf.json
  • macos
/demo/app_conf.json
  • windows
/AppData/Local/demo/app_conf.json
  • 嵌入式
/appdata/demo/app_conf.json

应用程序在启动时,会打印配置文件的位置,以实际位置为准。

  • 2.1 使用 ini 格式时的初始方法

包含头文件:

#include "conf_io/app_conf_init_ini.h"

调用初始化函数

/**
 * @method app_conf_init_ini
 *
 * 初始化应用程序的配置信息。
 *
 * @param {const char*} app_name 应用程序名称。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_init_ini(const char* app_name)

示例:

  ENSURE(app_conf_init_ini("demo") == RET_OK);
  • 2.2 使用 json 格式时的初始方法

包含头文件:

#include "conf_io/app_conf_init_json.h"

调用初始化函数

/**
 * @method app_conf_init_json
 *
 * 初始化应用程序的配置信息。
 *
 * @param {const char*} app_name 应用程序名称。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_init_json(const char* app_name)

示例:

  ENSURE(app_conf_init_json("demo") == RET_OK);
  • 2.3 使用 ubjson 格式时的初始方法

包含头文件:

#include "conf_io/app_conf_init_ubjson.h"

调用初始化函数

/**
 * @method app_conf_init_ubjson
 *
 * 初始化应用程序的配置信息。
 *
 * @param {const char*} app_name 应用程序名称。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_init_ubjson(const char* app_name)

示例:

  ENSURE(app_conf_init_ubjson("demo") == RET_OK);

3. 存取配置信息

3.1 包含头文件:

app_conf 作为可选组件,需要开发者自己包含头文件。

#include "conf_io/app_conf.h"

3.2 修改配置信息

/**
 * @method app_conf_set_int
 * 设置整数类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {int32_t} v 配置项的值。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_set_int(const char* key, int32_t v);

/**
 * @method app_conf_set_int64
 * 设置 64 位整数类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {int64_t} v 配置项的值。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_set_int64(const char* key, int64_t v);

/**
 * @method app_conf_set_bool
 * 设置 bool 类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {bool_t} v 配置项的值。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_set_bool(const char* key, bool_t v);

/**
 * @method app_conf_set_double
 * 设置双精度类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {double} v 配置项的值。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_set_double(const char* key, double v);

/**
 * @method app_conf_set_str
 * 设置字符串类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {const char*} v 配置项的值。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_set_str(const char* key, const char* v);

示例:

  app_conf_set_bool("wifi.on", TRUE);
  app_conf_set_str("wifi.name", "awtk");
  app_conf_set_int("server.port", 8080);
  app_conf_set_double("server.timeout", 1.0);

使用 ini 格式时,实际存为:

[wifi]
  on = 1
  name = awtk
[server]
  port = 8080
  timeout = 1.000000

使用 json 格式时,实际存为:

{
    "wifi" : {
        "on" : true,
        "name" : "awtk"
    },
    "server" : {
        "port" : 8080,
        "timeout" : 1
    }
}

使用 ubjson 格式时,实际存为二进制格式,这里不再举例。

3.3 读取配置信息

/**
 * @method app_conf_get_int
 * 获取整数类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {int32_t} defval 缺省值。
 * 
 * @return {int32_t} 返回配置项的值(如果不存在返回缺省值)。
 */
int32_t app_conf_get_int(const char* key, int32_t defval);

/**
 * @method app_conf_get_int64
 * 获取 64 位整数类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {int64_t} defval 缺省值。
 * 
 * @return {int64_t} 返回配置项的值(如果不存在返回缺省值)。
 */
int64_t app_conf_get_int64(const char* key, int64_t defval);

/**
 * @method app_conf_get_bool
 * 获取 bool 类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {bool_t} defval 缺省值。
 * 
 * @return {bool_t} 返回配置项的值(如果不存在返回缺省值)。
 */
bool_t app_conf_get_bool(const char* key, bool_t defval);

/**
 * @method app_conf_get_double
 * 获取单精度浮点数类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {double} defval 缺省值。
 * 
 * @return {double} 返回配置项的值(如果不存在返回缺省值)。
 */
double app_conf_get_double(const char* key, double defval);

/**
 * @method app_conf_get_str
 * 获取字符串类型配置项的值。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * @param {const char*} defval 缺省值。
 * 
 * @return {const char*} 返回配置项的值(如果不存在返回缺省值)。
 */
const char* app_conf_get_str(const char* key, const char* defval);

示例:

  on = app_conf_get_bool("wifi.on", FALSE);
  port = app_conf_get_int("server.port", 0);
  timeout = app_conf_get_double("server.timeout", 0);
  name = app_conf_get_str("wifi.name", NULL);

3.4 删除配置信息

/**
 * @method app_conf_remove
 * 删除配置项。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_remove(const char* key);

示例:

  app_conf_set_str("wifi.password", "123123");
  assert(app_conf_exist("wifi.password"));

  app_conf_remove("wifi.password");
  assert(!app_conf_exist("wifi.password"));

3.5 检查配置信息是否存在

/**
 * @method app_conf_exist
 * 检查配置项是否存在。
 *
 * @annotation ["static", "scriptable"]
 *
 * @param {const char*} key 配置项的名称。
 * 
 * @return {bool_t} 返回 TRUE 表示存在FALSE 表示不存在。
 */
bool_t app_conf_exist(const char* key);

示例:

  app_conf_set_str("wifi.password", "123123");
  assert(app_conf_exist("wifi.password"));

  app_conf_remove("wifi.password");
  assert(!app_conf_exist("wifi.password"));

3.6 持久保存配置信息

/**
 * @method app_conf_save
 * 持久保存配置。
 *
 * @annotation ["static", "scriptable"]
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_save(void);

示例:

app_conf_save();

3.7 关注配置信息的变化

/**
 * @method app_conf_on_changed
 * 注册配置变化事件。
 *
 * @annotation ["static", "scriptable"]
 * @param {event_func_t} on_event 事件处理函数。
 * @param {void*} ctx 事件处理函数上下文。
 * @return {uint32_t} 返回 id用于 app_conf_off_changed。
 */
uint32_t app_conf_on_changed(event_func_t on_event, void* ctx);

/**
 * @method app_conf_off_changed
 * 注销配置变化事件。
 *
 * @annotation ["static", "scriptable"]
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_off_changed(uint32_t id);

/**
 * @method app_conf_off_changed_by_ctx
 * 注销配置变化事件。
 *
 * @annotation ["static"]
 * @param {void*} ctx 事件处理函数上下文。
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_off_changed_by_ctx(void* ctx);

示例:

static ret_t app_conf_changed(void* ctx, event_t* e) {
  prop_change_event_t* evt = prop_change_event_cast(e);

  log_debug("%s changed\n", evt->name);

  return RET_OK;
}

...

app_conf_on_changed(app_conf_changed, NULL);

回调函数在修改配置的线程调用。如果是后台线程修改配置,在配置变化时,需要更新界面,此时需要用 idle_queue 进行串行化。

3.8 恢复出厂设置

删除用户配置文件,并重新加载。

/**
 * @method app_conf_reset
 *
 * 恢复出厂设置。
 *
 * @annotation ["global"]
 * 
 * @return {ret_t} 返回RET_OK表示成功否则表示失败。
 */
ret_t app_conf_reset(void);

6. 释放配置相关资源。

/**
 * @method app_conf_deinit
 * 释放 conf 对象。
 *
 * @annotation ["static", "scriptable"]
 * 
 * @return {ret_t} 返回 RET_OK 表示成功,否则表示失败。
 */
ret_t app_conf_deinit(void);

示例:

ret_t application_exit() {
  app_conf_deinit();
  log_debug("application_exit\n");
  return RET_OK;
}

7. key 的规则

  • 配置信息通常都是层次结构的为了能访问所有数据key 也必须是多级的,各级的名称之间用“.”分隔。

“.”不能作为名称的一部分,如果出现,需要用其它字符代替。

{
    "network" : {
        "eth0" : {
            "ip" : "192.169.0.100",
            "gateway" : "192.169.0.1",
            "mask" : "255.255.255.0"
        }
    }
}

在这个例子中,要访问 ip 的这个配置,可以用"network.eth0.ip":

  app_conf_set_str("network.eth0.ip", "192.169.0.100");
  • 按索引访问

在访问时,每一级的名称,可以用索引代替,格式为:[+索引+],比如 [0] 表示第一项,[2] 表示第三项。这对于不清楚名称的情况,或遍历全部配置,很有帮助。

如:

  const char* ip0 = app_conf_get_str("network.[0].ip", NULL);
  const char* ip1 = app_conf_get_str("network.[1].ip", NULL);
  const char* ip2 = app_conf_get_str("network.[2].ip", NULL);
  • 用 #size 获取子节点个数

示例:

[wifi]
  on = 1 
  name = awtk
[server]
  port = 8080
  timeout = 1.000000

在上面的例子中,"#size"获取分组的个数为 2"wifi.#size"获取 wifi 下的子项数为 2。

  assert(app_conf_get_int("#size", 0) == 2); 
  assert(app_conf_get_int("wifi.#size", 0) == 2); 
  • 用 #name 获取项的名称。

示例:

[wifi]
  on = 1 
  name = awtk
[server]
  port = 8080
  timeout = 1.000000

在上面的例子中,"[0].#name" 获取第一个分组的名称为"wifi""wifi.[0].#name" 获取 wifi 下第一项的名称为 "on"。

  assert(strcmp(app_conf_get_str("[0].#name", NULL), "wifi") == 0);
  assert(strcmp(app_conf_get_str("wifi.[0].#name", NULL), "on") == 0);
  • 用 #index 获取配置项在兄弟节点中的序数。
[wifi]
  on = 1 
  name = awtk
[server]
  port = 8080
  timeout = 1.000000

在上面的例子中,"wifi.#index" 获取 wifi 的序数为 0"server.#index" 获取 server 的序数为 1。

  assert(app_conf_get_int("wifi.#index", -1) == 0);
  assert(app_conf_get_int("server.#index", -1) == 1);

8. 缺省配置

  • 位置。缺省配置放到资源中位于design/default/data

  • 命名:应用程序名 + '.' + 文件扩展名。

  • 文件扩展名与初始化时指定的格式一致。

比如,下面的例子中,应用程序名 demo扩展名为 json。所以对应的缺省配置文件为design/default/demo.json

  ENSURE(app_conf_init_json("demo") == RET_OK);

优先读取用户配置,如果没有读到,再读取默认配置。

9. 注意事项

  • 英文的点(.) 作为 key 的分隔符,所以 key 不能包含英文的点(.)。

  • ini 格式不支持注释和换行。

10. 参考示例