本文共 13843 字,大约阅读时间需要 46 分钟。
最近关于自动背光的问题报的比较多,多是背光不灵敏以及背光调节不正常的问题,并且自动背光也是直接和用户交互的一个模块,对于用户体验有着最直观的影响,所以在此分析一下关于自动背光模块的处理逻辑,以及小米对于自动背光所作的优化,关于后期优化方案,也希望大家能多多建议。
自动背光在android系统中属于display显示相关模块,具体是和Lights和Power交互较多,Power控制其亮屏,灭屏,暗屏以及自动背光设置开关等,Lights负责背光灯的背光亮度值的变化。
自动背光的主要控制功能是DisplayPowerController和AutomaticBrightnessController两个类结合起来工作的。DisplayPowerController属于Display模块,其控制设备屏幕亮灭,背光,与Power关系非常紧密,前面亮屏流程已经讲过PowerManagerService与Display的交互过程,这里重点分析屏幕亮度的控制这方面的控制逻辑。其复杂程度尤以自动亮度最甚。
首先分析DisplayPowerController,其初始化是在DisplayManagerService中的
private final class LocalService extends DisplayManagerInternal { @Override public void initPowerManagement(final DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager) { synchronized (mSyncRoot) { DisplayBlanker blanker = new DisplayBlanker() { @Override public void requestDisplayState(int state, int brightness) { // The order of operations is important for legacy reasons. if (state == Display.STATE_OFF) { requestGlobalDisplayStateInternal(state, brightness); } callbacks.onDisplayStateChange(state); if (state != Display.STATE_OFF) { requestGlobalDisplayStateInternal(state, brightness); } } }; mDisplayPowerController = new DisplayPowerController( mContext, callbacks, handler, sensorManager, blanker); } } ....
initPowerManagement() 这个方法其实还是比较重要的,在看过我之前的文章大概就会知道,在该方法会有两个重要作用:1.回调到PowerManagerService中设置屏幕显示状态与Power状态到底层。2.向LightService里面去设置屏幕背光;
现在看到该方法里面在初始化时候,创建了DisplayPowerController这个重要类的对象。那么我们来开始分析其构造函数:mAutomaticBrightnessController = new AutomaticBrightnessController(this, handler.getLooper(), sensorManager, screenAutoBrightnessSpline, lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientLightHorizon, autoBrightnessAdjustmentMaxGamma);
在初始化之后其实重点要说明两个参数:
screenAutoBrightnessSpline,可以看到该参数直接是通过方法createAutoBrightnessSpline()直接返回的一个Spline对象int[] lux = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels);int[] screenBrightness = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { try { final int n = brightness.length; float[] x = new float[n]; float[] y = new float[n]; y[0] = normalizeAbsoluteBrightness(brightness[0]); for (int i = 1; i < n; i++) { x[i] = lux[i - 1]; y[i] = normalizeAbsoluteBrightness(brightness[i]); } Spline spline = Spline.createSpline(x, y); if (DEBUG) { Slog.d(TAG, "Auto-brightness spline: " + spline); for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); } } return spline; } catch (IllegalArgumentException ex) { Slog.e(TAG, "Could not create auto-brightness spline.", ex); return null; } }
可以继续看lux 和screenBrightness的配置:
- 1
- 2
- 3
- 5
- 10
- 20
- 50
- 100
- 200
- 300
- 400
- 500
- 600
- 700
- 800
- 1000
- 1200
- 1600
- 2200
- 3000
- 4000
- 1
- 2
- 4
- 8
- 12
- 20
- 30
- 46
- 49
- 54
- 61
- 65
- 70
- 76
- 82
- 87
- 98
- 108
- 131
- 161
- 230
- 255
这函数的效果就是screenAutoBrightnessSpline就相当于是一个spline函数环境光照lux 值1~4000的配置的值,一一对应到屏幕亮度0~255的值上。这一步是关键在自动亮度计算过程中,会对环境光照采集取平均值后来对应设置屏幕亮度。原生代码上并未提供标准配置,给出的解释是各个硬件厂商的光感器件配置各不相同,留给厂商自行去overlay配置。
由于亮屏之后屏幕自动亮度才会生效,所以在亮屏的时候,流程会走到DisplayPowerController的中核心函数updatePowerState() 在该函数中会调用到AutomaticBrightnessController中的configure()
mAutomaticBrightnessController.configure(autoBrightnessEnabled, mPowerRequest.screenAutoBrightnessAdjustment, state != Display.STATE_ON, userInitiatedChange, mPowerRequest.useTwilight); public void configure(boolean enable, float adjustment, boolean dozing, boolean userInitiatedChange, boolean useTwilight) { //... mDozing = dozing; boolean changed = setLightSensorEnabled(enable && !dozing); changed |= setScreenAutoBrightnessAdjustment(adjustment); changed |= setUseTwilight(useTwilight); if (changed) { updateAutoBrightness(false /*sendUpdate*/); } if (enable && !dozing && userInitiatedChange) { prepareBrightnessAdjustmentSample(); } }
这里能看到当Lsensor 状态发生改变,adj改变或者使用日出日落时刻动态调节屏幕亮度其中任意一个条件发生改变,都会使其去更新自动亮度,但是由于传入的是false,则不会去更新到屏幕上,后面重点分析该方法updateAutoBrightness。
在setLightSensorEnabled 中将Lsensor置成enable,并注册监听Lsensor的listener。
在setScreenAutoBrightnessAdjustment 中将最新的adj作为计算屏幕亮度的参数。 在setUseTwilight中设置是否使用Twilight模式。当亮屏之后,Lsensor启动后,并且注册其Lsensor的监听器mLightSensorListener ,在onchange方法中
private final SensorEventListener mLightSensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { final long time = SystemClock.uptimeMillis(); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } } private void handleLightSensorEvent(long time, float lux) { mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); applyLightSensorMeasurement(time, lux); updateAmbientLux(time); }
lux数组用来存sensor采集上来连续时间段的环境光照值,time则是当前时间
private void applyLightSensorMeasurement(long time, float lux) { mRecentLightSamples++; // Store all of the light measurements for the intial horizon period. This is to help // diagnose dim wake ups and slow responses in b/27951906. if (time <= mLightSensorEnableTime + mAmbientLightHorizon) { mInitialHorizonAmbientLightRingBuffer.push(time, lux); } mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon);//将最近11次检测到的的环境光照值存入数组中。11次之前的去除掉 mAmbientLightRingBuffer.push(time, lux); // Remember this sample value. mLastObservedLux = lux; mLastObservedLuxTime = time; }
可以看到在亮屏后,开始10s采集到的环境光线值复制给mInitialHorizonAmbientLightRingBuffer变量,后面超过10秒之后的环境光照采集值都会放到mAmbientLightRingBuffer中,并且prune所作的工作就是,最近11次 的光线变化,每次时间都会将11次之前的光线值给裁剪掉。push操作便是将最新的亮度值不断的送入mAmbientLightRingBuffer的数组中。
private void updateAmbientLux(long time) { if (!mAmbientLuxValid) { final long timeWhenSensorWarmedUp = mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; if (time < timeWhenSensorWarmedUp) { mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, timeWhenSensorWarmedUp); return; } setAmbientLux(calculateAmbientLux(time)); mAmbientLuxValid = true; updateAutoBrightness(true); }
第一断代码的大概的作用是,在从手动亮度调整成自动亮度时候。亮度需要立即适应当前环境光照来调整屏幕亮度。setAmbientLux(calculateAmbientLux(time));两个方法后面重点分析一下。
long nextBrightenTransition = nextAmbientLightBrighteningTransition(time); long nextDarkenTransition = nextAmbientLightDarkeningTransition(time); float ambientLux = calculateAmbientLux(time); if (ambientLux >= mBrighteningLuxThreshold && nextBrightenTransition <= time || ambientLux <= mDarkeningLuxThreshold && nextDarkenTransition <= time) { setAmbientLux(ambientLux); updateAutoBrightness(true); nextBrightenTransition = nextAmbientLightBrighteningTransition(time); nextDarkenTransition = nextAmbientLightDarkeningTransition(time); } long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition); nextTransitionTime = nextTransitionTime > time ? nextTransitionTime : time + mLightSensorRate; mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime); }
这里有四个重要的参数nextBrightenTransition,nextDarkenTransition,mBrighteningLuxThreshold,mDarkeningLuxThreshold,这个四个参数的计算以及函数实现,只是一个简单的数学公式,用代码实现的。这里先解释这四个参数的作用。
nextBrightenTransition :表示下一次变亮的过渡时间。 nextDarkenTransition: 表示下一次变暗的过渡时间。 mBrighteningLuxThreshold :使屏幕变亮所需要的环境光照阈值,这个参数的计算是当前环境光照*1.1 ,当环境光照大于该数值的时候,屏幕才可能变亮。 mDarkeningLuxThreshold :使屏幕变暗所需要的环境光照阈值,这个参数的计算是当前环境光照*1. 2,当环境光照小于该数值的时候,屏幕才可能变暗。 也就是当环境光照在当前环境光照的0.8~1.1倍数之间变化,屏幕维持在一个固定的亮度值上,这样做的原因是避免环境光照发生细微的变化,屏幕亮度就变化,这算是背光防抖的一种措施。private long nextAmbientLightBrighteningTransition(long time) { final int N = mAmbientLightRingBuffer.size(); long earliestValidTime = time; for (int i = N - 1; i >= 0; i--) { if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) { break; } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } return earliestValidTime + mBrighteningLightDebounceConfig; }
以上函数的作用是从mAmbientLightRingBuffer 循环环境光照lux数组里取出第一个大于变亮阈值的一项,取得其时间,加上变亮防抖时间mBrighteningLightDebounceConfig(这个在config.xml中可以配置的)。这里计算的是变亮的环境光照过渡时间。这里做的意义是,需要变亮的环境光照持续时间大于一个过渡时间nextBrightenTransition,才可以将屏幕亮度变亮。这样可以避免环境光照只是瞬间升高后马上回复(比如闪光灯),屏幕亮度马上跟着变亮。这也做算是一种防抖措施了,目的也是尽量在环境光照不发生大的变化时候也维持在同一水平。
这里我们就可以理解这个方法的意义了,当当前环境光照ambientLux大于 变亮的环境光照阈值mBrighteningLuxThreshold,并且该环境光照持续的时间已经超过了变亮的过度时间,这时候就可以去调用该函数updateAutoBrightness(true)更新屏幕亮度了,并且在setAmbientLux中重新计算变亮或者变暗时环境光照的阈值,同时重新计算下一次变亮或者变暗的过渡时间。
if (ambientLux >= mBrighteningLuxThreshold && nextBrightenTransition <= time || ambientLux <= mDarkeningLuxThreshold && nextDarkenTransition <= time) { setAmbientLux(ambientLux); updateAutoBrightness(true); nextBrightenTransition = nextAmbientLightBrighteningTransition(time); nextDarkenTransition = nextAmbientLightDarkeningTransition(time); }
在updateAutoBrightness中更新屏幕亮度:这里分析该函数的计算流程
float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux); //从mScreenAutoBrightnessSpline中获取到当前环境光照mAmbientLux对应的屏幕亮度值与255的比值value float gamma = 1.0f; if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT && mScreenAutoBrightnessAdjustment != 0.0f) { //当用户没有调整状态栏或者设置里的亮度条,那么传过来的参数mScreenAutoBrightnessAdjustment便等于0,但是大部用户都是调整过的。 final float adjGamma = MathUtils.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment))); gamma *= adjGamma; if (DEBUG) { Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma); } }
这里主要是做了两件事,从mScreenAutoBrightnessSpline中取出环境光照对应的屏幕亮度,其次计算gamma值,gamma值大概为3.0^adj(adj 为PowerManager中传递过来的adj参数,范围为-1.0~1.0);
if (gamma != 1.0f) { final float in = value; value = MathUtils.pow(value, gamma);//计算adj 调整之后的屏幕亮度与255的比值value, } int newScreenAutoBrightness = clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));//当前环境光照lux值对应的实际亮度值 if (mScreenAutoBrightness != newScreenAutoBrightness) { mScreenAutoBrightness = newScreenAutoBrightness; mLastScreenAutoBrightnessGamma = gamma; if (sendUpdate) { mCallbacks.updateBrightness();//回调到DisplayPowerController中去更新亮度值。 } }
至此屏幕亮度自动更新的算法基本已经分析完成,虽然逻辑稍微有些复杂,但是一点一点抽丝剥茧的来看,可以看到里面的设计逻辑还是有很多精妙的地方。其大概流程图如下
至于屏幕亮度进度条调整屏幕亮度这个逻辑,其实并不困难,
自动亮度下:滑动进度条就是不断的修改adj 值,而adj参数在上面已经分析其作用,大概就是做一个微调的作用,计算后的屏幕亮度反应设置到底层去。手动亮度下:就是不断传递screen_brightness值到DisplayPowerController中直接设置到底层。