使用规范

libaroma GUI 使用规范

在使用 libaroma framework 遇到很多使用问题,造成后期改动,其实就是使用没遵循规范造成死机,显示异常。各种莫名奇妙的问题。这里就列举几个使用规范。

字体 字号和大小问题

前期定义的有宏,大家有的直接使用宏 对应的数字,其实这里使用宏的原因,一是方便阅读,二是因为字体和宏是一一对应关系,后期加字体可能调整,但是宏不会变,这里主要涉及字体查找问题,查找不到就去字号对应id小的字体找,有一个优先级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define LIBAROMA_TEXT_TYPE_CHINESE 2
#define LIBAROMA_TEXT_TYPE_ENGLISH 3
#define LIBAROMA_TEXT_TYPE_NUMBERS 1
#define LIBAROMA_TEXT_TYPE_ALL 0
#define LIBAROMA_CHINESE_TEXT_SIZE_18PX 3
#define LIBAROMA_CHINESE_TEXT_SIZE_24PX 4
#define LIBAROMA_CHINESE_TEXT_SIZE_30PX 5
#define LIBAROMA_CHINESE_TEXT_SIZE_42PX 7
#define LIBAROMA_CHINESE_TEXT_SIZE_48PX 8
#define LIBAROMA_CHINESE_TEXT_SIZE_72PX 9
#define LIBAROMA_NUMBERS_TEXT_SIZE_36PX 6
#define LIBAROMA_NUMBERS_TEXT_SIZE_42PX 7
#define LIBAROMA_NUMBERS_TEXT_SIZE_48PX 8
#define LIBAROMA_NUMBERS_TEXT_SIZE_90PX 10
#define LIBAROMA_NUMBERS_TEXT_SIZE_96PX 11
#define LIBAROMA_NUMBERS_TEXT_SIZE_144PX 12

其中 LIBAROMA_TEXT_TYPE_ALL 是全量字体,像那些内容不确定的label,就要使用全量,比如通知音乐,蓝牙搜索。

业务内存申请和释放的时机a2dp_source_handle

这里有个明显的使用错误,就是timer的创建在click 的回调里面创建,删除是在一个条件语句里面删除,所以这里就潜在一个风险就是timer无法删除,造成内存泄露,典型的做法就是在生命周期里面创建和删除(一一 对应的生命周期)。在click回调或者timer回调里面只是更改timer状态。下面给个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static void vxpay_second_on_resume(LIBAROMA_CONTEXTP context)
{
if (current_select_win_id == -1)
{
if (timer != NULL)
xTimerStart(timer, 0);
}
}
static void vxpay_second_on_pause(LIBAROMA_CONTEXTP context)
{
if (timer != NULL)
{
xTimerStop(timer, 0);
timer = NULL;
}
libaroma_context_unregister_callback(MSG_DM_WECHAT_RELEVENCY);
libaroma_context_unregister_callback(MSG_DM_WECHAT_AUTH);
libaroma_context_unregister_callback(MSG_DM_WECHAT_AUTH_CODE);
}
static void vxpay_second_on_create(LIBAROMA_CONTEXTP context)
{
current_select_win_id = -1;
timer = xTimerCreate("wechatpay", 2000 / portTICK_PERIOD_MS, pdTRUE, NULL, pace_wechatpay_on_timercallback);
}
static void vxpay_second_on_destroy(LIBAROMA_CONTEXTP context)
{
if (timer != NULL)
{
xTimerDelete(timer, 0);
timer = NULL;
}
current_select_win_id = -1;
}
static void pace_wechatpay_on_timercallback()
{
if (current_select_win_id == vxpay_one_vxpay_splash_HINT || current_select_win_id == -1)
{
if (timer != NULL)
xTimerStop(timer, 0);
libaroma_context_run_on_ui_thread(pace_wechatpaytransition_on_ui_callback, NULL);
}
else
{
log_hal_info("----------------->>>> wecaht auth");
pace_protocol_send_push_wechar_auth();
}
}

下面还有内存是申请和释放时机,这要看业务对这片内存使用周期,如果是全局 就用全局静态 这种大多是状态变量,如果和context生命周期相同,建议在on_create 和 on_destroy生命周期里面申请和释放。这样在这个业务周期里面都能 使用。

野指针问题

这种问题主要暴露在自定义控件内存释放,和context 中控件的全局静态引用问题,一般会碰见下面 的log,出现这种问题一般有两种典型问题。

