在上一篇文章中,对自动背光的流程做了总结,在本篇中,将对自动背光涉及到的一些算法进行分析总结。
1.采集光强缓冲区
AmbientLightRingBuffer类是一个用于存储采集到的光照强度和对应时间点的数据结构。在自动背光控制器中,实例化了两个AmbientLightRingBuffer对象:
//包含所有光照样例的AmbientLightRingBuffer对象
mAmbientLightRingBuffer =
new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
//只包含在初始时间范围周期的光照样例的AmbientLightRingBuffer对象
mInitialHorizonAmbientLightRingBuffer =
new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
mAmbientLightRingBuffer是包含所有光照样例的AmbientLightRingBuffer对象,mInitialHorizonAmbientLightRingBuffer只包含在初始采光时间范围周期的光照样例的AmbientLightRingBuffer对象,这个初始采光时间范围周期是指当LSensor启用开始计时,到一个采光时间周期,采光时间周期mAmbientLightHorizon则在配置文件中读取:
<!--一个采光周期为10s-->
<integer name="config_autoBrightnessAmbientLightHorizon">10000</integer>
下面我们从它的构造方法开始,来分析缓冲区的实现原理和其中包括的算法。
1.1.构造方法
AmbientLightRingBuffer的构造方法如下:
private static final class AmbientLightRingBuffer {
private static final float BUFFER_SLACK = 1.5f;
private float[] mRingLux;
private long[] mRingTime;
private int mCapacity;
//缓冲区中第一个数据位置
private int mStart;
//缓冲区中下一个槽口位置
private int mEnd;
//缓冲区中光照样例的数量
private int mCount;
public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
//缓冲区容量
mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
//存储Lux值的数组
mRingLux = new float[mCapacity];
//存储时间的数组
mRingTime = new long[mCapacity];
}
在构造方法中,初始化了两个数组分别用来存储时间和Lux值,而这两个数组的长度,是根据采集光照样例的时间范围周期 * BUFFER_SLACK/LSensor事件率得到的。其实也可以这样理解:总时间 / 每次上报数据的时间 = 时间周期内上报事件的个数 = 缓冲区大小。
1.2.向缓冲区中push光照样例
将上报的LSensor数据记录到缓冲区时,使用push()
方法,该方法如下:
public void push(long time, float lux) {
//标记要存储的位置
int next = mEnd;
if (mCount == mCapacity) {
//如果缓冲区已满,则将进行扩容
int newSize = mCapacity * 2;
//初始化两个新的数组,大小为原来数组大小的2倍
float[] newRingLux = new float[newSize];
long[] newRingTime = new long[newSize];
//数组长度减去第一个元素位置=原数组的长度
int length = mCapacity - mStart;
//将旧数组中内容拷贝到新数组中
//将mRingLux数组从mStart位起,拷贝到newRingLux中,放在0位置起,拷贝length个数据
System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
//如果原数组中第一个数据的索引不为0
if (mStart != 0) {
//将mRingLux数组从0位起,拷贝到newRingLux中,放在length位置起,拷贝mStart个数据
//即如果mStart不为0,将mStart前的数据放在了最后
System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
}
mRingLux = newRingLux;
mRingTime = newRingTime;
next = mCapacity;
mCapacity = newSize;
mStart = 0;
}
//记录时间和Lux
mRingTime[next] = time;
mRingLux[next] = lux;
mEnd = next + 1;//下一个槽口
//如果到达数组尾端,则将mEnd置为0,在下一次进入后,由于mCount == mCapacity,因此又会将mEnd置为next+1
if (mEnd == mCapacity) {
mEnd = 0;
}
//记录数+1
mCount++;
}
在这个方法中,会将LSensor上报的值和时间点记录到缓冲区中,此处有两个地方的计算方式如下:
- 1.如果当前缓冲区已满,则对其进行扩容,新的容量是原来容量的2倍,实际上是重新初始化了两个数组,并将旧数组中的元素从保存的第一个元素起,拷贝到了新数组中,从0索引开始存放数据。
- 2.如果mStart不为0,说明缓冲区数组中存储元素的起始位置不为0(被
prune()
方法操作过),所以0-mStart之间的记录已被标记为删除,则在拷贝时,将旧数组中0-mStart之间的元素拷贝到新数组中,从length索引开始存放。
根据以上两个计算方式,从而形成了类似环的环形缓冲区。
1.3.从缓冲区中移除某个时间前的数据
每当LSensor收到上报数据后,会通过push()
方法记录到缓冲区,而对据此次上报时间一个采光时间周期前的数据,则进行删除,该方法如下:
public void prune(long horizon) {
//如果当前缓冲区中无数据,直接返回
if (mCount == 0) {
return;
}
while (mCount > 1) {
//表示第二个有效元素位置
int next = mStart + 1;
//如果第二个元素置位值大于等于容量值,说明整个缓冲区中只有一个元素,索引为mStart.
if (next >= mCapacity) {
next -= mCapacity;
}
//如果第二个有效元素的时间点大于传入的时间,则停止
if (mRingTime[next] > horizon) {
break;
}
//重置第一个有效元素索引
mStart = next;
//数量值-1
mCount -= 1;
}
//如果第一个有效元素时间小于传入的时间,则重置第一个有效元素的时间值
if (mRingTime[mStart] < horizon) {
mRingTime[mStart] = horizon;
}
}
下图简单表示了push()
和prune()
方法调用时缓冲区状态示例:
1.4.缓冲区获取索引值
缓冲区提供了getTime(int index)
和getLux(int index)
方法,根据参数index得到缓冲区中对应的数据,然而,由于缓冲区中数据的存储是根据缓冲区内部的变量mStart标记存储的起始位置,因此,需要将index进行转换,使用offsetOf(int index)
转换,从而得到正确的位置:
private int offsetOf(int index) {
if (index >= mCount || index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
//mStart表示第一个元素的位置
index += mStart;
//如果index >= mCapacity,直接index = index -mCapacity
if (index >= mCapacity) {
index -= mCapacity;
}
return index;
}
}
1.5.缓冲区中其他方法
其他方法比较简单,如下:
//获取Lux值
public float getLux(int index) {
return mRingLux[offsetOf(index)];