现在的位置: 首页 > 综合 > 正文

纪念一个曾经的软件产品(二)——基础技术与贴图

2012年03月29日 ⁄ 综合 ⁄ 共 7008字 ⁄ 字号 评论关闭
文章目录

“很多O2O领域创业者每个人心底都有个“大而全平台”的想法,但创业伊始就那么做往往不现实,要充分考虑手头‘可调配资源’,这里指的是自身有没有对内对 外hold住的能力。希望看到更多的满怀远大理想、认认真真做事踏踏实实执行的创业者,别就伟大蓝图却抓不住切入点,必须能找准垂直细分做透!”——这是最近在weibo上看到的一句话,深有感触,我想互联网行业发展至今,对我等草根而言,大而全的平台是越来越不可能的了,其实当初按照老吴的想法,就是想把这个软件做成一个平台……这个具体我就不展开了,还是继续谈谈这个项目及其相关技术,希望有人能从中获益。

三、基础技术

3.1,C++还是.net

我在着手做这个项目的时候,使用的是C++作为开发语言,说起C++,我话就多了,我不想在此挑起无聊的语言之争,但我想说:每一个程序员都应该掌握C++。当然,这个项目使用C++,而不是.Net不只是我个人偏好的原因,考虑到Windows Mobile的硬件和许多实际情况,唯有C++的性能能够应付得来,引用当初在Windows Mobile开发群里一位老兄的话:“用.net的话不是快和慢的问题,而是根本能不能运行的问题。”程序中使用了大量的贴图,系统通知消息处理,注册表访问,甚至硬件操作,这些功能使用原生代码(Native Code)实现的话就很方便,而使用.net可是半点优势都没有,还不说很多Windows Mobile手机都没有安装.net,如果强制用户安装的话还会一定程度上遭到用户的抵触。

注:Windows Mobile下的.net全称叫“.NET Compact Framwork”,和Windows环境下的.net是不太一样的,有兴趣的话可以参考[维基百科]。

我使用了纯粹的Windows编程,直接使用Windows API来写这个程序,连MFC都没有用到,因为在这么一个特殊的应用里,那些类库帮助不大。

那今天回头看看,正好提些问题:前面说到性能,那么Android用Java来开发,性能差吗?今天还存在前面说的那些问题吗?——很大程度上,这些问题都没有了,因为今天的手机的性能实在太强悍了,四核手机都开始普及了,Android系统也在规范化,Google已经着力在改进Android碎片化的问题,开发者不必费尽心思去挖掘系统底层的那些函数和方法来实现自己的某些功能了。

3.2,今日插件还是普通程序

Windows Mobile从它还是Pocket PC的时候开始,就有了一个叫“今日”(Today)的桌面,就相当于Windows的Desktop,大家看看下图:

(Windows Mobile的“桌面”——Today)

是不是觉得特别老土?还有这么小的东西怎么用手指操作?但它这么设计也是有它的道理的,过去的手机跟现在很不一样,有些根本没有触屏,还是靠导航键和OK键来选定,如这款Samsung i718手机,具有一定代表性:

(Samsung i718)

即便有触屏的手机,恐怕也很难用手指操作,大多数是这样操作的:

(用笔尖操作触屏)

无限今日的做法就是做一个桌面插件,把所有的内容渲染都做在这个插件上,当然,SoSoPi也有,这是后来添加的功能,但仅仅是给用户一个方便的启动SoSoPi的功能:

(SoSoPi今日插件)

SoSoPi并没有做成插件的形式,这一部分是因为今日插件的调试难度相对较大,因为插件有一套规则,按照那套规则去生成一个dll,然后把这个dll attach到Windows Mobile的shell进程去方可调试,另一部分则是老吴的意思,他希望程序能够被隐藏掉,让用户看到自己的“桌面”,也就是“今日”。

3.3,解决一些棘手的问题

3.3.1 处理挂机键隐藏所有窗口的问题

