认识迅雷界面引擎

简介: UI开发的新时代----认识迅雷界面引擎     第一部分:交互开发技术概述      软件产品的交互开发一直以来都不是一件令人愉悦的事情。首先,由于每个人编写的第一个图形应用程序就已经使用了一些交互开发技术,而且由于IDE工具的强大,容易总结出交互开发就是“拖拖控件,改改属性,写写响应”的经验,所以很容易被认为是没什么技术含量的工作。

UI开发的新时代----认识迅雷界面引擎

 

 
第一部分:交互开发技术概述
     软件产品的交互开发一直以来都不是一件令人愉悦的事情。首先,由于每个人编写的第一个图形应用程序就已经使用了一些交互开发技术,而且由于IDE工具的强大,容易总结出交互开发就是“拖拖控件,改改属性,写写响应”的经验,所以很容易被认为是没什么技术含量的工作。但实际上,这是一个特别不容易的工作:因为作为软件产品的脸面,上至公司老板,下至任意一个普通用户,大家都可以对你的工作成果拼头论足,并提出修改意见,而这些修改意见反应到产品的方案修改上,总是会让修改成本与项目的修改复杂度不呈线性关系。很多刚刚从事这一行的项目经理总是不能理解,为什么按一个方案修改交互,需要1天,而另一个看起来似乎差不多的方案,却要改上一个月?
 
    从另一个角度也可以发现这里的技术门槛不易:其它领域的设计模式,经验总结文章已经汗牛充栋,各种各样的新思路和在这些思路上建立起来的各种开源库,每过几年基本就要洗上一轮,但在交互开发上,除了各个厂商提供的平台开发方法,几乎没有被公认和被广泛使用的界面开发库,更不要提相关方法论和模式的升华,总结与创新了。
  
   但是,软件产品的交互体验,特别是互联网产品的交互体验,如今越来越多的受到人们的重视。从过去能实现功能完成交互,到后来提供一致的操作习惯,流畅的实现整个交互流程,如今还渴望交互体验能基于用户的使用心理设计,更加美观,并提高产品的整体品味。如今全球市值最高的Apple公司,正是靠着其ipod,iphone,ipad系列产品的优秀交互体验和近乎完美的工业设计,征服了广大用户。
 
   迅雷作为中国非常流行的一款Windows平台下的客户端产品,对于改进产品的交互体验有着强烈的愿望。迅雷5.8完成了工具软件的关键功能提升,随后的版本进化,希望能进一步改进产品的交互体验: 更美观也更现代。迅雷5.9(迅雷6)是基于传统Windows界面开发技术改进交互体验的结果,这个结果虽然还不错,但带来了另一个问题:开发成本的提高。迅雷作为一款由多个部门合作开发的(包括合作开发交互)客户端产品,传统的开发技术不能很好的组合各个部门开发的模块,开发成本与稳定性都有问题。公司迫切的需要一个能解决这些问题的下一代界面引擎,并在多个方面使用各种方式开始了勇敢的尝试。
 
 
第二部分  过去我们如何开发按钮
现在我将通过一个经典的windows下”自画按钮”的工程实例,和大家一起观察UI开发的过程。”自画按钮”是一个非常常见的产品需求,相信不少读者都有过相似的经历(下列故事纯属虚构,如有雷同,真是巧合).
那年正是Windows XP最流行的时候,我们的产品也跟进时代的发展,将界面风格升级到了XP Style,同时我们的产品还兼容Windows2000,设计和产品都希望我们的软件在两个系统下都能有一致的外观表现。于是,我就需要开发一个能在两个平台下都长得一样的按钮。这个活并不困难,我们先看看需求:
看了需求之后,作为一个合格的Windows开发工程师,很快就完成了。实现代码大致如下:
CButton ::OnPaint() {
    DrawBkg(m_state,0,0,width,height);
    DrawText(m_state,m_btnText,width/2-textWidth/2,height/2-textHeight/2);
    if (isFocus)
    {
        DrawFocusRect(2,2,width-2,height-2);
    }
}
 
