You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 19:54:23