一种Mobile UI及应用系统的构架与设计

 前言

我觉得手机的发展可以和服装的演变进行类比。一开始,服装追求遮羞保暖,手机追求通话质量(俗称信号好)。慢慢的,服装变成了时尚的代名词,保暖虽然是服装的essential的功能,但服装更重要的卖点变成了fashion。同样手机成了娱乐工具,通话是必备的功能,但如果缺乏了娱乐功能,如音乐,拍照,上网,游戏等,必将被淘汰。时装设计师充满灵感的设计让人们(特别是女同胞们)发狂。手机也有专门的设计师进行设计,炫酷的外观总是能吸引我们的眼球(以及钱包)。当然,这种设计总是以人为本的,宇航服模样的服装是没人穿的,同样鸟巢状的手机是不会有人用的。“以人为本”是我最喜欢的一个词语,我曾经用这个词语去告戒或者说服很多人,虽然大多数时候都是失败的。对Nokia“以人为本”的口号我一直是支持的(相比之下,Google“不做恶”的口号似乎难以引起共鸣)。服装有高低级,德基和中央新百的服装是不能同日而语的,手机也有高低端,这里的高低端是指质量上的,我非常厌恶把手机当作奢侈品(AURA之流)。如果再类比下去,还有很多类似的地方,比如品牌等等。

言归正传,我这里把Mobile Device的UI从广义上来看待,包括Hardware的UI以及Software的UI。H/W的UI由设计师创造,似乎每个品牌的Device都拥有自己的基因,给人留下不同的印象,比如Nokia适合大众,操作方便;Sangsum和LG是女人用的;Motorola比较严肃,是贵族使用(或者是猛男专用);Blackberry,palm等是商务人士专用;最近的Google Phone给人的印象也是商用机;iPhone我实在是想不出来,年轻人专用吧。说到Software的UI,就要涉及到软件工程师和美工了,Software的UI和品牌联系不如和采用的软件系统的关系来的大,比如Windows Mobile和Symbian系统的手机有他们各自的特点。而iPhone的出现似乎掀起了Mobile Device UI 革命的大潮,各个厂商都希望S/W的UI能更炫更酷,带来更好的用户体验。从各个厂商的基因来看,似乎Apple和Google更适合这场炫酷浪潮。让OL,贵妇人跳街舞似乎有点勉为其难。不过基因突变也是可能滴嘛,让Mobile Device更好的为人们服务吧。

----------------------------------少废话,开始正文!----------------------------------------

- UI Service在Mobile Device S/W系统中的位置

假设Mobile Device的软件系统构架如下图所示:

Mobile_Arch

  • App Layer:应用层
  • Service Layer:为了能将App移植到不同的平台,提供一个Device Service Provider层
  • Driver Layer:Hardware的Driver层,包括Connectivity相关的内容
  • Infrastructure:基础设施由各个层次共享,包括Device的配置信息数据库等等

将App Layer细分,如下图所示:

UIS

UI Service负责两件事情:1,显示UI;2,处理用户交互。 阅读上图中UI Service的三个箭头:1,和User的箭头,一方面处理用户输入,另一方面显示UI给用户;2,和App的箭头,一方面App的逻辑需要更新UI,比如Display一个Dialog,就可以通知UI Service,另一方面,UI Service需要给App的逻辑提供User的输入数据等等;3,和App Framework的箭头,App Framework包含一种Event Hanlding机制,底层系统的某些事件,比如按键,将通过App Framework发送给UI Service。所以准确地讲,上图中不应该将User和UI Service之间画线,User与Device的交互是通过Hardware Layer从下往上通过App Framework传到UI Service的。

- App Framework

App Framework作为OS的一个Task运行,Task运行起来之后,它的最主要的功能就是一个main event loop,伪代码如下:

