Android Q(10.0)及以上版本 SparseArray ArrayIndexOutOfBoundsException问题处理
android10.0发布之后,我们在最快的时间进行了适配,但是在项目发布上线之后,线上用户反馈了一个必现的崩溃bug。
经过分析发现,该bug只在android10.0及以上的手机上才会出现,在android10以下的版本从未出现。
根据崩溃日志分析发现,崩溃原因是数组下标越界,跟踪代码发现,是因为项目中使用了SparseArray,在使用SparseArray的keyAt及setValueAt方法的时候发生了数组下标越界的崩溃。
android Q 及以上版本SparseArray#keyAt方法实现
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*
* <p>The keys corresponding to indices in ascending order are guaranteed to
* be in ascending order, e.g., <code>keyAt(0)</code> will return the
* smallest key and <code>keyAt(size()-1)</code> will return the largest
* key.</p>
*
* <p>For indices outside of the range <code>0...size()-1</code>,
* the behavior is undefined for apps targeting {@link android.os.Build.VERSION_CODES#P} and
* earlier, and an {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
* {@link android.os.Build.VERSION_CODES#Q} and later.</p>
*/
public int keyAt(int index) {
if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
// The array might be slightly bigger than mSize, in which case, indexing won't fail.
// Check if exception should be thrown outside of the critical path.
throw new ArrayIndexOutOfBoundsException(index);
}
if (mGarbage) {
gc();
}
return mKeys[index];
}
android Q 及以下SparseArray#keyAt方法实现
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public int keyAt(int index) {
if (mGarbage) {
gc();
}
return mKeys[index];
}
对比发现,是因为android Q以上对index索引做了校验,索引不合法的时候并且配置了数组下标越界的异常上抛的时候会抛出一个ArrayIndexOutOfBoundsException异常。意思就是android Q的稀松数组系列的使用校验更严格了。查找 {@link android.os.Build.VERSION_CODES#Q} and later 发现,不止一处可能会抛出异常。
下面的这些类的下述方法都有可能因为index不合法而抛出 ArrayIndexOutOfBoundsException异常
ArrayMap
- keyAt(int index)
- valueAt(int index)
- setValueAt(int index, V value)
- removeAt(int index)
ArraySet
- valueAt(int index)
- removeAt(int index)
LongSparseArray
- keyAt(int index)
- valueAt(int index)
- setValueAt(int index, V value)
- removeAt(int index)
LongSparseLongArray
- keyAt(int index)
- valueAt(int index)
SparseArray
- keyAt(int index)
- valueAt(int index)
- setValueAt(int index, V value)
- removeAt(int index)
SparseBooleanArray
- keyAt(int index)
- valueAt(int index)
- setValueAt(int index, V value)
SparseIntArray
- keyAt(int index)
- valueAt(int index)
- setValueAt(int index, V value)
SparseLongArray
- keyAt(int index)
- valueAt(int index)
接下来考虑如何解决:
如果项目体量较小,用到的上述可能抛出异常的方法比较少,可以考虑直接对index进行合法性校验,成功之后再调用方法。以避免异常崩溃的发生。
如果项目体量大,使用的上述方法很多,一个一个去修改就太蛋疼了。现在考虑几种解决方案
- 方案一
既然10以上的上述方法实现中是先判断索引并且UtilConfig.sThrowExceptionForUpperArrayOutOfBounds成立的时候才会抛出异常。那是否可以考虑把后者的值置为false,不让抛异常呢,跟踪UtilConfig类发现实现很简单
/**
* Class to configure several of the util classes.
*
* @hide
*/
public class UtilConfig {
static boolean sThrowExceptionForUpperArrayOutOfBounds = true;
public static void setThrowExceptionForUpperArrayOutOfBounds(boolean check) {
sThrowExceptionForUpperArrayOutOfBounds = check;
}
}
注意:这个类是hide的,意味着api 28之后我们无法通过引用或反射的方式去修改该类,该方案放弃
- 方案二
使用androidx包下的兼容类替代,但是兼容类只有SparseArray【SparseArrayCompat】有兼容类,ArrayMap、ArraySet、LongSparseArray、LongSparseLongArray、SparseBooleanArray、SparseIntArray、SparseLongArray都没有兼容类,所以只用SparseArray的时候可以考虑使用SparseArrayCompat去替换,但是该类是androidx下面的类,指不定哪天androidx库升级又改了方法实现呢,所以该方案不靠谱,放弃 - 方案三
考虑使用工具类替代上述方法的直接引用,工具类代码如下
/**
* @description SparseArray工具类,用于兼容android Q之后的 ArrayIndexOutOfBoundsException
* @see {@link android.util.SparseArray#removeAt(int)}
* @see {@link android.util.SparseArray#keyAt(int)}
* @see {@link android.util.SparseArray#valueAt(int)}
* @see {@link android.util.SparseArray#setValueAt(int, Object)}
*/
public class SparseArrayUtils {
/**
* Removes the mapping at the specified index.
*/
public static void removeAt(SparseArray sparseArray, int index) {
if (sparseArray == null) {
return;
}
if (index < 0 || index >= sparseArray.size()) {
return;
}
sparseArray.removeAt(index);
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public static int keyAt(SparseArray sparseArray, int index) {
return keyAt(sparseArray, index, 0);
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public static int keyAt(SparseArray sparseArray, int index, int defaultValue) {
if (sparseArray == null) {
return defaultValue;
}
if (index < 0 || index >= sparseArray.size()) {
return defaultValue;
}
return sparseArray.keyAt(index);
}
/**
* Given an index in the range <code>0...size()-1</code>, returns
* the value from the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public static <E> E valueAt(SparseArray<E> sparseArray, int index) {
if (sparseArray == null) {
return null;
}
if (index < 0 || index >= sparseArray.size()) {
return null;
}
return sparseArray.valueAt(index);
}
/**
* Given an index in the range <code>0...size()-1</code>, sets a new
* value for the <code>index</code>th key-value mapping that this
* SparseArray stores.
*/
public static <E> void setValueAt(SparseArray<E> sparseArray, int index, E value) {
if (sparseArray == null) {
return;
}
if (index < 0 || index >= sparseArray.size()) {
return;
}
sparseArray.setValueAt(index, value);
}
}
在使用SparseArray的removeAt、keyAt、valueAt、setValueAt方法时,使用上述工具类替代,对应的ArrayMap、ArrayMap、ArraySet、LongSparseArray、LongSparseLongArray、SparseBooleanArray、SparseIntArray、SparseLongArray等其他类型的兼容同理,这里就不再赘述。
当然,如果使用kotlin语言开发的话,可以写一个kotlin版本的兼容
//=========================== ArrayMap扩展兼容 ============================
/**
* 兼容方式使用 ArrayMap.keyAt 方法
* @see ArrayMap.keyAt
*/
fun <K, V> ArrayMap<K, V>.keyAtCompat(index: Int): K? =
if (index in 0 until size) {
keyAt(index)
} else {
null
}
/**
* 兼容方式使用 ArrayMap.valueAt 方法
* @see ArrayMap.valueAt
*/
fun <K, V> ArrayMap<K, V>.valueAtCompat(index: Int): V? =
if (index in 0 until size) {
valueAt(index)
} else {
null
}
/**
* 兼容方式使用 ArrayMap.setValueAt 方法
* @see ArrayMap.setValueAt
*/
fun <K, V> ArrayMap<K, V>.setValueAtCompat(index: Int, value: V): V? =
if (index in 0 until size) {
setValueAt(index, value)
} else {
null
}
/**
* 兼容方式使用 ArrayMap.removeAt 方法
* @see ArrayMap.removeAt
*/
fun <K, V> ArrayMap<K, V>.removeAtCompat(index: Int): V? =
if (index in 0 until size) {
removeAt(index)
} else {
null
}
//=========================== ArraySet扩展兼容 ============================
/**
* 兼容方式使用 ArraySet.valueAt 方法
* @see ArraySet.valueAt
*/
@RequiresApi(Build.VERSION_CODES.M)
fun <E> ArraySet<E>.valueAtCompat(index: Int): E? =
if (index in 0 until size) {
valueAt(index)
} else {
null
}
/**
* 兼容方式使用 ArraySet.removeAt 方法
* @see ArraySet.removeAt
*/
@RequiresApi(Build.VERSION_CODES.M)
fun <E> ArraySet<E>.removeAtCompat(index: Int): E? =
if (index in 0 until size) {
removeAt(index)
} else {
null
}
//=========================== LongSparseArray扩展兼容 ============================
/**
* 兼容方式使用 LongSparseArray.keyAt 方法
* @see LongSparseArray.keyAt
*/
fun <E> LongSparseArray<E>.keyAtCompat(index: Int): Long =
if (index in 0 until size()) {
keyAt(index)
} else {
0
}
/**
* 兼容方式使用 LongSparseArray.valueAt 方法
* @see LongSparseArray.valueAt
*/
fun <E> LongSparseArray<E>.valueAtCompat(index: Int): E? =
if (index in 0 until size()) {
valueAt(index)
} else {
null
}
/**
* 兼容方式使用 LongSparseArray.setValueAt 方法
* @see LongSparseArray.setValueAt
*/
fun <E> LongSparseArray<E>.setValueAtCompat(index: Int, value: E) =
if (index in 0 until size()) {
setValueAt(index, value)
} else {
}
/**
* 兼容方式使用 LongSparseArray.removeAt 方法
* @see LongSparseArray.removeAt
*/
fun <E> LongSparseArray<E>.removeAtCompat(index: Int) =
if (index in 0 until size()) {
removeAt(index)
} else {
}
//=========================== LongSparseLongArray api29以上已改为hide,所以此处不再考虑扩展兼容 ============================
///**
// * 兼容方式使用 LongSparseLongArray.keyAt 方法
// * @see LongSparseLongArray.keyAt
// */
//fun <E> LongSparseLongArray<E>.keyAtCompat(index: Int): Long =
// if (index in 0 until size()) {
// keyAt(index)
// } else {
// 0
// }
//
///**
// * 兼容方式使用 LongSparseLongArray.valueAt 方法
// * @see LongSparseLongArray.valueAt
// */
//fun <E> LongSparseLongArray<E>.valueAtCompat(index: Int): E? =
// if (index in 0 until size()) {
// valueAt(index)
// } else {
// null
// }
//=========================== SparseArray扩展兼容 ============================
/**
* 兼容方式使用 SparseArray.keyAt 方法
* @see SparseArray.keyAt
*/
fun <E> SparseArray<E>.keyAtCompat(index: Int): Int =
if (index in 0 until size()) {
keyAt(index)
} else {
0
}
/**
* 兼容方式使用 SparseArray.valueAt 方法
* @see SparseArray.valueAt
*/
fun <E> SparseArray<E>.valueAtCompat(index: Int): E? =
if (index in 0 until size()) {
valueAt(index)
} else {
null
}
/**
* 兼容方式使用 SparseArray.setValueAt 方法
* @see SparseArray.setValueAt
*/
fun <E> SparseArray<E>.setValueAtCompat(index: Int, value: E) =
if (index in 0 until size()) {
setValueAt(index, value)
} else {
}
/**
* 兼容方式使用 SparseArray.removeAt 方法
* @see SparseArray.removeAt
*/
fun <E> SparseArray<E>.removeAtCompat(index: Int) =
if (index in 0 until size()) {
removeAt(index)
} else {
}
//=========================== SparseBooleanArray扩展兼容 ============================
/**
* 兼容方式使用 SparseBooleanArray.keyAt 方法
* @see SparseBooleanArray.keyAt
*/
fun SparseBooleanArray.keyAtCompat(index: Int): Int =
if (index in 0 until size()) {
keyAt(index)
} else {
0
}
/**
* 兼容方式使用 SparseBooleanArray.valueAt 方法
* @see SparseBooleanArray.valueAt
*/
fun SparseBooleanArray.valueAtCompat(index: Int): Boolean =
if (index in 0 until size()) {
valueAt(index)
} else {
false
}
/**
* 兼容方式使用 SparseBooleanArray.setValueAt 方法
* @see SparseBooleanArray.setValueAt
*/
fun SparseBooleanArray.setValueAtCompat(index: Int, value: Boolean) =
if (index in 0 until size()) {
setValueAt(index, value)
} else {
}
//=========================== SparseIntArray扩展兼容 ============================
/**
* 兼容方式使用 SparseIntArray.keyAt 方法
* @see SparseIntArray.keyAt
*/
fun SparseIntArray.keyAtCompat(index: Int): Int =
if (index in 0 until size()) {
keyAt(index)
} else {
0
}
/**
* 兼容方式使用 SparseIntArray.valueAt 方法
* @see SparseIntArray.valueAt
*/
fun SparseIntArray.valueAtCompat(index: Int): Int =
if (index in 0 until size()) {
valueAt(index)
} else {
0
}
/**
* 兼容方式使用 SparseIntArray.setValueAt 方法
* @see SparseIntArray.setValueAt
*/
fun SparseIntArray.setValueAtCompat(index: Int, value: Int) =
if (index in 0 until size()) {
setValueAt(index, value)
} else {
}
//=========================== SparseLongArray扩展兼容 ============================
/**
* 兼容方式使用 SparseLongArray.keyAt 方法
* @see SparseLongArray.keyAt
*/
fun SparseLongArray.keyAtCompat(index: Int): Int =
if (index in 0 until size()) {
keyAt(index)
} else {
0
}
/**
* 兼容方式使用 SparseLongArray.valueAt 方法
* @see SparseLongArray.valueAt
*/
fun SparseLongArray.valueAtCompat(index: Int): Long =
if (index in 0 until size()) {
valueAt(index)
} else {
0
}
这边博客只是解决了android Q适配中的跟数组下标越界相关的问题,如果您有更好的方案可以留言在评论区