Windows Mobile的手机基本上都有一个“挂机键”,大家对这个键应该不陌生,过去的手机都有的,(参考上文Samsung i718的图片)一个拨号键(通常是绿色的),一个挂机键(通常是红色的),挂机键默认的行为是挂掉电话和显示桌面,所以当自己想撤销掉一切工作,隐藏掉所有程序的时候,猛按挂机键总归没有错,可惜后面出来的手机基本上都去除了这经典的红绿两键。挂机键隐藏所有程序这个功能好是好,就是有点为难我了,因为我的程序作为一个“桌面程序”,是不希望被这样隐藏掉的,我尝试了很多方法,包括用钩子捕捉挂机键事件、隐藏后自动出现和屏蔽这个按键等等,效果都不理想,最后还是在开发群里得到了最后的解决方法,说出来很简单,就是在创建窗口的时候多加一个“WS_POPUP”属性。我的天啊,就这么简单?

真的就这么简单,可知道这么干却花掉了我好多时间,正所谓会则不难,很多事情就是这样……大家在开发过程中有没有过类似的经历?

3.3.2 全屏/非全屏的问题

从技术意义上说,Windows Mobile的绝大多数程序都是“全屏”的,也就是说,一个屏幕上只显示一个程序,所有程序默认都是“最大化”,而这里的全屏指的这是:隐藏掉任务栏,隐藏掉工具栏,隐藏掉输入法按钮,并把程序窗口大小和位置调整为铺满整个屏幕。这样才是对“全屏”的技术描述。

(任务栏,菜单栏与输入法按钮)

这些看起来不是问题的东西,在Windows Mobile下怎么都成了问题,原因就是碎片化,我们现在听得最多的碎片化我想来自于Android系统,每个手机厂商都想对系统进行一些差异化定制,有意无意地修改了系统的默认行为,导致软件厂商在做开发的时候遇到一些不兼容的问题,我想说的是:对比Windows Mobile,Android真是好太多了,起码它还有些章法可循,而Windows Mobile则毫无章法,这个我在之后会详细说。

最后,我找出了一种比较好的,让程序能够全屏显示,并且轻易切换到非全屏显示的方法,在诸多机器上测试通过,没有太大问题,当然,其中也是经过了大量的尝试。代码片段是这样:

            if(g_gm.IsFullScreen())
            {
                // It is the responsibility of the application to make sure it is sized
                // FULL SCREEN before using this flag. Otherwise, it will appear as though
                // the function did nothing.
                //     -- MSDN
                RECT rectFullScreen;
                SetRect(&rectFullScreen, 0, 0, GetSystemMetrics(SM_CXSCREEN),
                    GetSystemMetrics(SM_CYSCREEN));
                SetWindowPos(hWnd, 0, rectFullScreen.left, rectFullScreen.top,
                    rectFullScreen.right-rectFullScreen.left, rectFullScreen.bottom-rectFullScreen.top,
                    SWP_NOZORDER);
                SHFullScreen(hWnd, SHFS_HIDETASKBAR|SHFS_HIDESIPBUTTON);
            }
            else
            {
                SHFullScreen(hWnd, SHFS_SHOWTASKBAR|SHFS_SHOWSTARTICON|SHFS_HIDESIPBUTTON);
                RECT rectWorkArea;
                SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)&rectWorkArea, 0);
                SetWindowPos(hWnd, 0, rectWorkArea.left, rectWorkArea.top,
                    rectWorkArea.right-rectWorkArea.left, rectWorkArea.bottom-rectWorkArea.top,
                    SWP_NOZORDER);
            }

注意看看那段注释,都是细节,这种细节在Windows Mobile的开发过程中遇到太多了,这个还好,其它很多则根本就没有文档提及。

(SoSoPi的全屏与非全屏)

四、贴图

4.1,为什么用贴图?

开发一个程序的时候,我们习惯于用控件,按钮,下拉框,单选框,复选框,文本框……可你认为这些东西适合用在SoSoPi吗?试想想SoSoPi的首页需要多少个怎么样的控件,其它页需要多少个?这样加起来可不得了,要知道,在Windows编程中,每个控件其实就是一个子窗口(Child Window),而Windows Mobile的资源是十分有限的,它的开发文档上并不推荐我们在一个窗口中放置大量的子窗口,另一个重要的原因就是用控件不利于界面的定制,SoSoPi界面上很多效果的实现都跟现有控件的行为差距甚大,控件对我的开发没什么帮助。

4.2 DirectX?