CButton ::OnLButtonDown(){
    ChangeState(BTN_STATE_DOWN);
}
 
CButton ::OnLButtonUp(){
    ChangeState(BTN_STATE_NORMAL);
    FireEventOnClick();
}
 
分析实现代码,大家可以看到基本思路是这样的
1.       确定按钮有几个状态,然后根据这些状态下按钮的外观,确定如何实现OnPaint函数。
2.       处理WM_LBUTTONDOWN,WM_MOUSEMOVE等输入消息,在这些消息里改按钮的状态
3.       在WM_LBUTTONUP,WM_KEYUP等消息里,Fire一个按钮自己定义的OnClick事件
 
很好,我们的软件用上了新按钮,问题解决了,产品经理和设计师都很满意。咱也结束了一天的工作,安心回家睡个好觉。
 
又过了几天,公司的产品总监在产品会上提到,我们要统一所有产品的交互逻辑,特别是某些按钮,根据现在的交互规范,应该加上醒目的图标。会后设计师打开PHOTOSHOP,很快就把按新规范调整的按钮发给了我。如下图
这次我没有立刻开始实现,因为公司的另一产品也用到了我实现的这个按钮,我要保证原有的按钮能继续正常工作,我打算和那边的研发商量一下怎么改。这里我有两个方案。
方案一:
1.定义 CIconButton,继承CButton
2.CIconButton添加方法SetIcon
3.在CIconButton::OnPaint里添加如下代码
OnPaint() {
    DrawBkg(m_state);
DrawIcon(m_state);
    DrawText(m_state);
}
4.使用新的CIconButton完成需求
 
方案二、
1.在CButton里添加两个扩展点回调
OnPaint() {
    if(OnDrawBkgCallback())
       DrawBkg(m_state);
    if(OnDrawItemCallback())
       DrawText(m_state);
}
并提供设置回调的接口
SetOnDrawBkgCallback()
SetOnDrawItemCallback()
2.产品通过SetOnDrawItemCallBack完成需求
void OnBtnDrawItemCallback()
{
   DrawIcon();
   return true;
}
m_button.SetOnDrawItemCallback(OnBtnDrawItemCallback);
 
 
   在方案的选择上,大家有了一些争议:方案一看起来比较直接,而且实现起来也比较快,最重要的是使用我的控件库的产品开发工程师特别希望这么改;而方案二实现需要更多的代码,并且产品开发工程师需要学习和编写更多的代码才能完成这个需求,不过这个方案的未来我更看好。最后我们选定了一个方案(在这里选哪一个都能完成需求),加班写好代码,提交测试,发布新的界面控件库,制作新的安装包,一阵忙碌,这个小小的需求升级总算是结束了。
 
  又过了几天,公司大老板通过邮件告诉大家,公司产品的大客户比较喜欢Windows Rabbion风格。于是产品经理再次调整设计,要求一些按钮要改成如下样式:
 
 
   很明显,这次图标跑到按钮文本上面去了,这就是最新的时代潮流。虽然很不情愿,但这不就是生活么?隔壁卡位的设计师,半个小时搞定了所有需要调整的地方的效果图,按时下班回家了,而我们工程师还必须加班! 这次的工作量就和上次修改的方案有关了:
如果我们上一步使用了方案一,这次我们日子没那么好过了。我们需要创建一个新的CIconButton2继承自CIconButton,再加一个新接口SetIcon2,可以在设置图标的同时调整图标在文本的什么位置(这次还耍了个小聪明,这个接口可以支持图标在文本的上下左右4个方位) 。
 