WHILE TRUE
    Wait for external indication from OS
    Map the OS indication to internal event set
    Route the event set to the app stacks
    Free the event set
    FOR all the event sets in the internal queue
        Route the event set to the app stacks
        Free the event set
    END FOR
    Create a Render event set and route it to the app stacks
    Free the Render event set
END WHILE

App Framework维护两条队列,Internal Queue和External Queue。Internal Queue存放App之间以及UI Service发送的event。External Queue则存放OS发送的Indication。当收到一个OS的indication时(即External Queue中加入了一个OS indication),App Framework将这个indication从External Queue中取出,转换成Event Set,放入Internal Queue。然后App Framework将迭代Interal Queue中所有的Event Set,将它们route到App,全部完成之后App Framework将发送Render事件进行重画。重画完之后再检查External Queue,这样保证每处理一个OS Indication就重新render一次。

同时App Framework维护四个stack,分别是Not inited Apps stack,Preprocessing Apps stack,Focus Apps stack以及Postprocessing Apps stack(App stack使用双向链表实现)。App Framework会按照Preprocessing Apps stack-->Focus Apps stack-->Postprocessing Apps stack的顺序route event set。其中Focus Apps handle event的时候会和UI Service进行交互,通俗的讲,只有Focus Apps stack中的app有UI。

Postprocessing App的例子是tone播放器,当按键事件或其他事件在所有的App中传递完之后,可能需要播放某些tone,比如按键音,这时,tone播放器就可以播放按键音。另外,可能还有一个Postpreocessing App,它能把某个Event Set重新放到Internal Queue中,从而实现“retry”的效果。

- Event 和 Event Set

Event都是被封装在Event Set里面进行Route的,Event Set是一些Event的集合,其中包括两条Event链表,一条包含当前active的Event,另一条包含已经处理过的Event。当某个App或UI Service 决定Consume掉某个Event的时候,这个Event将被从active链表放到history链表中。而Event结构包含Event的code,category(给Event分类,如App handle 的Event,UI Service handle的Event等),附带的data(不同category的Event data可以放在union中)以及一些flag。

- Application inside App Framework

在App Framework中运行的App基本上是包含了一些Callback函数的状态机。一个典型的App包含以下几个部分:

  • App Data
    包含App运行时的数据,其中定义一个所有App共有的Data Struct,主要包含了App关联的dialog的handle、指向状态转换表的指针、App的退出函数指针、App名字,id、当前状态、是否focus的flag等等
  • 状态转换表
    类似{状态名字,入口函数,出口函数,指向事件处理表的指针}的数据结构
  • 注册函数
    所有App在Compile的时候是确定的,把所有App的注册函数放在统一的一个地方,App Framework起来的时候去调用所有App的注册函数。当注册函数被调用的时候,App可以1,Start;2,不启动而是注册在一个专门管理App的App中。第一种App可能是上文所讲的Preprocess App。第二种情况中的管理其他App的App可能就是上文所讲的Postprocess App,或者直接build-in在App Framework中。当App在管理者App中注册的时候,填上start函数指针以及触发事件,当管理者App收到触发事件时就可以调用start函数来启动App。
  • Start函数
    当App Start的时候,将初始化一些数据,将自己加入到对应的App stack,设置对应的状态等等。如果放在Focus stack,App Framework将进行Focus Transition。一般情况下,App Start的时候的状态是INIT状态,当收到Focus事件时才create dialog并使show出来。
  • Exit函数
    当App处于exit状态,或某个handling出错,或者收到END Key和Flip close事件的时候,调用Exit函数进行resource free等退出工作
  • Event Handler函数
    Event Handler函数包括一个总的控制函数,以及一系列针对某个状态的事件处理函数。总控函数中根据当前的状态和接收到的Event Set,从事件处理表中选择合适的事件处理函数对Event Set进行处理。总控函数可能还需要做其他事情,比如handle common的事件,出错处理等等。如果这个App是in focus的,那么总控函数还要将Event Set传给UI Service。