说起DirectX,我是有点心有余悸的,这东西太晦涩难懂,所以我们在Windows下编程,需要图形加速的话通常都用现有的引擎,不太会自己再重新去研究DirectX接口了,我很清楚它的难度,因为这事情我以前做过,太多图形学专业的东西我不懂,出不了什么成果。而现在,要做些简单的动画效果的话别的方法很多,WPF,Silverlight,Flash,甚至HTML5,总之就是不要自己再去碰底层,否则就算写出个凑合能用的东西,回头一看也是通篇的垃圾代码。

另一个关键的原因是:经过一系列的研究,我发觉Windows Mobile根本就不支持DirectX加速,虽然有接口提供,但调用会出现莫名其妙的失败,并且每台手机的行为不一致,贴图速度根本没有任何提高,我的结论就是:使用DirectX不会提高效率,反而会提高代码的复杂性。

4.3 位图及贴图效率

位图分为两种,DIB和DDB,DIB(Device Independent Bitmap)是设备无关位图,DDB(Device Dependent Bitmap)是设备相关位图。简单地说,能在各种不同的设备上显示出来的位图就是DIB,否则就是DDB。那我们常见的jpg文件是DIB还是DDB?——肯定是DIB了,jpg哪都能用对吧?bmp呢?还不是?哪都能用啊。那DDB是什么?DDB存在于你的显示设备的内存中(嗯,俗称显存),你之所以能在屏幕上看到你的照片,那是显存中有这么一个区域,摆放了你的照片,这个区域的数据格式是设备相关的,不同的显卡、分辨率和色深,都可能导致其格式不一样,但它是最直接的位图显示方式,所有的DIB要真正显示出来,都要转为DDB。

我花了很多时间去研究贴图效率,研究的结果竟然是这个:要想贴图快,就得把那些要描绘的图转为跟Windows Mobile的显示设备相关的位图。

将一个设备相关位图,通过BitBlt绘制到窗口上去的速度是相当快的,我调试下来发觉一张满屏的位图通常只需要1-2ms,这么快的速度足够实现流畅的动画效果了!

Windows Mobile的设备相关位图用两个字节(16bit)来表示一个点(俗称16位色),RGB分别占据5bit、6bit和5bit,用16位色而不是24位色我想完全是从资源节省的角度考虑。那现在问题来了:我要贴半透明的图片怎么办?

半透明的位图,不可能是DDB,前面提到了,用16bit表示一个点,里面根本就没有表示半透明度的地方啊?这个我还真没什么好办法,所以我在设计的时候,就尽量把半透明贴图做得小一点,这样运算量会少很多。半透明贴图总的来说是要比DDB的BitBlt慢很多的,一张全屏的半透明贴图,得花上100ms左右,在较快的机器上,也得花上20-30ms。

在把这些主要的问题都研究透彻之后,我写了一个Demo程序,用来演示类似HTC Sense的底栏的滑动的效果,拿给老吴看,老吴赞口不绝:“太厉害了,居然比官方(HTC)的还流畅!”老吴这样的夸奖还是很少见的,看得出来他当时很兴奋。

关于贴图相关的技术,我有一篇博客: http://www.cppblog.com/guogangj/archive/2010/06/20/118316.html

4.4 程序的贴图方式

我省略掉了图片加载的具体过程,我有些担心本文因为描述了太多的细节而对读者失去吸引力。而贴图方式则是一套很混杂的代码,为什么会这么混杂,还不是因为要提高那点效率,我在代码中使用了大量的技巧,这里面已经很难用文字描述清楚了,所以我画了这么一个图,演示手指按下滑动区滑块从一个模块切换至另一模块的贴图过程。

(贴图原理图)

1,手指按上滑块的时候,就截个屏,把屏幕的内容存放到一个缓存位图中,这个缓存位图称为“FastSurface”,这是我起的名字,意思是帮助快速贴图的缓存位图,它是一个设备相关位图。这个动作是相当快的,通常只需要1-2ms。再在这个FastSurface上绘制一层半透明遮盖,这个动作是一个半透明贴图,执行速度较慢,在比较慢的手机上大概需要100ms左右,但所幸的是在这个移动滑块的过程中,只需要一次。

2,为了使得绘制过程不出现闪烁,我创建了一个跟程序窗口大小一致的设备相关位图,叫“BackSurface”,所有最终要绘制到屏幕上的东西都先绘制到BackSurface,现在,把刚才生成的带半透明覆盖层的那个在FastSurface上的位图绘制到BackSurface的对应的位置去,这个过程很快,也是1-2ms。

