2015-11-04

写Android的一点事

昨天把Android的VectorDrawable不能rotate 90度的bug给提了issue.
早上起来check邮件的时候已经变为assigned了,大概确实是个bug了.

这个的原因之前也大致提过下.
基本就是算误用了transform Matrix的一些维度吧.

在旋转90度的时候,这个3*3 matrix的MSCALE_X和MSCALE_Y项目会是近似0.
而VectorDrawable的draw实现是直接拿这两个值乘width/height做scaling的.
其结果是算出个零,只是short path return什么都不做了.

于是结果就是rotate 90度的时候,canvas上什么都没有.

看了下注释,提到用这两个值的原因是避免blur.
翻了下commit log,最近的改动的引入也确实是为了解决这个原因.

不过就实际经验来说,抛开90度旋转的问题,这个blur还是存在的.
这个在自己做animation的时候,从0到90度之前的几帧可以看到.

而且翻看了下目前的具体实现,貌似还不是直接走的canvas的通道.
而是draw到一个临时的canvas的bitmap上面,再把这个bitmap draw回提供的canvas上面.

倒是有些不知道当初是如何定位到blur的问题跟这个有关系的.

下午简单地实现了一个半成品的VectorDrawable.
直接是draw到提供的canvas上面,加上animation也没发现有blur的问题.

不过也可能是因为有些功能并没有完全实现的原因.

毕竟,自己写的只取了vector xml定义里的部分值.

一个是vector element的viewportwidth/height.
这个主要是拿来算scale的.

毕竟,虽然说上面提到的Matrix里有两个值有时候能拿来直接动作scale系数.
但终究来说,这个matrix事实上可能是一系列transform的composition而不是单一的scale matrix.
所以直接取值是不可取的.

因此,需要一个svg/vector graph的原始大小定义,然后再通过在canvas上想要paint的bounds的大小一起算scale系数.

另一个是path element的pathData.
这个是类SVG的command string.
也就是实际的SVG/Vector graph的定义.

之前用javascript写的SVG也只用了lineto/moveto/cloase几个command,所以生成的SVG定义也只有这些.
本来想着直接导入的.
但Android的vector resource跟SVG的还不完全一样.

所幸的是这个command序列还是语意相同的.
也少了写麻烦.

所以在写这个的parser的时候,也只支持了这几个命令.

不过看了下,要加其他的话,倒也不难.
毕竟都是直接map到canvas的对应方法上面的.

而且基本上这个的parser也没什么好写的.
本身提供了XmlResourcesParser,自己只要实现个command string的parser就行了.

不过由于这个drawable到底不是原生的drawable,所以要取/构造还是有点不够友善的.

一般来说,都是直接拿context,然后直接通过auto generate的R id去拿drawable.

而现在,这个R id也还是指向原生的VectorDrawable的.

一个思路当然是去hack这个generate的过程,让它指向自己的这个实现.
不过明显的这个成本太高,侵入性太强.
估计至少要动到对应的plugin或者Android Studio.

退一万步说,即使找到了侵入点,那到底也是不兼容的改动,后面纯粹的升级维护纯粹是给自己找麻烦.

所以只能自己直接构造了.

要解决的第一个问题自然是怎么去读vector xml了.

文件路径倒是一目了然.
解析的话,如果不用XmlResourcesParser,随便用sax什么的也OK.

不过这种本身就已经侵入性很强的改动,就没必要添加无谓的不确定/稳定因素了.

翻了下原生实现的locate过程,其实也简单.
拿到AssetManager直接open就行了.

剩下一个就是文件路径的问题.

直接硬编码路径也OK.
但总体来说,不算一个特别优雅的方式.

跟了下相关代码,用R id去locate也不麻烦.
拿到对的Resources直接get value得到的就是了.

这里的问题有两个.
一个是Resources.

本身是有个Resources.getSystem()的.
不过从名字上看就知道是非app specific的.

app specific的也还是只能通过context拿.

另一个问题就是get到的TypedValue.

这里拿到的确实也是路径.
不过需要用value.string这种不太好看的方式来拿.

这里的点子啊与string是个public的field.
虽然说方便,不过看起来终究是有些碍眼.
奈何也没其他稍微顺眼点的方式.

然后既然xml读到了,也parse完了,构造自然是没什么问题了.

剩下的就是一些cache相关的.

原生的drawable一般都会带有个ConstantState,基本上就是对应drawable的实际image数据或者定义.

想想也不难理解.
像image的话,总不能每次都load一次.

所以有个ConstantState的设计,倒也挺符合直觉的.

只是对应的方法倒不是强制overide的,这点有点不太舒服.
毕竟原则上来说可以是abstract强制一下.

不过也无所谓了.

个人喜好问题.

其他的也就没什么了.

说白了就是基本上所有drawble都是到canvas的.
拿到canvas,什么都好说.

不过有一点就是,canvas这个并不是线程安全的.
因为其实也是一个transform的composition,多一个不同batch的modify一起concurrent的话,结果挺难预料的.

有想过看看能不能clone一下,然后parallel batch一下,最后在一个个apply/merge回来.
因为底层估计也是一些matrix的运算,应该可以独立.

不过看到是native的操作,一时也懒得去拉代码看开销,于是就算了.

当初写custom viewgroup的时候就想过.
如果现在的render pipeline对visual region/canvas的invalide能像NUMA一样,分块管理/invalidate的话,估计反而能省一些事.
直接invalidate所属的physical region就好了,交给邮件处理,而不是在拼命算dirty region.

当然,这个也可以软件模拟就是了.
每个visual element assign一个logical region,然后dirty就dirty整个region,然后每个run就batch update/redraw dirty就行了.

之前写custom viewgroup的layout的时候大致也是这个思路.

而且由于采用的取巧的top align的方式,one pass就OK,所以能直接parallel.

如果是像bottom align之类的,可能就不行了.

由于每放置/slice一块区域就可能得重新调整line height.
因为新加的元素的高可能超过了当前的高度,为了对齐得把该行的line height调整到行内最高的元素.
于是,就至少得对之前元素的offset bottom作调整.
无可避免地对整行做重新的layout计算.

所以,这里的一个思路就是,如果现代浏览器做布局的时候基本算法也是如此的话.
那么align-top和提取fix box width/height的话,估计对render性能有点影响.

不过涉及到GUI的,实际开销和代价值不值得,甚至有没必要就是另一回事了.

毕竟,这种有视觉元素的跟纯粹服务端的思路还是很不同的.

后者的哲学在于感觉不到存在.
无论是从性能还是容灾方面来说都是.

而带视觉效果的,评价体系就是相对离散化和多态了.

毕竟,人的视觉系统是一个有着明显边界分段的函数.




没有评论:

发表评论

爽文

去看了好东西. 坦白说,多少是带着点挑刺的味道去的. 毕竟打着爱情神话和女性题材的气质,多多少少是热度为先了. 看完之后倒是有些新的想法. 某种程度上来说,现在的年轻人或者说声音就像小叶. 只要说点贴心的话就能哄好. 也是那种可以不用很努力了. 留在自己的舒适区避难所小圈子抱团就...