而使用方案二的优越性则在这里得到了体现:作为按钮的开发者,并不需要修改一行代码,你只要继续指导一下需要实现这个需求的产品开发工程,调整一下OnBtnDrawItemCallback的实现即可。
  
  实际上,在迅雷,我们有过一次在方案一这条路上走到头的经历,迅雷的代码里有一个叫 CXLButton的怪物,提供了近200个接口,有快2万行代码。当编写这个类的哥们走人以后,接手这个Class的新人发现这个按钮的用途(在最后一个版本的需求里)只是用来实现一个图标在文字上面的工具栏按钮,”这垃圾代码是谁写的!”,他逢人就要这么嘲笑一下,然后花了一个下午的时间写了一个新类 CXLButton2,清晰明了的实现了这个需求。但我们都知道,这是另一个轮回的开始。而且,有资格使用方案一还意味着控件开发工程师愿意帮助产品开发工程师修改接口与实现,如果这两组工程师不在一个公司,更大的成本会花在人与人的沟通上甚至无法进行。
 
 Microsoft提供的标准控件,通过类似方案2的方法提供可扩展性。方案二在过去,都被普遍认为是一个出色的解决可复用性问题的模式(我很少在招聘的时候能看到有人能在开发控件的时候通过这种方式提供可扩展性),但其缺点之一就是让控件的使用有点复杂,学习成本很高。比如我至今都没有通过MSDN 完全搞清楚过微软TreeView提供的这种扩展事件的细节,还好有一份泄漏的Windows源代码,让我对这些回调之间的关系能有清楚的认识。而且这个方案会 让产品开发工程师学习很多高度依赖控件库内部实现的知识,如果产品更换的底层界面库,那么大量的这种经验就没有意义了
  而且,方案二真的能以不变应万变,支持所有的需求变化么?大家可以自行思考。
 
  通过这个可扩展的按钮控件的开发实例,我们总结了三个交互开发的经验:
一、 交互是最易改变的需求
二、 开发一个可复用的交互控件的主要工作有
1)根据最初的需求决定方法,事件
2)根据最初的需求决定如何实现OnPaint,如何将原始的OS输入事件转成逻辑事件
3)在OnPaint里织入扩展点。
   三、交互开发涉及的技术点很多,实现时注意细节,注规避常见问题,当绘制特别复杂的时候还要仔细优化性能。
 
第三部分  新的思路
   从头观察上面的例子,我发现: 不管怎么修改交互需求,隔壁卡位的设计师总是很少加班,他们的工作量似乎能很好的和交互需求的修改量成正比,而且没有什么局限。相反,我们的工作总是存在一些天花板,一旦需求改变越过了开发时预设的一些前提条件,需求修改的响应时间会大幅增加。
   不管是通过那种方法,有限的接口和有限的扩展点,理论上满足不了近乎无限的需求变化。而我们观察所谓的通过事件扩展控件的方案,其核心是希望OnPaint能够根据各种参数,调整工作流程。而OnPaint里实现的功能,说到底就是 “ 在指定位置使用适当的函数绘制文本或贴图”。要想让一段代码的流程具有很强的可变性,是非常困难的。从某种意义上说,OnPaint函数就是万恶之源。
  明白了问题的根源,那么如何解决问题就有了方向:我们要在交互开发的过程中干掉OnPaint。实际上在这之前已经有技术实现了这一点:HTML本身并没有提供绘制函数,而实际上,前端交互开发是目前所有交互开发技术里流程分工合理,并且需求修改响应速度保持线形增长,容易学习,有完善工具链条的技术。我们可以在传统app开发里使用HTML技术么? 很多人回答YES,并做出了开创性的尝试。不过经过了一些思考之后我们并没有选择HTML(原因我们以后会在另外的文章里详细的介绍)。经过了一些借鉴和总结,我围绕这个问题提出了一个新的概念: 可以通过定义原子 UI 对象(UIObject) 之间的父子关系和位置关系组成对象树(UIObjTree) 来描述界面,UIObject 的类型是相当有限的。而按逻辑构建的UIObjTree 的实际上组织了OnPaint 里“按什么样的顺序在什么地方画什么”的问题
   这个概念有点抽象?还是你已经完全明白了?都没关系,还是刚刚例子里的按钮,按这种方法分析以后,我们会得到一个怎样的UIObjTree呢?
   非常直观的对象树,只有两个UIObject。接下来的需求修改就变成了修改这个数据结构。而树结构的修改接口提供起来是非常简单缺 完备的:添加节点,删除节点,查找节点,修改节点属性。我们看看在这个结构上如何响应上面的需求
 
 
   你会发现抽象UI得到的UIOjbTree会与设计师提供的PSD图结构非常相似,这就非常符合真正的开发情况:开发得到的需求并不直接来自产品经理,而是来自设计师。 如果技术能够将设计师的成果直接转化成 App 可以使用的数据结构,那么这样的技术无疑在实现能力和开发效率上,能与设计师相同。(这个理论迅雷通过迅雷7 ,XMP多个产品的大规模实践,已经得到了有效的证明)所以BOLT界面引擎的创新,根本上是提出了一种新的抽象交互的思路。
  这里建议您可以考虑将一些常见的控件,或则您现在正在开发的软件的交互解构成UIObjTree,试一试吧。
 
         围绕这个概念来构建整个界面引擎,我们还获得了一系列进一步的好处:
 
