最近有用戶反饋圖片變成黑色,顯示有點異常,因此作了追查,同時也是提醒本身後續寫代碼時候,要注意邊界的檢查。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這個屬性函數
看這段api29時候的bitmapDrawable的代碼段,咱們知道這個標籤裏面的alpha應該是設置浮點,取值是0-1.0f的範圍纔是真確的,但以前不少人是當0-255的範圍來設置也沒問題,具體後面來說。 而後在繪製的時候給到了paint的setAlpha裏面去的,其中restoreAlpha是255看這段代碼,咱們更肯定,咱們寫在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值沒在作校驗致使的問題,也但願本身借鑑,對閾值邊界作判斷,而後儘可能對於異常狀況直接拋異常處理,避免線上引入。