在实际当中,当状态转换的时候,可以将状态存储在stack中,这样当想退回到上一个状态的时候只需要执行pop操作即可。

有些App不是运行在App Framework中的,比如Java。它们运行在OS独立的Task中。那么如何和App Framework一起合作呢,答案就是proxy(类似proxy设计模式,不详细展开)。

- App Framework的其他问题

  • Token Manager
    有些资源是不可以共享的,比如Network,麦克风;有的是可以共享的,比如Speaker。取得Token的App才能得到某个资源。同样,UI也算一种资源,Token Manager将根据优先级来确定某个App能获取UI Token。比如,当在运行Alarm App的时候来了一个Incoming call,显然call的优先级更高,call获取UI Token,并取得Focus。
  • Resource Manager
    Resource包括:字符串,菜单,按钮,图片,字体等。这些resource存储在数据库文件当中,并提供API(API用信号量进行同步)来访问它们。不同语言的resource存放在不同的数据库文件当中,可以根据当前上下文的语言来获取正确的resource,很容易地做到I18N,同时,可以为不同的运营商制作不同的resource。
    Resource可以用ID唯一指定,这些ID将编译进可执行文件中,它们是静态的resource。如果定义一个变量来表示一个ID,这个变量在编译时是不可知的,这个时候就要创建动态的resource,动态的resource一旦被填充了具体的resource之后,它的behavior和静态resource一致。
    在开发的时候可以做一个类似MFC资源编辑器的小程序来管理所有的resource。

- UI Service的API

UI Service对外提供的API划分成5大类:

  1. UI Service的核心API
    - init函数:初始化UI Service
    - MakeContent函数:Resource里面很多都是需要参数的,比如有个字符串resource:“你的名字是”,真正的名字在运行时是动态的,MakeContent函数可以将运行时的名字填到字符串resource中。
    - Get/Set函数:获取/设置相关参数
    - HandleEvent函数:UI Service的事件处理函数,将Event Set交给相应的dialog进行处理。App可以直接call此函数。
    - Create函数:创建dialog,list等。返回它们的句柄。Handle的算法:内部维护一个handle的数组,handle是一个字长的整数,前半个字递增,后半个字为数组的索引。
    - Delete函数:删除UI组件。
  2. Canvas API
    Canvas是一个special的dialog(当然,Canvas API也可以用在不需要dialog的情形下),App用到它是因为它们知道应该怎样去画图。Canvas API主要用于Browser和Java。Canvas API提供了很多典型的画图函数,例如:

    CreateColorCanvas,CanvasDrawText,CanvasDrawPicture,CanvasFillRegion,CanvasDrawLine,CanvasDrawRect,CanvasDrawArc,CanvasGetForegroundColor,CanvasSetForegroundColor,CanvasGetFillColor,CanvasSetFillColor等等。

  3. 和Browser相关的API
    Browser继承自Canvas,UI Service的API主要包括创建一个Browser的函数等等。
  4. 和显示设备相关的API
    这是对显示设备的抽象。其中的操作包括把显示设备打开关闭,把背景灯打开关闭,更新屏幕的某个区域等等。
  5. 内存管理API
    主要是UI Service内部使用的,某些component可能也会用到。首先申请一块编译阶段已知的大块内存。在某些情况如画图像的时候就从这块内存中分配。这样做的原因首先是底层OS是通过消息来提供malloc服务的,这样就不是实时的了。另外,这样做可以分配若干小块内存或一个大块内存,避免了碎片。

HandleEvent函数的伪代码如下:

Async objects(e.g. timer) handle the event first
IF the event is App Framework’s render event
    window manager render the dialog
ELSE
    dialog handle the event
ENDIF

- 窗体

-- Dialog

UI Service内部使用面向对象语言实现。所有对话框都继承Dialog类,Dialog类的主要职责如下:

  • 存储App发来的和用户输入的数据
  • 将数据以某种format呈现在screen上
  • 处理用户输入,更新用户输入数据

