Android多线程创建LinearLayout触发ArrayIndexOutOfBoundsException问题求助
问题描述
您好,我正在调试一个与Android Widget相关的问题。
我的应用通过Glide库提供的两个线程分别生成Widget的原图和缩略图文件。
生成Widget图片文件的逻辑为:创建LinearLayout等布局,将其绘制到Bitmap上(示例:layout.draw(Canvas(bitmap))),再将Bitmap保存为本地文件。
该逻辑绝大多数场景下运行正常,但偶尔会上报如下异常:
java.lang.ArrayIndexOutOfBoundsException: length=3; index=3 at android.util.ContainerHelpers.binarySearch(ContainerHelpers.java:28) at android.util.SparseArray.get(SparseArray.java:120) at android.util.SparseArray.get(SparseArray.java:111) at android.view.ViewConfiguration.get(ViewConfiguration.java:494) at android.view.View.<init>(View.java:5432) at android.view.View.<init>(View.java:5624) at android.view.ViewGroup.<init>(ViewGroup.java:687) at android.widget.LinearLayout.<init>(LinearLayout.java:254) at android.widget.LinearLayout.<init>(LinearLayout.java:250) at android.widget.LinearLayout.<init>(LinearLayout.java:246) at android.widget.LinearLayout.<init>(LinearLayout.java:242)
该异常在我执行如下LinearLayout初始化代码时抛出:LinearLayout(context).apply { orientation = LinearLayout.VERTICAL }
我怀疑问题和并发场景有关,请问多线程环境下创建LinearLayout是否存在安全问题?
解答
多线程环境下创建LinearLayout确实存在线程安全问题,你遇到的异常就是典型的并发场景触发的系统底层缓存冲突。
从崩溃栈可以定位到问题根源出在ViewConfiguration.get()方法:ViewConfiguration是Android系统存储UI通用配置(比如触摸超时、滚动速度等)的类,内部用SparseArray为不同屏幕配置的设备缓存对应实例,早期Android版本对这个缓存的读写没有加锁保护。当多个子线程同时首次调用ViewConfiguration的get方法时,可能同时触发缓存写入逻辑,导致SparseArray内部数组结构被破坏,最终抛出数组越界异常。
所有View的初始化逻辑都会调用ViewConfiguration.get(),所以不止LinearLayout,任何View在多线程并发初始化时都有可能触发这个问题,只是出现概率低,和你用Glide多线程生成图片的场景完全吻合。
你可以选择以下任意一种方案修复:
- 方案1:将View创建、布局绘制的逻辑全部挪到主线程串行执行。如果生成的Widget图片体积不大,这个方案最稳妥,不会有并发风险。
- 方案2:给View初始化的逻辑加全局锁,保证同一时间只有一个线程执行View创建操作,不需要调整现有线程调度,改造成本低。示例代码如下:
// 全局锁对象 private val viewCreateLock = Any() fun generateWidgetImage() { // 仅把View初始化逻辑放锁内即可,后续Bitmap存储逻辑可放在锁外避免影响性能 val layout = synchronized(viewCreateLock) { LinearLayout(context).apply { orientation = LinearLayout.VERTICAL } } // 后续填充布局、绘制、存文件逻辑 }
- 方案3:在应用启动阶段(比如Application的onCreate方法),提前在主线程调用一次
ViewConfiguration.get(context),提前完成缓存写入,后续子线程调用时只会读取缓存不会触发写入操作,也能规避并发问题,改造成本最低。
高版本Android已经修复了ViewConfiguration缓存的并发安全问题,但为了兼容低版本系统,还是建议采用上述任意一种方案做兜底处理。
内容的提问来源于stack exchange,提问作者박찬준

