前言
最近解決了一個(gè)掉幀的問題,從應(yīng)用層來看是buffer申請不到,最后發(fā)現(xiàn)是Q平臺(tái)升級+高通的代碼+我們自己驅(qū)動(dòng)優(yōu)化算法導(dǎo)致的计呈,三者缺一不可,由于保密協(xié)議征唬,我只能簡單的原生代碼和簡單的圖來描述這個(gè)問題捌显,避免大家踩坑。
一总寒、Q平臺(tái)上setBrightness的升級
1.1 Android Q
@Override
public void setBrightness(int brightness, int brightnessMode) {
synchronized (this) {
// LOW_PERSISTENCE cannot be manually set
if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId +
": brightness=0x" + Integer.toHexString(brightness));
return;
}
// Ideally, we'd like to set the brightness mode through the SF/HWC as well, but
// right now we just fall back to the old path through Lights brightessMode is
// anything but USER or the device shouldBeInLowPersistenceMode().
if (brightnessMode == BRIGHTNESS_MODE_USER && !shouldBeInLowPersistenceMode()
&& mSurfaceControlMaximumBrightness == 255) {
// TODO: the last check should be mSurfaceControlMaximumBrightness != 0; the
// reason we enforce 255 right now is to stay consistent with the old path. In
// the future, the framework should be refactored so that brightness is a float
// between 0.0f and 1.0f, and the actual number of supported brightness levels
// is determined in the device-specific implementation.
if (DEBUG) {
Slog.d(TAG, "Using new setBrightness path!");
}
SurfaceControl.setDisplayBrightness(mDisplayToken,
(float) brightness / mSurfaceControlMaximumBrightness);
} else {
int color = brightness & 0x000000ff;
color = 0xff000000 | (color << 16) | (color << 8) | color;
setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
}
}
}
1.2 Android P
@Override
public void setBrightness(int brightness, int brightnessMode) {
synchronized (this) {
// LOW_PERSISTENCE cannot be manually set
if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId +
": brightness=0x" + Integer.toHexString(brightness));
return;
}
int color = brightness & 0x000000ff;
color = 0xff000000 | (color << 16) | (color << 8) | color;
setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
}
}
1.3 兩者的區(qū)別
we'd like to set the brightness mode through the SF/HWC as well
我們也希望通過SF/HWC設(shè)置屏幕亮度
就是一旦以下條件滿足就會(huì)走Q版本上新的設(shè)置亮度流程
if (brightnessMode == BRIGHTNESS_MODE_USER && !shouldBeInLowPersistenceMode()
&& mSurfaceControlMaximumBrightness == 255)
Android Q上有兩種方式設(shè)置屏幕亮度扶歪,如下圖表示,導(dǎo)致掉幀的就是方式1
方式1:system_server->SF->HWC HAL->設(shè)備節(jié)點(diǎn)->背光驅(qū)動(dòng)(Android Q)
方式2:system_server->Light HAL->設(shè)備節(jié)點(diǎn)->背光驅(qū)動(dòng)(Android Q摄闸, P)
二善镰、為什么會(huì)導(dǎo)致掉幀?
我在前言中已經(jīng)說了是由Q平臺(tái)升級+高通的代碼+我們自己驅(qū)動(dòng)優(yōu)化算法導(dǎo)致的年枕,三者缺一不可炫欺。
2.1 Q平臺(tái)升級
出問題的項(xiàng)目就是走了方式1的流程,所以滿足條件画切。
2.2 高通的代碼
高通的HWC HAL層代碼中對setDisplayBrightness接口實(shí)現(xiàn)中加了一個(gè)display的鎖竣稽。也就意味這HWC其他的接口會(huì)和setDisplayBrightness產(chǎn)生鎖的競爭關(guān)系。
2.3 我們自己驅(qū)動(dòng)優(yōu)化算法
我們在驅(qū)動(dòng)中對背光設(shè)置有一些優(yōu)化霍弹,在特定的情況下毫别,會(huì)導(dǎo)致寫設(shè)備節(jié)點(diǎn)的時(shí)間耗時(shí)200ms左右。
2.4 還原現(xiàn)場
首先lightsensor觸發(fā)了自動(dòng)背光調(diào)節(jié)典格,然后走SF-HWC去設(shè)置了亮度岛宦,持有了display的鎖。
由于驅(qū)動(dòng)的優(yōu)化算法耍缴,導(dǎo)致這把鎖持有了200ms砾肺。
這個(gè)200ms時(shí)間段里,SF繪制每一個(gè)幀的代碼中也有和HWC的調(diào)用防嗡,因?yàn)槟貌坏芥i導(dǎo)致也
block了变汪,從而鎖住了一個(gè)buffer。
應(yīng)用申請一個(gè)buffer蚁趁,完成繪制裙盾,交給sf,sf來不及使用。
應(yīng)用又申請一個(gè)buffer番官,完成繪制庐完,交給sf,sf來不及使用徘熔。
最后應(yīng)用的三個(gè)buffer门躯,一個(gè)處于lock,兩個(gè)處于未用的狀態(tài)(手機(jī)中bufferqueue設(shè)置的是3個(gè))
應(yīng)用再次申請buffer的時(shí)候酷师,沒有可用的buffer了讶凉,導(dǎo)致了主線程的block,最后導(dǎo)致了掉幀的問題的出現(xiàn)山孔。
三缀遍、另外一個(gè)詭異的事情。
雖然問題基本已經(jīng)解決饱须,但是我無法解釋,還有一個(gè)詭異的事情台谊。
同一個(gè)手機(jī)蓉媳,在驅(qū)動(dòng)代碼完全一樣,只不過刷了不同高通基線的代碼
竟然一個(gè)走方式1锅铅,一個(gè)走方式2酪呻。
日志發(fā)現(xiàn)的原因:
方式1的時(shí)候mSurfaceControlMaximumBrightness為255
方式2的時(shí)候mSurfaceControlMaximumBrightness為0
3.1 maximumBrightness為什么是0
基本可以猜到下面的代碼在初始化的時(shí)候有異常,導(dǎo)致了maximumBrightness為0.
為什么會(huì)導(dǎo)致maximumBrightness為0盐须,簡單的說一下就是高通的基線升級導(dǎo)致了getDisplayBrightnessSupport返回了true玩荠,我就不展開講了。
private LightImpl(Context context, int id) {
mId = id;
mDisplayToken = SurfaceControl.getInternalDisplayToken();
final boolean brightnessSupport = SurfaceControl.getDisplayBrightnessSupport(
mDisplayToken);
if (DEBUG) {
Slog.d(TAG, "Display brightness support: " + brightnessSupport);
}
int maximumBrightness = 0;
if (brightnessSupport) {
PowerManager pm = context.getSystemService(PowerManager.class);
if (pm != null) {
maximumBrightness = pm.getMaximumScreenBrightnessSetting();
}
}
mSurfaceControlMaximumBrightness = maximumBrightness;
}
3.2 負(fù)負(fù)得正
這個(gè)就是典型的負(fù)負(fù)得正贼邓,基線沒有升級導(dǎo)致了getDisplayBrightnessSupport為false阶冈,導(dǎo)致了mSurfaceControlMaximumBrightness為0,最后走方式2塑径,掉幀問題也就消失女坑。
總結(jié)
基本上整個(gè)問題的分析過程,我是通過trace分析出來的统舀,然后結(jié)合特定關(guān)鍵點(diǎn)的log匆骗,把這個(gè)問題給解決了。這是一個(gè)很有意思的問題誉简,不方便放trace的截圖碉就,無法和大家分享如何看trace。
但是除了學(xué)會(huì)看trace的技巧闷串,你一定要清楚的知道每一個(gè)進(jìn)程瓮钥,每一個(gè)線程之前的通信的關(guān)系,然后在自己的腦海中去還原現(xiàn)場,抽絲剝繭骏庸,找到問題點(diǎn)毛甲。
整個(gè)問題牽涉到APP-Framework-Kernel,如果你想要在性能優(yōu)化上更近一步具被,我個(gè)人認(rèn)為打通APP-Framework-Kernel是非常重要的一步玻募。
尾巴
為什么Android Q上要大費(fèi)周章通過SF/HWC去設(shè)置屏幕亮度,我推測是谷歌希望將屏幕亮度調(diào)節(jié)和屏幕UI顯示之間建立起一個(gè)關(guān)系一姿,一起配合調(diào)整七咧,讓用戶對屏幕的觀感效果更好。