基于同样的模式分析交互,能输出近乎一致的结果。
可以将界面布局从逻辑代码中独立出来。
对界面的抽象是平台无关的。(虽然我们没有把跨平台作为BOLT界面引擎的关键目标)
让对象的属性在一段时间里按规律变化,就能实现动画。开发各种特效也有了一致的思路
基于对象树枝叶嫁接的复用和界面模块划分,易于理解。并提供了统一的可访问性 (所谓的可访问性,是指你可以在控件的开发者不提供实现的基础之上,只通过UIObjTree的树操作借口,就能访问并修改其表现)   
   
交互开发技术的变迁
 
整理上面的思路,我们得到交互开发技术,或则就是界面库的分代标准:
第一代 :SDK开发,使用系统默认控件
第二代 :基于窗口子类化的自绘控件皮肤库。绘制通常基于GDI。控件类型和系统一致。
第三代 :提供一套完整的体系(窗口,绘制等),所有的控件都基于这个体系开发。本身提供很多功能更强的控件,并有统一的方法开发/使用新控件。这一代库主要是解决实现能力问题。由于Windows的系统限制,二代库有很多功能局限。(比如按钮发光这个需求需要按钮的实现代码可以绘制到按钮子窗口以外的)
第四代 :布局文件+脚本语言的开发模式,依靠”组合”抽象界面并围绕这个概念搭建 。不提供控件但提供易学易用的控件开发模式。不提供内置特效但能让使用者轻易开发自己的界面特效。
 
这个分代标准基本上是参考windows上的界面库发展而制定的,第二代和第三代库本身其实没有什么本质上的区别,主要是Windows由于兼容原因,提供的UI相关API和窗口混和器过于古典(子窗口与父窗口之间只有遮盖关系,没有办法混合,很多GDI函数都无视Alpha通道),使得开发一套完整的界面库反而还需要自己实现窗口管理和图形库这些有一定技术门槛的基础设施。新的OS本身提供的UI框架本身就解决了这些问题,所以这些新的OS上的库就直接是3代库了。
 
需要指出的是,有一些技术框架也提供基于组合的方法构建界面的方法.比如现在相当流行的iOS上的cocoa框架,很多时候可以只通过组合各种View的方法来创建新的控件。但这个框架我依旧认为他是三代库,因为框架还是依旧以绘制为核心构建,比如你可以组合多个不同属性的按钮构成一个新控件,但你无法获得按钮上文字的SubView,没有获得统一的可访问性。
 
第四部分 Bolt 界面引擎概念介绍
 
关键概念
       使用XML文件来定义UIObjTree,用Lua脚本来实现界面逻辑
       UIObject的类型包括ImageObject,TextObject,还有一些精心设计的原子类型  《引擎内置的元对象介绍》
       围绕核心概念建立了一系列辅助设施
 
基本工作原理
    
 
BOLT界面引擎这里提供了两个重要的核心概念HostWnd ,Render。Render可以把一颗构建完成的UIObjTree渲染成一张位图。而HostWnd是界面引擎核心与操作系统之间的桥梁,能把这张位图通过系统提供的API画到屏幕上,并能转化操作系统的键盘/鼠标等事件成为引擎的定义得标准输入事件(Action)。
 