一个App对应一个Dialog显然是不够的,这就引出了subdialog的概念,一个subdialog是有一个dialog产生的dialog,当subdialog被focus的时候,由subdialog来handle events。Event Set将会从UI Service的HandleEvent函数route到dialog的handleEvent函数,然后route到subdialog的handleEvent函数。另外,每个dialog都会对应一个菜单(包括左右键)。

Dialog的handleEvent函数伪代码如下:

IF there's subdialog
    let subdialog handle the event first
ELSE
    IF the event is key press, key release or key hold
        check if the key event need special handling
        if not, use default key event handle function
    ELSE
        call none key event handler
    ENDIF
ENDIF
IF there's subdialog
    call parent event handler
ENDIF
call default none key event handler

这是一种典型的Template Method设计模式,key event special handling,default key event handle function,none key event handler,parent event handler都是抽象的。

Subdialog以成员变量的形式出现在Dialog类中:Dialog* subdialog; 创建subdialog的时候只要使用new操作符即可。值得注意的是一般App是不aware subdialog的,subdialog仅仅是用于和User的交互。菜单,Notice,List中的Editor等都是subdialog的例子。

-- Menu

这里的菜单包括menu和softkeys。一个Menu就是一个数组,数组的每个element是一个结构体,包括operation,event code,resource_id。菜单的创建需要App提供数据给这个数组,如何创建则完全由UI Service控制。在Resource Manager中,每个菜单项目存储以下内容:

  • Softkey lable
  • Menu Item Lable
  • Softkey priority
  • Menu priority
  • Boolean1:在选择这个菜单项之后,是否发送App Done事件
  • Boolean2:在选择这个菜单项之后,是否发送Dialog Done事件

某个Dialog上的菜单项包括:这个Dialog类型内置的菜单项(如List内置的菜单项),和上下文有关的菜单项(比如Copy&Paste),App自定义的菜单项。菜单项放置的位置遵循一定的算法,收集完上述三种菜单项并排除重复的菜单项之后,首先根据Softkey priority的绝对值从大到小排序,最大的两个将作为Softkey,Softkey priority为负数的放在左Softkey,Softkey priority为正数的放在右Softkey。其他的作为菜单并按照Menu priority排序。最后,还有些菜单项可能是固定的,比如一个列表一页显示不下了,需要在左Softkey显示More,固定的菜单会覆盖上述的几种菜单。

当选择了某个菜单项之后,对应的Dialog将进行处理,或者发给App处理。如果Boolean1为TRUE,将把App Done Event Set放进App Framework的Internal Queue里面;如果Boolean2为TRUE,将把Dialog Done Event Set放进App Framework的Internal Queue里面。

如果某个菜单项的event code是Nested Menu,表示这是一个多重菜单。UI Service发送事件给App要求取得内嵌的菜单项,App则返回内嵌菜单的数据。

-- Dialog的子孙们

写一个Dialog的子类大致需要实现(覆盖)如下函数:

  • 构造/析构函数
  • 此Dialog特定的Key Event Handler
  • 非Key Event的Handler
  • 此Dialog内置的菜单项
  • 此Dialog不同Context下的菜单项
  • 菜单项处理函数
  • draw
  • coverd:被其他Dialog覆盖时可能需要停止动画释放资源等等
  • 取得此Dialog Type的函数
  • Hanlde subdialog events的函数

对某些复杂的Dialog如Datebook里的Week View等,采用另外一种Key Event机制来处理。首先有一个基类KeyHandler,里面维护一张mapping表,把某个key的event映射到某个函数。这些复杂的Dialog会包含一个KeyHandler的子类来handle key event。

Dialog的子孙有:Caculator,Canvas,Editor,Datebook,DateTimePicker,Idle,List,Notice,Progressbar,Screensaver,Ticker,Viewer等等。

