之前在调试flash 11在ICS上的使用效果时(这个版本使用的是surfaceView,市场上下的,基本上都是用nativeWindow了),发现一个bug,bug的现象如图1所示(访问http://www.qiaqiafood.com,页面主体是一个flash):

android12 skia drawImageRect如何走GPU android graphicbuffer_shell

图1:错误的图片

android12 skia drawImageRect如何走GPU android graphicbuffer_shell_02

图2:正常的图片

Webpage加载时,没有问题,但当将网页上下拖动后,就出现如图1所示的黑色区域,且图像显示的层叠关系也不正常。

该问题则追踪过程不细说,也花了好久。对这个问题:

1.通过dump framebuffer的数据:

$adb shell cat/dev/graphics/fb0 > framebuffer.bin

查看结果,framebuffer中确实是花的,和屏幕上表现的一致,这不意外

2.接上hierarchyviewer,分析framebuffer,却发现这是好的

3.通过screencap -p /sdcard/screenshot.png 发现也是好的


有现象和重现方法,接下来分析原因了。

从现象上看,这个问题是与画图,也就是surfaceFlinger有关的。查看surfaceFlinger的状态:

#dumpsys SurfaceFlinger

重点关注下BrowserActivity:

+ Layer 0x95cc0(com.android.browser/com.android.browser.BrowserActivity)
     z=    21025, pos=(0,0),size=(1024, 600), isOpaque=0, needsDithering=0, invalidate=0, alpha=0xff,flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
     client=0x918a8, identity=40
     format= 1, activeBuffer=[1024x 600:1024, 1], transform-hint=0x00, queued-frames=0
           mBufferCount=2, mSynchronousMode=1, default-size=[1024x600],mPixelFormat=1, mTexName=3
           current: {crop=[0,0,-1,-1], transform=0x00, current=1}
           next   : {crop=[0,0,-1,-1],transform=0x00, FIFO(0)={}}
            [00] state=FREE    ,crop=[0,0,-1,-1], transform=0x00, timestamp=16875976105982, 0x919b0 [1024x600:1024,  1]
           >[01] state=QUEUED  ,crop=[0,0,-1,-1], transform=0x00, timestamp=16877388086666, 0x8b6a0 [1024x600:1024,  1]
 Region transparentRegion (this=0x95e60, count=1)
   [  0, 211, 1014, 715]
 Region transparentRegionScreen (this=0x95cf4, count=1)
   [  0, 211, 1014, 715]
  RegionvisibleRegionScreen (this=0x95cd0, count=2)
   [  0,   0, 1024, 211]
   [1014, 211, 1024, 552]

仔细分析输出内容,可以发现一个问题:browser的区域是有一个透明区域:

[ 0,211, 1014, 715]

这个区域把整个activity(对应的surface),划分为两个可见区域:

[ 0, 0, 1024, 211]

[1014, 211, 1024, 552]

Scroll一下browser的页面,再查看BrowserActivity的状态,两次一样,这个透明区域的位置坐标竟然没有随着scroll跟着改变

顺着surfaceView的创建和render过程,发现问题了:

Android将surfaceView(一般的Game, video及browserplugin会使用的一种view)在其layout的区域,会设成transparent(也就是挖洞), 但是其设置只是在attachWindow的时候做的。这样造成的问题是:transparent的区域是固定的,当一个页面scroll的时候,不会更新这个transparent区域的boundary,最终给hwComposer合成的boundary,和scroll之后,surface的内容对不上,造成hwcomposer合错

知道问题后,解决方法也可以知道了。

常规做法:让每次layout的遍历viewHierarchy时都做transparent的重置, 但这个影响面太大且较复杂(还没找到可行的修改方法)

workaround做法:给hwComposer的composer区域,不考虑transparent区域即可 (目前测试,还没看到会造成系统有什么不好的的影响),让它把surface的区域全部拿去计算(理论上效率可能有影响,但没观测出来影响)

修改方法,注释掉setGeometry@LayerBase.cpp中对visibleRegionScreen的赋值:

hwcl->visibleRegionScreen.rects =
           reinterpret_cast<hwc_rect_t const *>(
                   visibleRegionScreen.getArray(
                           &hwcl->visibleRegionScreen.numRects));

至于hierarchyviewer和screencap得到的screenshot为何是好的呢?这里可以看看screenshot的实现(在surfaceFlinger中),这个screenshot和framebuffer的合成还不是一回事,screenshot没有用到hwcomposer,只是通过openGL将各个layer直接draw到FBO中,这里就没有挖洞的区域了,实际计算的区域和workaround后差不太多了。

另外还有两个问题:

1. 在scroll 窗口和初次加载flash(会比网页慢), flash内容盖住navigation bar的问题, 如图

android12 skia drawImageRect如何走GPU android graphicbuffer_移动开发_03

图3:surfaceView overlap BrowserActivity

2. 网页Scroll的时候,flash内容的scroll和browser内容不同步的问题,不同步的区域会留有黑框。

覆盖的问题,由于surfaceView默认是放在browser的surface前面的,这个无解,要解只能将surfaceView放在borwser的后面(前面说过,由于surface有实现挖洞(transparent)的功能,实际上放在后面也是可以解决的)


而不同步的问题,参看今年的google IO关于webview的介绍,这个在surfaceView上应该是无解了,Google的解决方法,是使用Hardware Accelerated,通过clipLayer/mediaLayer的方式 (openGL的纹理贴图)。相信webview插件往后都不会用surfaceView实现了吧。

不过近期,我在某超大型跨国公司的同事,也碰到类似问题,现象几乎一样,也是跨进程使用Surface引起的这个问题。查看Android的release更新,4.0,4.1和4.2都没有修复这个问题