Skip to content
Ever-Never edited this page Jul 9, 2018 · 1 revision

5 control 理解

control就是相当与安卓中的View,对android的自定义控件的有了解都知道,自定义view最主要是重写onDraw、onMeasure、onLayout方法,其实还有一个onTouch方法。这里先简单看下一个control的结构体表示。

5.1 control结构体

struct _LIBAROMA_CONTROL{
  word id;
  voidp internal;
  LIBAROMA_WINDOWP window;
  LIBAROMA_COLORSETP colorset;

  /* px measured */
  int x;
  int y;
  int w;
  int h;

  /* requested */
  int rx;
  int ry;
  int rw;
  int rh;

  /* measured size */
  int left;
  int top;
  int width;
  int height;

  /* minimum control size */
  int minw;
  int minh;

  /* callbacks */
  LIBAROMA_CONTROL_HANDLERP handler;
};

这里可以看出control和window的数据结构比较类似多了一个最小的宽高。还有一个回调函数结构体LIBAROMA_CONTROL_HANDLERP handler

typedef struct {
  dword (*message)(LIBAROMA_CONTROLP, LIBAROMA_MSGP);
  void (*draw)(LIBAROMA_CONTROLP, LIBAROMA_CANVASP);
  byte (*focus)(LIBAROMA_CONTROLP, byte);
  void (*destroy)(LIBAROMA_CONTROLP);
  byte (*thread)(LIBAROMA_CONTROLP);
} LIBAROMA_CONTROL_HANDLER, * LIBAROMA_CONTROL_HANDLERP;
  • draw回调和android的onDraw回调类似,这里有两个参数(LIBAROMA_CONTROLP, LIBAROMA_CANVASP第一个是控件本身,第二个是一张画布。
  • thread回调相当于android view里面的draw方法,只不过这里如果是激活window,每16ms会调用一次,其作用是更新控件的一些状进状态,并进一步决定是否重绘control
  • message 这个回调有点类似android view中的onTouch方法,是在一个事件处理线程中被调用,改函数的参数LIBAROMA_MSGP,是读取来自改control依附的window的事件队列。
  • focus 这个回调暂时没发现用的场景,暂不分析。
  • destroy control附属于的window销毁的时候调用,用来释放control对应的内存,和android的onDetachFromWindow有点类似

这里需要特别关注几个成员word id、 voidp internal、 LIBAROMA_WINDOWP window

  • id和android 中view对应一个id类似。通过id可以遍历window中的control 树找到对应的控件。
  • internal 改成员是voidp,用的时候一般强转成对应的结构体,该成员一般表示控件的属性和状态等(颜色,文字、样式等)。
  • window control依赖的window一般在构造一个control的时候会传进一个window。

5.2 control 绘制流程

在windowmanager分析章节中,直到windowmanger有个线程,相当于渲染线程,改线程每16ms调用window的ui_thread回调和,wm的ui_thread回调,其中在window的ui_thread会遍历window里面的control,首先回调control的thread方法,根据thread回调决是否调用libaroma_control_draw,也就是在这个方法中绘制control,根据libaroma_control_draw,返回值,进一步决定是否要更新wm里面更新区域(现阶段只是更新windowmanger里面的几个变量)所有control中如果没有一个要更新界面的,windowmanger就不需要从新上屏,只要有一个需要更新,就需要更新windowmanger中几个变量决定更新区域,最后就是刷新上屏,上屏区域,就是之上几个变量决定的区域。

说这么多其实我们最关心control是怎么绘制出来的。就要详细分析下libaroma_control_draw方法。改方法有两个参数,第一个参数当前control,第二个参数决定是否立即上屏。

byte libaroma_control_draw(LIBAROMA_CONTROLP ctl, byte sync) {
    LIBAROMA_CANVASP c = libaroma_control_draw_begin(ctl);
    if (c != NULL) {
        if (ctl->handler->draw != NULL) {
            ctl->handler->draw(ctl, c);
        }
        libaroma_control_draw_end(ctl, c, sync);
        return 1;
    }
    return 0;
}

其实看起来是很简介,其实里面暗含好多玄机,大致看做了以下几件事

  • 拿到一个Canvas(没错 Canvas,这里和android很类似)
  • 回调control的回调draw方法,把刚才的Canvas传进去
  • 最后调用libaroma_control_draw_end

写自己的控件一般是实现自己的draw方法,在画布上作图。画布其实就是控件依赖的window对应区域的存储空间。所以这里要先分析下画布拿到的过程。

5.2.1 Canvas获取的过程

安卓有Canvas,当时对这个Canvas其实不太理解,现在研究Libaroma之后,其实Canvas对应一片内存,先简单看下Canvas对应的数据结构。

struct _LIBAROMA_CANVAS{
  int      w;       /* width */
  int      h;       /* height */
  int      l;       /* line size */
  int      s;       /* width x height */
  wordp    data;    /* color data */
  bytep    alpha;   /* alpha data */
  bytep    hicolor; /* hicolor data */
  byte     flags;   /* flags */
};

这里简单解释下 w、h就是canvas的宽高 l现在暂时返现是window的line size ,s=w*h,这里有几个指针这里才是重点,这几个指针决定了绘制区域对应的存储空间。

LIBAROMA_CANVASP libaroma_control_draw_begin(LIBAROMA_CONTROLP ctl) {
    if (!libaroma_window_isactive(ctl->window) || !libaroma_control_isvisible(
            ctl)) {
        return NULL;
    }
    LIBAROMA_WINDOWP win = ctl->window;
    if (win->handler != NULL) {
        if (win->handler->control_draw_begin != NULL) {
            return win->handler->control_draw_begin(win, ctl);
        }
    }
    if (win->dc == NULL) {
        return NULL;
    }
    LIBAROMA_CANVASP c = libaroma_canvas_area(win->dc, ctl->x, ctl->y, ctl->w,
            ctl->h);
    return c;
}

看看这个libaroma_control_draw_begin方法其实做的事情真的不少,大致分为以下几方面

  • 看当前control在所依赖的window是否激活,或者是或否可见,不可见直接返回NULL,control就不绘制了,这里其实就是过滤下,因为渲染线程是遍历window上的所有control,这里就是对于不激活不可见的window进行过滤,减轻渲染线程的负担。
  • 这里又有一个比较好玩的hook,如果window设置了control_draw_begin 回调就有window的回调分配对应的canvas,这里相当交给开发者自己实现自己的window,加大的可定制性。
  • 这里看下默认获取Canvas指针的过程libaroma_canvas_area
LIBAROMA_CANVASP libaroma_canvas_area(
    LIBAROMA_CANVASP parent,
    int x,
    int y,
    int w,
    int h) {
  if (!parent) {
    ALOGW("canvas_area parent is null");
    return NULL;
  }
  /* initializing canvas memory */
  LIBAROMA_CANVASP c = (LIBAROMA_CANVASP) calloc(sizeof(LIBAROMA_CANVAS),1);
  if (!c) {
    ALOGW("canvas_area malloc(LIBAROMA_CANVASP) Error");
    return NULL;
  }
  if (!libaroma_canvas_area_update(c,parent,x,y,w,h)){
    free(c);
    return NULL;
  }
  return c;
}

做了以下几件事

  • 分配canvas结构体存储空间
  • 更新canvas树结构体的成员变量

其实最重要的就是libaroma_canvas_area_update 函数

byte libaroma_canvas_area_update(
    LIBAROMA_CANVASP c,
    LIBAROMA_CANVASP parent,
    int x,
    int y,
    int w,
    int h) {
  if (!parent) {
    ALOGW("canvas_area_update parent is null");
    return 0;
  }
  if (!c) {
    ALOGW("canvas_area_update canvas is null");
    return 0;
  }

  /* Set Target Positions */
  int x2 = x + w;
  int y2 = y + h;

  /* Fix Positions */
  if (x2 > parent->w) {
    x2 = parent->w;
  }

  if (y2 > parent->h) {
    y2 = parent->h;
  }

  if (x < 0) {
    x = 0;
  }

  if (y < 0) {
    y = 0;
  }

  /* Set Fixed Size */
  w = x2 - x;
  h = y2 - y;

  if ((w < 1) || (h < 1)) {
    ALOGW("canvas_area_update calculated width or height < 1");
    return 0;
  }

  c->w      = w;
  c->h      = h;
  c->s      = w * h;
  c->flags  = LIBAROMA_CANVAS_CHILD;
  c->l      = parent->l;
  c->data   = parent->data + (y * parent->l) + x;

  if (parent->alpha != NULL) {
    c->alpha = parent->alpha + (y * parent->l) + x;
  }
  else {
    c->alpha = NULL;
  }

  if (parent->hicolor != NULL) {
    c->hicolor = parent->hicolor + (y * parent->l) + x;
  }
  else {
    c->hicolor = NULL;
  }

  return 1;
}

其实就是无非就是做几件事情。

  • 修正canvas 宽高,因为canvas对应的区域有可能超过window的区域。
  • 把control在window对应位置的指针付给canvas成员,其实核心就在这里(找到对应的存储空间)
  • 这里canvas有个flags比较有趣(这里应该是canvas结构体在释放的时候有用)

这里我才搞懂为啥canvas的l 为parent的l,这个可能在绘制的时候找到,对应的存储空间,因为这里我们得到的只是头指针,其实一个方形区域对应的window里面的内存是不连续的。window分配的空间应该是连续的。

5.2.2 绘制完毕控件之后

绘制控件完毕,无非就是是否直接上屏。这里就贴出下代码。

void libaroma_control_draw_end(LIBAROMA_CONTROLP ctl, LIBAROMA_CANVASP c,
        byte sync) {
    if (sync) {
        libaroma_control_draw_flush(ctl, c, sync);
    }

其实就是根据sync决定是否上屏,继续跟进去看。

byte libaroma_control_draw_flush(LIBAROMA_CONTROLP ctl,
        LIBAROMA_CANVASP canvas, byte sync) {
    if (ctl == NULL) {
        ALOGW("window_control_draw ctl is null");
        return 0;
    }
    if (ctl->window == NULL) {
        ALOGW("window_control_draw ctl doesn't have window");
        return 0;
    }
    LIBAROMA_WINDOWP win = ctl->window;
    if (win->handler != NULL) {
        if (win->handler->control_draw_flush != NULL) {
            return win->handler->control_draw_flush(win, ctl, canvas, sync);
        }
    }
    if (win->dc == NULL) {
        ALOGW("window_control_draw window dc uninitialized");
        return 0;
    }
    if (sync) {
        int sx = ctl->x;
        int sy = ctl->y;
        libaroma_window_sync(win, sx, sy, ctl->w, ctl->h);
    }
    return 1;
}

这里面也有hook-control_draw_flush 这里也加大了自定义程度,一般在写复杂控件的时候用,这里看下默认的流程,其中核心就在libaroma_window_sync.这里先不深入分析,里面也有一个hook(sync)之后分析从头理一般window 对应的Canvas的存储空间的分配,获取子Canvas,上屏的流程。
其实这里上屏过程会从先从window到windowmanger,再到fb回调过程。,这里先埋个引子(window对应的dc 是在哪里获取的?)

5.3 control绘制流程回顾

control绘制的流程其实是特定的主要入后就是在ui渲染线程

  • windowmamger的ui渲染线程遍历window里面的控件,调用控件的thread回调
  • 根据thread回调决定是否绘制控件(thread回调一定程度上减轻了渲染线程的负担)
  • 获取控件对应的canvas
    • 控件是否激活
    • 控件在所依附window是否可见其中激活可以个自定义hook回调control_isvisible
    • 不激活,不可见则不绘制控件,这里一定程度也减轻了渲染线程的负担
  • 调用控件的draw方法 绘制控件
  • 决定是否同步上屏,这里面会从window 到 windowmanger 到fb 一层层的call下去

再总结下window对应回调调用的时机,从上到依次回调

  • control_isvisible hook判断control是否可见,可见之后才找对应的canvas。
  • control_draw_begin hook分配control依赖window对应位置的canvas。
  • control_draw_flush hook上屏过程,如果没有hook就是默认上屏过程
  • sync 默认上屏过程hook。

Clone this wiki locally