- Async Object Manager

Singleton或实现成全static的util类。目的是给非dialog的object handle event,这些object包括Image,wallpaper,timer等,它们有一个共同的基类AsyncObject。

Async Object Manager内部维护一张表,表的元素是一个结构体,包含AsyncObject的指针,identifier以及object的类型(是否是dialog)。提供注册及注销函数。Async Object Manager的HandleEvent(即UI Service 的HandleEvent调用的函数)函数伪代码如下:

FOR all the registered AsyncObjects
    IF the event is timer expiration event
        call timer object’s timer out function
    ELSE
        call AsyncObject’s handle event function
    ENDIF
END FOR

AsyncObject类则是一个简单的基类,最主要的是里面包含一个虚的handle event函数,由具体的子类实现。

- Window Manager

实际上,所有Dialog都继承自Window类,而Window类又是AsyncObject的子类。在Window类的构造函数里面,向Window Manger注册自己,而在析构函数里则执行注销操作。

同样,在Window Manager里面有一张表,表元素结构体包含window的指针,dialog的handle,注册的显示区域,可见的显示区域,ZOrder,以及一些flag。虽然这是一个数组,但能实现stack的功能。

Window Manager是Singleton。它的功能主要有:

  • 更新window的ZOrder
  • Call 各个window的Cover函数
  • 对可见的window调用render函数
  • 注册,注销window
  • 将被cover的dialog dim掉
  • 处理翻盖
  • 通知status bar manager更新status bar

- Statusbar Manager

UI Service 提供SetStatus的API给App调用来设置Statusbar上某个项目。而SetStatus实现的时候将调用Statusbar Manager 的setStatus函数。反过来Statusbar Manager调用Dialog的statusChanged函数来通知Dialog某个status发生变化,Dialog可能根据这个status的变化进行相应的处理。Statusbar Manager也是singleton,内部维护一个数组存储所有Statusbar上项目的值。

- Painter

Dialog通过Painter来画自己,Dialog知道要画什么,而Painter知道怎么画。一个Painter的子类可能对应一个或多个Dialog类型。当一个Dialog调用draw函数的时候,它就可以调用自己所关联的Painter的drawContent函数(Painter的drawContent函数往往是几百几千行的大型函数)。

