前言
UE提供的图片资源,一般除了做下压缩外,都是直接使用的。不过有一次UE提供了一张1*1尺寸的小图片后,却引发了app一个UI crash。下面想分享下该问题的原因及解决方案。
问题描述
java.lang.llegalArgumentException: Dimensions must be positive! provided (0, 0)
系统要求的尺寸是正的,但提供系统的尺寸却是0,即尺寸是0导致了问题的出现。这需要结合堆栈信息进行分析、定位。
原因定位
堆栈信息如下。这里只展示了Android系统API的堆栈,业务代码调用的地方被省略了,不过这些信息已经足够定位原因。
java.lang.llegalArgumentException: Dimensions must be positive! provided (0, 0)
at android.graphics.ImageDecoder.setTargetSize(lmageDecoder.java:1033)
at android.graphics.lmageDecoder.computeDensity(lmageDecoder.java:1823)
at android.graphics.ImageDecoder.decodeDrawablelmpl(lmageDecoder. java:1670)
at android.graphics.ImageDecoder.decodeDrawable(lmageDecoder.java:1645)
at android.content.res.Resourceslmpl.decodelmageDrawable(Resourceslmpl.java:766)
at android.content.res.Resourceslmpl.loadDrawableForCookie(Resourceslmpl.java:839)
at android.content.res.Resourceslmpl.loadDrawable(Resourceslmpl.java:631)
at android.content.res.Resources.loadDrawable(Resources.java:897)
at android.content.res.TypedArray.getDrawableForDensity(TypedArray.java:955)
at android.content.res.TypedArray.getDrawable(TypedArray.java:930)
at android.widget.ImageView.(lmageView.java:189)
at android.widget.ImageView.(lmageView.java:172)
......
由堆栈信息的第2行可见,最终引发问题的是ImageDecoder类下的setTargetSize方法。这里打开Android的源码看看setTargetSize方法的具体实现,如下。
public void setTargetSize(@Px @IntRange(from = 1) int width,
@Px @IntRange(from = 1) int height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Dimensions must be positive! " + "provided (" + width + ", " + height + ")");
}
mDesiredWidth = width;
mDesiredHeight = height;
}
可见,当width=0或height=0,setTargetSize方法会抛出异常。那么为什么width、height会等于0呢?根据堆栈信息继续往下看。computeDensity方法的源码实现如下。
private int computeDensity(@NonNull Source src) {
if (this.requestedResize()) {
return Bitmap.DENSITY_NONE;
}
final int srcDensity = src.getDensity();
if (srcDensity == Bitmap.DENSITY_NONE) {
return srcDensity;
}
if (mIsNinePatch && mPostProcessor == null) {
return srcDensity;
}
Resources res = src.getResources();
if (res != null && res.getDisplayMetrics().noncompatDensityDpi == srcDensity) {
return srcDensity;
}
final int dstDensity = src.computeDstDensity();
if (srcDensity == dstDensity) {
return srcDensity;
}
if (srcDensity < dstDensity && sApiLevel >= Build.VERSION_CODES.P) {
return srcDensity;
}
float scale = (float) dstDensity / srcDensity;
int scaledWidth = (int) (mWidth * scale + 0.5f);
int scaledHeight = (int) (mHeight * scale + 0.5f);
this.setTargetSize(scaledWidth, scaledHeight);
return dstDensity;
}
setTargetSize的width、height就是通过computeDensity方法传入。mWidth、mHeight是图片的尺寸,两个都为1。即宽、高的计算公式都为(int)(1 * scale + 0.5f)。到这里会发现,导致宽、高为0的罪魁祸首是scale。scale的计算公式为(float)dstDensity/srcDensity。根据系统API对dstDensity、srcDensity的描述可知,dstDensity为屏幕像素密度,srcDensity为APP传入的密度参数、取决于放入了哪一个图片文件。
因为该图片放进了xxhdpi文件夹中,即srcDensity为480。dstDensity这个值就需要根据手机系统的具体情况来分析。假如一台手机的分辨率为1920 * 1080,4.5英寸。dstDensity的值就为(1920 * 1920 + 1080 * 1080)开根号、再除以4.5,约等于489.5。此时scale=489.5 / 480 = 1,计算得到的宽、高也会大于0,这种情况是不会抛出异常的。但假如是在宽度较大的平板,尺寸是13.5,那么得到的dstDensity约等于163。在这种情况下,根据宽、高的计算公式:(int)(1 * (163 / 480) + 0.5f) = 0。这时候系统就会抛出异常,导致APP crash。
解决方式
原因定位后,有两种解决方案:
- 方案1:找UE换一张尺寸不低于3*3的图片。
- 方案2:删除该1*1的图片。
和UE沟通后,该图片下个版本不会再使用,删除不会产生影响,于是采用了方案2。问题解决。
欢迎关注公众号度熊君,一起分享交流。