最近有用户反馈图片变成黑色,显示有点异常,因此作了追查,同时也是提醒本身后续写代码时候,要注意边界的检查。android
图片在Android 10系统变黑问题,一开始觉得是夜间模式等问题,由于在Android10开始有不少手机开始加多夜间模式,但排查不是这个致使的,通过排查是系统的bug。canvas
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:alpha="130"
android:src="@mipmap/alpha_img">
</bitmap>
复制代码
这小段代码,在Android 10.0 ( api 29 )如下系统是能正常显示图片,但在Android 10.0 时候图片会变成黑色。效果以下,右边是带alpha通道的正常图片,左边是在Android10系统下的绘制效果api
新的Android10系统在外部传入的alpha值时候,没有判断输入的数值是否符合规范,致使的溢出问题,同时也是平时方式不对致使的。ide
根据标签被解析的源码能够知道,对应的的alpha是赋值到了state.mBaseAlpha这个属性函数
看这段代码,咱们更肯定,咱们写在xml里面的应该是按照0-1.0f的范围的,由于这个paint.setAlpha 方法就是要求的0-255的范围啊;那么为何在Android10如下系统正常,就算在xml里面设置alpha值为【0-255】取值都正常,新版本的就出问题呢? this
综上,这个bug能够简单的在自定义View来作复现。google
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
Bitmap bitmap = getBitmapFromId(R.mipmap.details_slogan);
Paint p = new Paint();
p.setAlpha((int) (255 * 130 + 0.5f));
canvas.drawBitmap(bitmap, 0, 0, p);
}
复制代码
在明白了发生问题在setAlpha的地方上,咱们须要探究,为何同样的alpha值,在api29上有问题,在29如下的系统没问题。咱们看下对应的系统源码spa
@ColorLong
public static long pack(float red, float green, float blue, float alpha,
@NonNull ColorSpace colorSpace) {
if (colorSpace.isSrgb()) {
int argb =
((int) (alpha * 255.0f + 0.5f) << 24) |
((int) (red * 255.0f + 0.5f) << 16) |
((int) (green * 255.0f + 0.5f) << 8) |
(int) (blue * 255.0f + 0.5f);
return (argb & 0xffffffffL) << 32;
}
复制代码
看到这里对比起来两边都是正常的,虽然传入的值不符合范围要求,但由于使用pack函数里面有 <<24和 安位与 的操做,避免了传入数值错误致使的问题。3d
所以,有可能这个alpha的配置在native层面的代码发生了变动,咱们继续看下小于29api的native层的代码,这里以5.0系统代码做为参考rest
skpaint的代码
void SkPaint::setAlpha(U8CPU a) {
this->setColor(SkColorSetARGB(a, SkColorGetR(fColor),
SkColorGetG(fColor), SkColorGetB(fColor)));
}
void SkPaint::setColor(SkColor color) {
fColor = color;
}
复制代码
SkColorSetARGB
这个咱们到SkColor.h里面看这个函数
/** Return a SkColor value from 8 bit component values
*/
static constexpr inline SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
return SkASSERT(a <= 255 && r <= 255 && g <= 255 && b <= 255),
(a << 24) | (r << 16) | (g << 8) | (b << 0);
}
#define SkASSERTF(cond, fmt, ...) static_cast<void>(0)
复制代码
能够看到,对于外部传入的alpha,会通过 a<<24运算后再赋值,因此不会有显示错误问题。 接下来咱们看下10.0 的代码。
nSetAlpha的代码位置
static const JNINativeMethod methods[] = {
...
{"nSetColor","(JI)V", (void*) PaintGlue::setColor},
{"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong},
{"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha},
...
}
struct SkRGBA4f {
float fR; //!< red component
float fG; //!< green component
float fB; //!< blue component
...
}
static void setAlpha(jlong paintHandle, jint a) {
reinterpret_cast<Paint*>(paintHandle)->setAlpha(a);
}
// Helper that accepts an int between 0 and 255, and divides it by 255.0
void setAlpha(U8CPU a) {
this->setAlphaf(a * (1.0f / 255));
}
void SkPaint::setAlphaf(float a) {
SkASSERT(a >= 0 && a <= 1.0f);
fColor4f.fA = a;
}
复制代码
这个结构体定义的fA就是一个浮点型的数据。能够看到没有作数据的判断,对大于1.0f和小于0的数值,没有作转换为默认值1.0f的范围判断,而Android29如下的版本使用了 <<24的运算符,从而规避了这个问题。 最终在绘制的时候,当获取这个alpha时候是个错误的值,致使绘制异常。
既然咱们找到问题根源,那么对于Android 10.0的手机,咱们是否以对这个paint.setAlpha()的调用,改成paint.setColor的调用,从而证实就是这个脏数据致使的呢,由于setColor()有作校验。从而再也不去看canvas.drawBitmap()的详细内容,判断出就是这个地方的问题呢?因此咱们加多这段
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
Bitmap bitmap = getBitmapFromId( R.mipmap.details_slogan );
Paint p = new Paint();
// p.setAlpha((int) (255 * 130 + 0.5f));
// 等价于下面这段代码
ColorSpace cs = Color.colorSpace(p.getColorLong());
float r = Color.red(p.getColorLong());
float g = Color.green(p.getColorLong());
float b = Color.blue(p.getColorLong());
p.setColor(Color.pack(r, g, b, (255 * 130) * (1.0f / 255), cs));
canvas.drawBitmap(bitmap, 0, 0, p);
}
复制代码
这段代码和以前直接调用setAlpha是等价的,只是换成了setColor的方式来修改alpha。运行后发现显示效果正常,因此能够判断就是这个Android10系统在这个地方有bug,给google提个pr去😊
因为定位到位置在哪里,就不贴设置了颜色后在native层的绘制流程内容,有兴趣的能够去看下。 由于这个 fA值没在作校验致使的问题,也但愿本身借鉴,对阈值边界作判断,而后尽可能对于异常状况直接抛异常处理,避免线上引入。