Painter draw content的时候大致分三个步骤:1,将Contents转换成logic content;2,将logic content转换成physical content;3,将physical content画到屏幕。Painter在画Dialog的时候将用到如下Component:

  • UI components
    UI components包括:文字,模拟时钟,按钮,Camera上的icon,输入法中的mini列表,输入法中的auto complete提示及插入符号的matrix,滚动条,标题栏(子标题栏),Softkey,标签的外框,进度条等等。当Painter使用UI components的时候只要创建它们然后调用它们的draw函数即可。
  • Image manager
    Image manager用于画图像,支持GIF,WMB,EMS/EMP,JPEG,PNG,BMP格式的图片。解码图片的来源可以是文件,包含多个图像的二进制文件,RAM,resource数据库。调用者只要先创建一个Image对象,设置必要的参数,即可以通过Image Manager来画图像。Image Manager首先要根据图片的类型来解码图像,某些格式的图片解码是同步的,另外一些则是异步的。
    由于内存的限制,对图像的大小也有一定的限制,当然对某些图片在解码的时候可以按照某种算法进行缩小。
    Image对象是AsyncObject,对于GIF动画,当解码完第一帧之后起一个timer,timeout之后解码下一帧,不需要dialog和Painter的干预。但是在dialog被cover的时候dialog需要负责停止动画。
    对于主题皮肤中的图片,Image manager会将它们cache起来
    图像也可以使用多媒体引擎来解码,Image对象将buffer传给多媒体引擎,多媒体引擎解码之后以异步地通知Image对象,之后就可以画在屏幕上了。
  • Theme manager
    Theme manager从主题文件中读取一系列属性,并使用抽象工厂模式创建一系列属性的对象。Painter在画屏时需要到Theme Manager里面去获取相关的属性值。
  • Frame manager 
    UI Service通过Frame Manager将多媒体引擎产生的数据输出到屏幕。UI Service 创建一个Frame,然后将此Frame的信息返回给多媒体引擎及其App。通常在运行Camera或播放视频图片的时候,App将启动多媒体引擎并将数据输出到Frame。Frame Manager在这里起的作用是创建并维护Frame,并将Frame的信息及其变化通知App和多媒体引擎。Frame能同时输出UI Service和多媒体引擎的数据。
  • Wallpaper
    Wallpaper类负责将Wallpaper画在屏幕上。Wallpaper有若干类型,比如idle,main menu及app的Wallpaper。Wallpaper是一个AsyncObject,首先decode图片需要时间,另外这也是为了支持从网络下载图片并设置成wallpaper。
  • Font manager
    Font manager是singleton,并且使用信号量同步。Font Manager使用第三方库来画不同的字体。字体存放在资源数据库当中。Font Manager提供API接口,输入字符串,把字符串逐个字符转换成图形输出。Painter拿到输出的buffer就可以画到屏幕上。可以想象,这种转换是相当的慢的,而且转换操作都是同步的,最好针对整个字符串进行同步而不是单个字符。由于某些政治法律因素,使用的是中文位图字体,当Painter向Font Manager请求中文字体的时候,Font Manager将直接到resource数据库中取出文字的图形。Font Manager维护一张字体表,只需要使用Font ID即可取得相应的字体。
  • Graphics
    Graphics用于画图形,是singleton。提供一个基类提供通用的接口,底下继承有SoftwareGraphics以及直接由硬件芯片画图形的子类。接口的例子有:drawLine,drawRectangle,drawCircle,drawArc等。
  • ImageOperator
    ImageOperator用于对Image buffer进行操作,比如位移,旋转等(数字图像处理)。同样,可以同硬件芯片或软件实现。而处理的图像可以是二值图像,灰度图,或RGB图像。ImageOperator主要的函数有Fill,DrawPixel,DrawBitmap,UpdateDisplay等。当调用DrawBitmap的时候,传入一个Operator类型的参数,比如XorOperator,在画Bitmap的时候将对图像进行异或操作。它是singleton。
  • DisplayAbstract
    根据不同的显示设备使用不同的子类,比如ATI芯片就使用ATI子类。 DisplayAbstract主要的函数有WriteDisplayRegion,DisplayPowerOn,GetConfig等。

- 其他及一些Special Case

  1. 如果一个App没有关联一个Dialog的handle,那么就创建一个NullDialog,用于处理如关机键等事件。
  2. 有一个Preprocess App用于监控User的动作,当用户产生一个动作的时候,起一个timer,如果一段时间内User没有动作,那么发事件给相应的App,比如,屏保,背景灯等。

- 总结

这是一种非常老的构架设计,如果纯粹用这样的构架去实现Mobile Device上的App似乎很难在当今的UI浪潮中脱颖而出。从技术上来讲,UI Service只给App dialog的handle,App不能直接操作dialog的实例,似乎不够面向对象,不像我们写计算机App(更像MFC?)的时候能创建Dialog,并能设置参数,然后为所欲为。这样的构架设计给程序员增加了不小的负担,如果要新增或改写一个App必然要花更多的人力。另外,现在的技术浪潮是script和Markup Language(Palm Pre的WebOS非常振奋人心啊),这种纯粹面向传统程序员的App开发模式显得非常过时了。 当然,这显然不是一无是处,至少这样速度应该是比较快的。而且,如果在整个App框架的基础上重新设计一下UI系统,比如完全用Browser来实现App的UI,而Browser能支持script,Ajax,HTML5等,那……我们就不需要裁员了吧……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值