1
2
3
4
5
6
7
8
assert failed: ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0, file: ..\..\..\..\..\kernel\rtos\FreeRTOS\Source\portable\MemMang\heap_4.c, line: 341
In Hard Fault Handler
SCB->HFSR = 0x40000000
Forced Hard Fault
SCB->CFSR = 0x01000000
Usage fault: Unaligned access

  1. malloc 一个struct struct里面有指针,在释放struct使用需要释放里面的指针对应的动态分配的内存。
  2. 全局状态引用没有在生命周期里面没有重置,造成状态错乱的问题。
  3. 自定义控件里面动态更新一个指针,或者释放一个指针的内存没强制置空,造成double free 一个指针内存。

对于第一种使用calloc 代替 malloc,第二种要养成释放内存强制置空的习惯,第三种应尽量避免使用全局指针 引用。代码上已经设计出方法来规避。

1
2
3
4
void libaroma_context_set_internal_data(LIBAROMA_CONTEXTP context, voidp data, LIBAROMA_INTERNAL_DATA_FREE_CB free_cb);
voidp libaroma_context_get_internal_data(LIBAROMA_CONTEXTP context);
void libaroma_context_set_controls(LIBAROMA_CONTEXTP context, voidp controls, int size);
voidp libaroma_context_get_controls(LIBAROMA_CONTEXTP context);

自定义控件的数据使用规则

自定义控件可以被重复利用的,一个界面上可以放几个,所以一定 不能访问全局变量,比如控件使用的字符和串都要复制一份到内部,复制的数据都要在control生命周期 释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void _libaroma_ctl_label_destroy(LIBAROMA_CONTROLP ctl)
{
_LIBAROMA_CTL_CHECK(
_libaroma_ctl_label_handler, _LIBAROMA_CTL_LABELP, );
libaroma_mutex_lock(me->mutex);
if (me->text)
{
free(me->text);
}
if (me->canvas != NULL)
{
libaroma_canvas_free(me->canvas);
me->canvas = NULL;
}
libaroma_mutex_unlock(me->mutex);
libaroma_mutex_free(me->mutex);
free(me);
}
byte libaroma_ctl_label_set_text(
LIBAROMA_CONTROLP ctl, constbyte libaroma_ctl_label_set_text(
LIBAROMA_CONTROLP ctl, const char *text, byte update)
{
_LIBAROMA_CTL_CHECK(
_libaroma_ctl_label_handler, _LIBAROMA_CTL_LABELP, 0);
libaroma_mutex_lock(me->mutex);
if (me->text)
{
free(me->text);
}
if (me->canvas != NULL)
{
libaroma_canvas_free(me->canvas);
me->canvas = NULL;
}
me->text = (char *)strdup(text);
libaroma_mutex_unlock(me->mutex);
if (update)
{
me->update = 1;
}
return 1;
}
char *text, byte update)
{
_LIBAROMA_CTL_CHECK(
_libaroma_ctl_label_handler, _LIBAROMA_CTL_LABELP, 0);
libaroma_mutex_lock(me->mutex);
if (me->text)
{
free(me->text);
}
if (me->canvas != NULL)
{
libaroma_canvas_free(me->canvas);
me->canvas = NULL;
}
me->text = (char *)strdup(text);
libaroma_mutex_unlock(me->mutex);
if (update)
{
me->update = 1;
}
return 1;
}

关于control 是否要耦合context 和 control 状态恢复 思考

android 中每个view的构造函数都传入一个context,这里耦合的最大原因其实就是为了调用资源API进行,加载资源。 另外 其实还有一点,就是内存不够的时候window相关的内存回收,所以 这里就回收 了view里面的收据,后台的时候回收,但是context没被回收,context是业务的最小单元,所以view相关的私有数据都会保存在context里面,在作libaroma gui framework设计的时候,就遇到一个内存问题,当时做法就是在resume 重建window,但是这样没法保证被覆盖回来只后view的状态,所以这里借助全局变量,全局指针,导致control free后 指针为野指针,导致业务逻辑错乱。

  1. 关于control是否要耦合context,其实这里是要的,这样可以在econtext切换到后台,释放大块内存比如图片,切换回来从新load进来,所以依赖context
  2. 关于是否要进行状态保存,这要扩展control生命周期。context和control都要扩展。
  3. 关于全局引用,其实可以借助二级指针规避,但是所有控件和业务都要改一把。

####### android

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
// view
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (state != null && !(state instanceof AbsSavedState)) {
throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
+ "when two views of different type have the same id in the same hierarchy. "
+ "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ "other views do not use the same id.");
}
if (state != null && state instanceof BaseSavedState) {
mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
}
}
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (mStartActivityRequestWho != null) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
return state;
}
return BaseSavedState.EMPTY_STATE;
}