[052]Q平臺上setBrightness的巨坑

前言

最近解決了一個掉幀的問題,從應(yīng)用層來看是buffer申請不到,最后發(fā)現(xiàn)是Q平臺升級+高通的代碼+我們自己驅(qū)動優(yōu)化算法導(dǎo)致的,三者缺一不可,由于保密協(xié)議,我只能簡單的原生代碼和簡單的圖來描述這個問題,避免大家踩坑。

一、Q平臺上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è)置屏幕亮度

就是一旦以下條件滿足就會走Q版本上新的設(shè)置亮度流程

 if (brightnessMode == BRIGHTNESS_MODE_USER && !shouldBeInLowPersistenceMode()
                && mSurfaceControlMaximumBrightness == 255) 

Android Q上有兩種方式設(shè)置屏幕亮度,如下圖表示,導(dǎo)致掉幀的就是方式1

方式1:system_server->SF->HWC HAL->設(shè)備節(jié)點->背光驅(qū)動(Android Q)
方式2:system_server->Light HAL->設(shè)備節(jié)點->背光驅(qū)動(Android Q, P)

二、為什么會導(dǎo)致掉幀?

我在前言中已經(jīng)說了是由Q平臺升級+高通的代碼+我們自己驅(qū)動優(yōu)化算法導(dǎo)致的,三者缺一不可。

2.1 Q平臺升級

出問題的項目就是走了方式1的流程,所以滿足條件。

2.2 高通的代碼

高通的HWC HAL層代碼中對setDisplayBrightness接口實現(xiàn)中加了一個display的鎖。也就意味這HWC其他的接口會和setDisplayBrightness產(chǎn)生鎖的競爭關(guān)系。

2.3 我們自己驅(qū)動優(yōu)化算法

我們在驅(qū)動中對背光設(shè)置有一些優(yōu)化,在特定的情況下,會導(dǎo)致寫設(shè)備節(jié)點的時間耗時200ms左右。

2.4 還原現(xiàn)場

首先lightsensor觸發(fā)了自動背光調(diào)節(jié),然后走SF-HWC去設(shè)置了亮度,持有了display的鎖。

由于驅(qū)動的優(yōu)化算法,導(dǎo)致這把鎖持有了200ms。

這個200ms時間段里,SF繪制每一個幀的代碼中也有和HWC的調(diào)用,因為拿不到鎖導(dǎo)致也
block了,從而鎖住了一個buffer。

應(yīng)用申請一個buffer,完成繪制,交給sf,sf來不及使用。
應(yīng)用又申請一個buffer,完成繪制,交給sf,sf來不及使用。
最后應(yīng)用的三個buffer,一個處于lock,兩個處于未用的狀態(tài)(手機中bufferqueue設(shè)置的是3個)
應(yīng)用再次申請buffer的時候,沒有可用的buffer了,導(dǎo)致了主線程的block,最后導(dǎo)致了掉幀的問題的出現(xiàn)。

三、另外一個詭異的事情。

雖然問題基本已經(jīng)解決,但是我無法解釋,還有一個詭異的事情。

同一個手機,在驅(qū)動代碼完全一樣,只不過刷了不同高通基線的代碼
竟然一個走方式1,一個走方式2。

日志發(fā)現(xiàn)的原因:

方式1的時候mSurfaceControlMaximumBrightness為255
方式2的時候mSurfaceControlMaximumBrightness為0

3.1 maximumBrightness為什么是0

基本可以猜到下面的代碼在初始化的時候有異常,導(dǎo)致了maximumBrightness為0.
為什么會導(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ù)得正

這個就是典型的負(fù)負(fù)得正,基線沒有升級導(dǎo)致了getDisplayBrightnessSupport為false,導(dǎo)致了mSurfaceControlMaximumBrightness為0,最后走方式2,掉幀問題也就消失。

總結(jié)

基本上整個問題的分析過程,我是通過trace分析出來的,然后結(jié)合特定關(guān)鍵點的log,把這個問題給解決了。這是一個很有意思的問題,不方便放trace的截圖,無法和大家分享如何看trace。

但是除了學(xué)會看trace的技巧,你一定要清楚的知道每一個進程,每一個線程之前的通信的關(guān)系,然后在自己的腦海中去還原現(xiàn)場,抽絲剝繭,找到問題點。

整個問題牽涉到APP-Framework-Kernel,如果你想要在性能優(yōu)化上更近一步,我個人認(rèn)為打通APP-Framework-Kernel是非常重要的一步。

尾巴

為什么Android Q上要大費周章通過SF/HWC去設(shè)置屏幕亮度,我推測是谷歌希望將屏幕亮度調(diào)節(jié)和屏幕UI顯示之間建立起一個關(guān)系,一起配合調(diào)整,讓用戶對屏幕的觀感效果更好。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容