这张图里还提到了我们的UI资源管理模块和XML布局文件读取与管理模块。
 
Render 的基本工作原理
 
   DirtyRect(脏矩形)是驱动Render工作的核心。关于脏矩形,在很多2D游戏开发的文章里都有详细介绍。当一颗UIObjTree上的产生了脏矩形,其对应的Render在下次渲染时就会开始有动作,否则就什么也不干。Render会选取与脏矩形相交的所有UIObject,然后按这些UIObject的z-order排序(从小到大)排序 ,再按这个顺序依次调用这些UIObject的Draw方法。由于所有的UIObject都是由引擎实现的,所以这个Draw方法也是一个不可见的内部函数,Draw的实现会调用一些我们精心优化过的图形图像绘制函数,这一切就构成了Bolt界面引擎的高速渲染引擎。
 
注:虚线表示的矩形是脏矩形,那么Render只会渲染与这个区域相交的UIObject。
 
BOLT 界面引擎里控件的概念
         基于UIObject和UIObjTree,开发控件就是设计一个可复用的“对象树片断”,而使用控件就是由界面引擎完成这个对象树片断如何嫁接到对象树上。
这里通过一个简单的例子演示一下这个过程
 
这样的一个MessageBox。抽象成UIObjTree
我们可以定义Button的对象树片断是:
合并以后
 
而且很明显,引入控件的概念,能把一颗复杂的UIObjTree分解成多个片断交给团队开发,而最后合成的UIObjTree依旧保持了各个节点的可访问性。
 
第五部分  未来展望
 Bolt界面引擎的核心概念,是完全创新与独立的,并不依赖任何操作系统。所以BOLT界面引擎的发展方向之一是把界面引擎移植到各种各样的平台,目前最成熟稳定的平台是Windows,有工业级产品的质量。我们在Andiord平台和MacOS平台都有初步移植的版本,但我们还需要花费很多精力在合适的机会完成让这些平台的界面引擎更完美
   我们在移植到Andiord平台时发现,界面引擎要想在这些手持设备上流程运行,原有的基于CPU指令集优化的高速渲染器是不好的。一是CPU性能达不到,不够流畅,二是太耗电,这里迫切需要使用设备提供的硬件加速功能。得益于界面引擎不希望用户编写绘制代码的核心概念,我们只需要重新调整高速渲染器就可以实现硬件加速。目前我们正在尝试各种各样的支持硬件加速的框架方案:既能高效使用硬件的能力 ,又能兼顾CPU算法的灵活性。
   我们目前的主要精力都放在引擎本身的完善上,但实际上,如同HTML与Dreamweaver一样,界面引擎在设计之初就可以让布局部分由使用专业工具的专业人员完成,而不是负责编写逻辑代码的工程师。我们对我们的布局XML格式的简单优雅和可扩展性都有充分的信心,我们衷心的希望各位同行在理解认可了我们的概念后支持我们的标准,这样大家就能开发各种辅助工具,互相通用,共同改进BOLT界面引擎的工具链支持。
 
作者:Bonker
出处:http://www.cnblogs.com/Bonker
QQ:519841366
       