3,滑块区背景和任务栏上的内容我同样创建了设备相关位图作为其缓存,现在,把它们也绘制到BackSurface的对应位置去,这个动作也是很快,1ms这样的开销。

4,接下来把要展现在上层的一些元素绘制到BackSurface上去,包括滑块区的图标,滑块,屏幕中心区图标和文字,这些内容会随着用户手指的移动发生改变,所以无法缓存,只能直接绘制,而且大多是半透明贴图,开销相对较大,但由于要贴图的图片都不太大,所以通常都能在30ms左右完成。

5,最后就是把BackSurface贴到屏幕上去。这个1-2ms的耗时。

手指在移动的时候重复动作2到5,就能看到动画效果,在很旧的机器上,也能达到20多帧每秒,已经算是极限了。而在性能较好的机器上,相当流畅,如老吴的HTC HD2,用起来是很爽的。

从这个例子看,我的基本的思路就是:减少绘制!把不用每次都要绘制的元素,缓存到设备相关位图里,只绘制必须绘制的。

实际的绘制过程还比我上面描述的要复杂一些,这其中还有一些细节,比如剪图,在绘制滑动区的图标的时候,要将超出边界的部分剪掉,这些细节我就不一一列举了,从原理上看,贴图基本上就是这样。

也许你要问:那你看看那些Windows Mobile上的游戏,为什么能达到那么流畅的效果呢?而且还是3D的。——呃,这个问题我还真不知道怎么回答,我确确实实用HTC HD2玩过Windows Mobile版的极品飞车,效果还不错,但我真不知道这是怎么做出来的,众所周知,Windows Mobile是一个完全可以由设备制造商定制的系统,也许有不少手机制造商在其中加入了对3D渲染的硬件支持,使得运行这些大型游戏成为可能,你想确实这些游戏也并非所有手机都能跑呀。

最后,关于贴图,我还要提的一点就是:由于程序运用了大量的这种前面所提及到的“技巧”,其代码也就变得有些难维护了,如果后面还有人来维护这套代码的话(当然这个可能性为0,这里只是学术性的“如果”),他得好好看看这篇文章,明白了我的意图之后,才能比较好地去做维护,编程中其实这种技巧运用得越多,代码的质量恐怕也就越糟糕,但这是没办法的。

4.5 图片旋转

使用Windows GDI、GDI+或WPF去旋转一张图片是不难的事情,可Windows Mobile不支持图片的旋转。当我需要绘制一个模拟时钟的时候,想用贴图的方式来绘制时分秒针,就真是个问题了,我尝试了许多的方法,最简单的方法当然是用绘制线条的方式代替位图旋转,但效果并不好,而且这样做不利于个性化,最后我找到了一种用二次线性插值旋转位图的方法,代码有少少复杂。旋转的效果图如下:

(图片旋转效果)

大家可以清楚地看到,旋转后的图片尺寸发生了变化,图片本身也会“走样”,这是显而易见的,那么,我们如何绘制一个模拟时钟呢?——必须确定针的中轴位置,所以我们规定:作图的时候要把针轴放在最中心点!这样一来,不管怎么旋转,针轴还依然是在最中心点……这样,有问题吗?各位。

(时钟的针)

有问题!举个例子,如果你做了一张5*5的图,中心点显而易见是(2,2),可一旋转,这张图可能就变成了6*6,那中心点请问是……所以按照这种旋转图片的方法绘制出来的模拟时钟,仔细看总归是有那么一点瑕疵,经过无数次的实验和校正,我都没办法绘制出一个完美的时钟,我认为这个问题“无解”。事实上并非无解,公司的美工就拿了一个别人的产品给我看,这是一家国外公司的产品,所绘制出来的模拟时钟真是十分精美,而且时分秒针动起来可是相当的完美,我想不出他们是如何实现的,也许是专门分别绘制了15幅时分秒的贴图,(水平/垂直翻转图片不会出现走样,所以只需要15幅图,而不是60)而不是采用旋转图片的方式,我猜的。

最后,SoSoPi并没有提供模拟时钟的绘制功能,因为我编写的那个二次线性插值的位图旋转方法实在太慢了(其实我已经尽力优化它了),一个稍大的图片的旋转,都得耗上几百毫秒,耗时也耗电,所以最终还是拿掉了。

(待续...)

抱歉!评论已关闭.