Bitmap.getColor(x, y)在API 28及以下版本崩溃问题求解
崩溃根因
Bitmap.getColor(int x, int y)是Android API 29(Android 10)才新增的公开方法,在API 28及以下版本的系统框架中不存在该方法,直接调用必然抛出NoSuchMethodError,和AWS Device Farm的运行环境无关,本地低版本模拟器可复现问题也印证了这一点。
兼容全版本的实现方案
1. 低版本兼容的Bitmap取色方法
使用从API 1就已存在的Bitmap.getPixel(x, y)方法替代不存在的getColor方法,该方法直接返回ARGB格式的颜色int值,完全满足取色需求,可封装为扩展函数统一调用:
import android.graphics.Bitmap import android.graphics.Color import kotlin.math.abs /** * 兼容全API版本的Bitmap取色方法,返回ARGB格式的颜色int值 */ fun Bitmap.getColorCompat(x: Int, y: Int): Int { return getPixel(x, y) } // 可选:扩展颜色通道读取方法,和高版本Color类的使用体验对齐 fun Int.colorAlpha() = Color.alpha(this) fun Int.colorRed() = Color.red(this) fun Int.colorGreen() = Color.green(this) fun Int.colorBlue() = Color.blue(this)
2. 适配复杂场景的视图取色逻辑
针对存在background drawable、ColorFilter、ColorStateList等多属性叠加的场景,不要单独读取视图属性值判断(属性值无法反映多效果叠加后的最终显示效果),直接将视图整体绘制到Bitmap后取色,所有渲染效果都会被完整捕获:
import android.graphics.Bitmap import android.graphics.Canvas import android.view.View fun View.captureDrawnContent(): Bitmap { // 处理视图未完成布局的边界场景 if (width == 0 || height == 0) { measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) layout(0, 0, measuredWidth, measuredHeight) } val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) // 绘制背景(包含shape/drawable、ColorStateList对应状态的颜色) background?.draw(canvas) // 绘制视图主体内容(ImageView的src、ColorFilter、文字颜色等所有渲染内容都会被绘制) draw(canvas) return bitmap }
3. Espresso测试Matcher封装
将上述逻辑整合为可直接使用的Espresso断言,注意颜色比对时增加容差,规避不同系统版本渲染抗锯齿带来的微小色值偏差,取色点优先选视图中心区域,避开边缘阴影、抗锯齿区域:
import android.view.View import androidx.test.espresso.matcher.BoundedMatcher import org.hamcrest.Description fun withExpectedColor(expectedColor: Int, colorTolerance: Int = 5) = object : BoundedMatcher<View, View>(View::class.java) { override fun describeTo(description: Description) { description.appendText("view displayed color matches expected value, tolerance: $colorTolerance") } override fun matchesSafely(view: View): Boolean { val drawnBitmap = view.captureDrawnContent() // 取视图中心点像素,避开边缘 val centerX = drawnBitmap.width / 2 val centerY = drawnBitmap.height / 2 val actualColor = drawnBitmap.getColorCompat(centerX, centerY) // 逐通道比对色值,差值在容差范围内即判定为匹配 return abs(actualColor.colorAlpha() - Color.alpha(expectedColor)) <= colorTolerance && abs(actualColor.colorRed() - Color.red(expectedColor)) <= colorTolerance && abs(actualColor.colorGreen() - Color.green(expectedColor)) <= colorTolerance && abs(actualColor.colorBlue() - Color.blue(expectedColor)) <= colorTolerance } }
注意事项
- 不要使用反射调用低版本不存在的
getColor方法,getPixel是官方公开稳定API,全版本兼容、性能更优 - 验证ColorStateList相关效果时,先将视图切换到对应状态(pressed/selected/disabled等)并等待视图重绘完成,再执行取色断言,避免取到未刷新的旧状态
- 对设置了elevation、阴影的视图,取色点要避开阴影区域,否则会拿到半透明的阴影色值导致断言失败
内容的提问来源于stack exchange,提问作者MACROSystems
相关产品推荐
相关产品推荐

