问题点:
在播放完关机动画的时候会出现一个动画之前的界面,比如我在锁屏界面长按power键关机,等关机动画播放完后会闪现一下刚才的锁屏界面,如果是在home界面或是某个应用界面关机,等播完动画之后也会闪现一下之前的home界面或那个应用界面,而且问题必现。
分析:
该问题在之前负责开关机模块的同事那里已经分析过,后经排查发现和移植的圆角功能有关,而圆角功能是我这边移植的,所以问题转到我这了。
圆角的整个feature是有开关控制的,一个是上层的prop开关persist.tcf.screen.corner,一个是native层的宏开关TINNO_CUSTOM_FEATURE_SCREEN_CAPTURE_EXCLUDE_SCREEN_CORNER,拿到问题之后我做了个初步验证:
1. 通过adb命令将persist.tcf.screen.corner设置为0,也就是关掉开关。重启手机后圆角的效果没有了,但是问题依旧存在。
2. 将TINNO_CUSTOM_FEATURE_SCREEN_CAPTURE_EXCLUDE_SCREEN_CORNER宏去掉,编译验证,问题消失。
由此可以得出结论是由于TINNO_CUSTOM_FEATURE_SCREEN_CAPTURE_EXCLUDE_SCREEN_CORNER这个宏定义导致的问题。而这个宏的作用是什么呢?其实就是处理截屏的。就是说在截屏的时候我们需要将圆角去掉,否则截出来的图片会有明显的四个圆角,不好看。
代码分析:
再看下对应的代码:
android/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:
#ifdef TINNO_CUSTOM_FEATURE_SCREEN_CAPTURE_EXCLUDE_SCREEN_CORNER
{
// this value fromandroid.view.WindowManager.LayoutParams.TYPE_SCREEN_CORNER -FIRST_SYSTEM_WINDOW * 10000
// some other app also needcaptureScreen for some Animation
//ALOGE("captureScreenImplLockedreqWidth=%d reqHeight=%d maxLayerZ=%d ", reqWidth, reqHeight, maxLayerZ);
if(reqWidth != 0 && reqHeight !=0 &&maxLayerZ > 390000) {
ALOGE("captureScreenImplLockedfix for 18:9 device XY (%d X %d) Z (%d -%d(->%d)) ", reqWidth, reqHeight, minLayerZ, maxLayerZ,390000);
maxLayerZ = 390000;
}
}
#endif
这段代码是在SurfaceFlinger::captureScreenImplLocked()中,当时看到这的时候还比较纳闷,看这方法名不是应该在触发了截屏动作才会走到的么?按理说关机流程中应该不会跑到这来,接下来就需要追下这个方法的调用流程了。
通过在搜索该方法的调用及加入的log确认发现从Java层到native层的调用关系如下:
SurfaceControl.screenshot()
->android_view_SurfaceControl.nativeScreenshot()
-> ScreenshotClient::capture()
-> SurfaceFlinger::captureScreen()
-> SurfaceFlinger::captureScreenImplLocked()
接着我又在SurfaceControl.java的screenshot()方法中加入log,让其主动跑异常,这样就可以继续往上看到这个方法的调用关系了,打印出来的日志如下:
01-0302:27:28.486 741 886 D SurfaceControl: call stack
01-03 02:27:28.486 741 886 D SurfaceControl: java.lang.RuntimeException: calledby
01-03 02:27:28.486 741 886D SurfaceControl: atandroid.view.SurfaceControl.screenshot(SurfaceControl.java:925)
01-03 02:27:28.486 741 886 D SurfaceControl: atandroid.view.SurfaceControl.screenshot(SurfaceControl.java:843)
01-03 02:27:28.486 741 886 D SurfaceControl: at com.android.server.display.ColorFade.captureScreenshotTextureAndSetViewport(ColorFade.java:483)
01-03 02:27:28.486 741 886 D SurfaceControl: atcom.android.server.display.ColorFade.prepare(ColorFade.java:153)
01-03 02:27:28.486 741 886 D SurfaceControl: at com.android.server.display.DisplayPowerState.prepareColorFade(DisplayPowerState.java:179)
01-03 02:27:28.486 741 886 D SurfaceControl: atcom.android.server.display.DisplayPowerController.animateScreenStateChange(DisplayPowerController.java:1269)
01-03 02:27:28.486 741 886D SurfaceControl: atcom.android.server.display.DisplayPowerController.updatePowerState(DisplayPowerController.java:728)
01-03 02:27:28.486 741 886 D SurfaceControl: atcom.android.server.display.DisplayPowerController.-wrap6(Unknown Source:0)
01-03 02:27:28.486 741 886 D SurfaceControl: atcom.android.server.display.DisplayPowerController$DisplayControllerHandler.handleMessage(DisplayPowerController.java:1566)
01-03 02:27:28.486 741 886 D SurfaceControl: atandroid.os.Handler.dispatchMessage(Handler.java:106)
01-03 02:27:28.486 741 886 D SurfaceControl: atandroid.os.Looper.loop(Looper.java:164)
01-03 02:27:28.486 741 886 D SurfaceControl: atandroid.os.HandlerThread.run(HandlerThread.java:65)
01-03 02:27:28.486 741 886 D SurfaceControl: at com.android.server.ServiceThread.run(ServiceThread.java:46)
到这里,起初我是比较怀疑这个流程是不是不对,然后想起之前N版本上面我们也有移植该功能,但是没有这个问题,所以拿N版本的机器也打了下log对比,发现也有这个调用流程,所以确定我怀疑的这个方向错了,问题不是出在这里。再次对比log,又发现一个可疑地方,在N版本上,只有真正触发了截屏的时候才会打印这行log:
SurfaceFlinger:captureScreenImplLocked reqWidth=720 reqHeight=1440 maxLayerZ=2147483647
但是我们的39O平台上面的项目在关机的时候也打出来了这行,结合上面的代码可以看到if( reqWidth != 0 && reqHeight !=0 && maxLayerZ >390000)这个条件已经形同虚设了。
山重水复疑无路,柳岸花明又一村:
经过上面的对比分析,总算是找到一点眉目了,基本可以确定是if()条件判断里面的参数问题。接着继续debug,给调用该方法的地方加log,看看上层传下来的参数都是啥。
01-03 02:27:28.485741 886 D SurfaceControl: screenshot2222222: width=0, height=0------wzl
01-0112:25:44.483 775 900 E SurfaceComposerClient:ScreenshotClient: capture 111111111: reqWidth=0, reqHeight=0
01-01 12:25:44.486 340 877 E SurfaceFlinger: 1111111111 SurfaceFlinger::captureScreen reqWidth=0reqHeight=0
01-01 12:25:44.486 340 877 E SurfaceFlinger: 4444444444 SurfaceFlinger::captureScreen reqWidth=720reqHeight=1440
从这里的log就可以很清晰的看出来了,上面传的参数是0,但是最终到了SurfaceFlinger::captureScreen()这里值就变了,好,接下来重点看SurfaceFlinger::captureScreen()方法。
然后在这个方法中发现了这个:
updateDimensionsLocked(displayDevice,rotationFlags, &reqWidth, &reqHeight);
再看其实现:
……
if (*requestedWidth == 0) {
*requestedWidth = displayWidth;
}
if (*requestedHeight == 0) {
*requestedHeight =displayHeight;
}
……
果然,这里有重新赋值,导致下面captureScreenImplLocked()方法传入的参数变了。我又对比了下N版本surfaceflinger部分的代码,改动还是比较大的,而且没有updateDimensionsLocked()方法,由于不清楚这部分的改动,也不确定是不是mtk在新版本上做的修改,也不敢随意删除。后面咨询了下mtk工程师,他们答复这部分的代码他们也是follow google的,应该是增加了容错处理,这样的话就不能直接删了,还是要保持原始代码,要解决我们现在这个问题只能是在updateDimensionsLocked()方法调用之前来进行reqWidth和reqHeight的判断。
小结:
这个问题确实也是埋的比较深的一个坑,因为这套圆角的方案在N版本上面还是比较成熟了,移植到O1上面也一直没有报过相关的问题,而且大部分项目都没有加关机动画,并且报出问题来了之后很难想到会和这个圆角的功能有关,确实也是辛苦了开关机模块同事的分析和排查。
定位到了问题之后就是要找出具体原因了,需要追寻那个可疑点往各个方向排查。其实,先提出一些假设也是挺有必要的,通过假设,对比,验证来确定是否成立,追根溯源,找到root cause,然后解决问题。当然,有时候可能找到了root cause也不一定能有好的解决方案,但是要解决问题肯定还是要先找到原因。