本页版权归作者和博客园所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利
目录
相关文章
|
Web App开发 缓存 移动开发
V8 JS AOT化的探索与实践
JS 语言的动态性非常优秀,其弱类型等语言特性也使得一线业务开发者更容易上手,但这也导致 JS 每一次运行前都要重复编译,使得 JS 的执行性能不理想;虽然之前 UC 内核有做过 Code Cache 方案,但支持的场景不够完整,与原生 Native 的技术方案比,尤其是首次启动场景(如各类大促活动等)还是有比较大的差距。为了能尽可能做到与 Native 对标,缩小性能差距,同时让业务开发者无感,我们开发了 JS AOT 功能。本分享将结合目前集团内自有业务形态,以及 JS 在 Web 中的执行过程,介绍JS AOT是如何设计和实现的,以及能给业务带来哪些收益。本篇分享来自阿里巴巴的喻世江在第
2931 0
V8 JS AOT化的探索与实践
|
存储 Java Android开发
Android插件化动态加载apk
支付宝作为一个宿主apk提前将要集成的apk作为一个插件(plugin)下载到本地,然后当使用该plugin(apk)的时候再去加载对应plugin(apk)的资源文件以及对应的native页面。就是不去安装plugin(apk)就可以直接运行该plugin(apk)中的页面。
1233 0
|
安全 网络协议
最新可靠好用的DNS服务器地址汇总
如果修改DNS服务器地址就可以访问google等服务,你还等什么?使用免费DNS解析服务除了去掉了运营商的各种广告,还有个最大的好处就是不会重定向或者过滤用户所访问的地址,这样就防止了很多网站被电信、网通劫持,有利于提供访问一些国外网站的成功率 如googlecode,网友应该养成不使用默认DNS的习惯,笔者汇总了常用可靠的DNS服务器地址。
16645 0
|
12月前
|
机器学习/深度学习 人工智能 数据可视化
生成AI的两大范式:扩散模型与Flow Matching的理论基础与技术比较
本文系统对比了扩散模型与Flow Matching两种生成模型技术。扩散模型通过逐步添加噪声再逆转过程生成数据,类比为沙堡的侵蚀与重建;Flow Matching构建分布间连续路径的速度场,如同矢量导航系统。两者在数学原理、训练动态及应用上各有优劣:扩散模型适合复杂数据,Flow Matching采样效率更高。文章结合实例解析两者的差异与联系,并探讨其在图像、音频等领域的实际应用,为生成建模提供了全面视角。
2344 1
生成AI的两大范式:扩散模型与Flow Matching的理论基础与技术比较
|
10月前
|
人工智能 JSON 自然语言处理
多快好省,Qwen3混合部署模式引爆MCP
本文介绍了MCP(Model Context Protocol)与Qwen3模型的结合应用。MCP通过统一协议让AI模型连接各种工具和数据源,类似AI世界的“USB-C”接口。文中详细解析了MCP架构,包括Host、Client和Server三个核心组件,并说明了模型如何智能选择工具及工具执行反馈机制。Qwen3作为新一代通义千问模型,采用混合专家架构,具备235B参数但仅需激活22B,支持快速与深度思考模式,多语言处理能力覆盖119种语言。文章还展示了Qwen3的本地部署流程,以及开发和调试MCP Server与Client的具体步骤。
3024 36
多快好省,Qwen3混合部署模式引爆MCP
|
机器学习/深度学习 编解码 搜索推荐
实测13个类Sora视频生成模型,8000多个案例,一次看个够
SORA-like模型是一类基于OpenAI的SORA模型发展而来的视频生成技术,以其在生成高质量视频上的卓越表现受到关注。该模型不仅提升了视频的分辨率、自然度和视觉语言对齐,还增强了对长视频序列的可控性。适用于内容创作、世界模拟等多种场景,展现出广泛的应用潜力。然而,模型在自动化评估、与人类偏好匹配及处理复杂运动上仍面临挑战。未来研究将聚焦于多模态、连续、交互式及个性化视频生成等领域。
1121 2
|
移动开发 JavaScript 前端开发
UniApp H5 跨域代理配置并使用(配置manifest.json、vue.config.js)
这篇文章介绍了在UniApp H5项目中处理跨域问题的两种方法:通过修改manifest.json文件配置h5设置,或在项目根目录创建vue.config.js文件进行代理配置,并提供了具体的配置代码示例。
UniApp H5 跨域代理配置并使用(配置manifest.json、vue.config.js)
|
缓存 Android开发
Skia深入分析5——skia文字绘制的实现
文字绘制主要包括编码转换(主要是中文)、字形解析(点线或image)和实际渲染三个步骤。在这个过程中,字形解析和实际渲染均是耗时步骤。Skia对文字解析的结果做了一套缓存机制。在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制。 1、SkPaint 文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关
7660 0
|
存储 SQL 关系型数据库
【MySQL技术内幕】6.1-锁、lock和latch
【MySQL技术内幕】6.1-锁、lock和latch
393 0

热门文章

最新文章